Update: 2026-06-11 18:22:57

This commit is contained in:
Hamza-Ayed
2026-06-11 18:22:59 +03:00
parent c5170a88d2
commit 727068b668
629 changed files with 46050 additions and 46109 deletions

View File

@@ -1,5 +1,4 @@
import 'package:siro_rider/constant/links.dart';
import 'package:siro_rider/views/home/map_page_passenger.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../main.dart';
@@ -8,6 +7,7 @@ import '../../views/auth/otp_page.dart';
import '../../views/widgets/error_snakbar.dart';
import '../functions/crud.dart';
import '../functions/package_info.dart';
import '../functions/country_logic.dart';
import 'login_controller.dart';
// --- Helper Class for Phone Authentication ---
@@ -19,67 +19,13 @@ class PhoneAuthHelper {
static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php';
static final String _registerUrl = '${_baseUrl}register_passenger.php';
static String formatSyrianPhone(String phone) {
// Remove spaces, symbols, +, -, ()
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
// Normalize 00963 → 963
if (phone.startsWith('00963')) {
phone = phone.replaceFirst('00963', '963');
}
// Normalize 0963 → 963
if (phone.startsWith('0963')) {
phone = phone.replaceFirst('0963', '963');
}
// NEW: Fix 96309xxxx → 9639xxxx
if (phone.startsWith('96309')) {
phone = '9639' + phone.substring(5); // remove the "0" after 963
}
// If starts with 9630 → correct to 9639
if (phone.startsWith('9630')) {
phone = '9639' + phone.substring(4);
}
// If already in correct format: 9639xxxxxxxx
if (phone.startsWith('9639') && phone.length == 12) {
return phone;
}
// If starts with 963 but missing the 9
if (phone.startsWith('963') && phone.length > 3) {
// Ensure it begins with 9639
if (!phone.startsWith('9639')) {
phone = '9639' + phone.substring(3);
}
return phone;
}
// If starts with 09xxxxxxxx → 9639xxxxxxxx
if (phone.startsWith('09')) {
return '963' + phone.substring(1);
}
// If 9xxxxxxxx (9 digits)
if (phone.startsWith('9') && phone.length == 9) {
return '963' + phone;
}
// If starts with incorrect 0xxxxxxx → assume Syrian and fix
if (phone.startsWith('0') && phone.length == 10) {
return '963' + phone.substring(1);
}
return phone;
}
// removed formatSyrianPhone
/// Sends an OTP to the provided phone number.
static Future<bool> sendOtp(String phoneNumber) async {
try {
// إصلاح الرقم قبل الإرسال
final fixedPhone = formatSyrianPhone(phoneNumber);
final fixedPhone = CountryLogic.formatCurrentCountryPhone(phoneNumber);
final response = await CRUD().post(
link: _sendOtpUrl,
@@ -109,7 +55,7 @@ class PhoneAuthHelper {
static Future<void> verifyOtp(String phoneNumber, String otpCode) async {
try {
final fixedPhone = formatSyrianPhone(phoneNumber);
final fixedPhone = CountryLogic.formatCurrentCountryPhone(phoneNumber);
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {

View File

@@ -666,7 +666,7 @@ class DriverTipWidget extends StatelessWidget {
Toast.show(
context,
'${'Tip is '.tr}${(controller.totalPassenger) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
'${'Tip is '.tr}${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
@@ -685,7 +685,7 @@ class DriverTipWidget extends StatelessWidget {
box.write(BoxName.tipPercentage, '0.10');
Toast.show(
context,
'${'Tip is'.tr} ${(controller.totalPassenger) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
'${'Tip is'.tr} ${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
@@ -704,7 +704,7 @@ class DriverTipWidget extends StatelessWidget {
box.write(BoxName.tipPercentage, '0.15');
Toast.show(
context,
'${'Tip is'.tr} ${(controller.totalPassenger) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
'${'Tip is'.tr} ${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
@@ -723,7 +723,7 @@ class DriverTipWidget extends StatelessWidget {
box.write(BoxName.tipPercentage, '0.20');
Toast.show(
context,
'${'Tip is'.tr} ${(controller.totalPassenger) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
'${'Tip is'.tr} ${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))}',
AppColor.cyanBlue);
controller.update();
},
@@ -754,7 +754,7 @@ class DriverTipWidget extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(6),
child: Text(
'${(controller.totalPassenger) * (double.parse(box.read(BoxName.tipPercentage.toString())))} ${box.read(BoxName.countryCode) == 'Egypt' ? 'LE'.tr : 'JOD'.tr}',
'${(double.parse(controller.totalPassenger.toString())) * (double.parse(box.read(BoxName.tipPercentage.toString())))} ${box.read(BoxName.countryCode) == 'Egypt' ? 'LE'.tr : 'JOD'.tr}',
style: AppStyle.title,
),
),

View File

@@ -0,0 +1,94 @@
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/main.dart';
class CountryLogic {
/// Formats the phone number based on the country's dialing rules.
static String formatPhone(String phone, String country) {
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
if (country == 'Egypt') {
// Rule: 010, 011, 012, 015 -> 2010, 2011, 2012, 2015
if (phone.startsWith('0020')) phone = phone.replaceFirst('0020', '20');
if (phone.startsWith('01') && phone.length >= 10) {
return '20${phone.substring(1)}';
}
if (phone.startsWith('1') &&
phone.length >= 9 &&
!phone.startsWith('20')) {
return '20$phone';
}
if (!phone.startsWith('20') && phone.length > 8) return '20$phone';
} else if (country == 'Jordan') {
// Rule: 07x -> 9627x
if (phone.startsWith('00962')) phone = phone.replaceFirst('00962', '962');
if (phone.startsWith('07') && phone.length >= 9) {
return '962${phone.substring(1)}';
}
if (phone.startsWith('7') &&
phone.length >= 8 &&
!phone.startsWith('962')) {
return '962$phone';
}
if (!phone.startsWith('962') && phone.length > 7) return '962$phone';
} else {
// Default to Syria
if (phone.startsWith('00963')) phone = phone.replaceFirst('00963', '963');
if (phone.startsWith('0963')) phone = phone.replaceFirst('0963', '963');
if (phone.startsWith('096309')) {
phone = phone.replaceFirst('096309', '963');
}
if (phone.startsWith('96309')) phone = '9639${phone.substring(5)}';
if (phone.startsWith('9630')) phone = '9639${phone.substring(4)}';
if (phone.startsWith('9639') && phone.length == 12) return phone;
if (phone.startsWith('963') &&
phone.length > 3 &&
!phone.startsWith('9639')) {
phone = '9639${phone.substring(3)}';
}
if (phone.startsWith('09')) return '963${phone.substring(1)}';
if (phone.startsWith('9') && phone.length == 9) return '963$phone';
if (phone.startsWith('0') && phone.length == 10) {
return '963${phone.substring(1)}';
}
}
return phone;
}
/// Returns the default country prefix (EG, JO, SY) for UI initial selection.
static String getCountryPrefix(String country) {
if (country == 'Egypt') return 'EG';
if (country == 'Jordan') return 'JO';
return 'SY';
}
/// Returns the default emergency number for the country.
static String getEmergencyNumber(String country) {
if (country == 'Egypt') return '122';
if (country == 'Jordan') return '911';
return '112'; // Syria
}
/// Returns the hint text for phone inputs based on the country.
static String getPhoneHint(String country) {
if (country == 'Egypt') return 'e.g. 01012345678 (Default +20)';
if (country == 'Jordan') return 'e.g. 0791234567 (Default +962)';
return 'e.g. 0912345678 (Default +963)'; // Syria
}
/// Helper to format phone using the current country in box.
static String formatCurrentCountryPhone(String phone) {
String cleanPhone = phone.replaceAll(RegExp(r'[ \-\(\)]'), '').trim();
if (cleanPhone.startsWith('+963') || cleanPhone.startsWith('00963')) {
return formatPhone(cleanPhone, 'Syria');
}
if (cleanPhone.startsWith('+20') || cleanPhone.startsWith('0020')) {
return formatPhone(cleanPhone, 'Egypt');
}
if (cleanPhone.startsWith('+962') || cleanPhone.startsWith('00962')) {
return formatPhone(cleanPhone, 'Jordan');
}
final country = box.read(BoxName.countryCode) ?? 'Syria';
return formatPhone(cleanPhone, country);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:siro_rider/print.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
import 'package:siro_rider/controller/functions/country_logic.dart';
void showInBrowser(String url) async {
if (await canLaunchUrl(Uri.parse(url))) {
@@ -9,16 +10,11 @@ void showInBrowser(String url) async {
}
Future<void> makePhoneCall(String phoneNumber) async {
// 1. تنظيف الرقم (إزالة المسافات والفواصل)
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
// 2. منطق التنسيق (مع الحفاظ على الأرقام القصيرة مثل 112 كما هي)
if (formattedNumber.length > 6) {
if (formattedNumber.startsWith('09')) {
// إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي) -> +963
formattedNumber = '+963${formattedNumber.substring(1)}';
} else if (!formattedNumber.startsWith('+')) {
// إذا لم يكن دولياً ولا محلياً معروفاً -> إضافة + فقط
formattedNumber = CountryLogic.formatCurrentCountryPhone(formattedNumber);
if (!formattedNumber.startsWith('+')) {
formattedNumber = '+$formattedNumber';
}
}

View File

@@ -125,25 +125,23 @@ class RideLifecycleController extends GetxController {
late String driverCompletedRides = '0';
late String driverTier = 'Verified driver';
late String driverToken = '';
double kazan = 8;
double totalPassenger = 0;
String totalPassenger = '0';
double totalDriver = 0;
double costDistance = 0;
double costDuration = 0;
double averageDuration = 0;
double totalCostPassenger = 0;
String totalCostPassenger = '0';
double totalPassengerSpeed = 0;
double totalPassengerBalash = 0;
double totalPassengerComfort = 0;
double totalPassengerElectric = 0;
double totalPassengerLady = 0;
double totalPassengerScooter = 0;
double totalPassengerVan = 0;
double totalPassengerRayehGai = 0;
double totalPassengerRayehGaiComfort = 0;
double totalPassengerRayehGaiBalash = 0;
String totalPassengerSpeed = '0';
String totalPassengerBalash = '0';
String totalPassengerComfort = '0';
String totalPassengerElectric = '0';
String totalPassengerLady = '0';
String totalPassengerScooter = '0';
String totalPassengerVan = '0';
String totalPassengerRayehGai = '0';
String totalPassengerRayehGaiComfort = '0';
String totalPassengerRayehGaiBalash = '0';
double latePrice = 0;
double fuelPrice = 0;
@@ -744,7 +742,7 @@ class RideLifecycleController extends GetxController {
style: TextStyle(color: AppColor.greenColor)),
onPressed: () {
Get.back();
double newPrice = totalPassenger * 1.10;
double newPrice = double.parse(totalPassenger) * 1.10;
increasePriceAndRestartSearch(newPrice);
},
),
@@ -755,7 +753,7 @@ class RideLifecycleController extends GetxController {
}
Future<void> increasePriceAndRestartSearch(double newPrice) async {
totalPassenger = newPrice;
totalPassenger = newPrice.toStringAsFixed(2);
update();
await CRUD()
@@ -822,6 +820,7 @@ class RideLifecycleController extends GetxController {
_isRideStartedProcessed = true;
currentRideState.value = RideState.inProgress;
statusRide = 'Begin';
box.write(BoxName.passengerWalletTotal, '0');
remainingTimeDriverWaitPassenger5Minute = 0;
_stopWaitPassengerTimer();
@@ -1413,7 +1412,7 @@ class RideLifecycleController extends GetxController {
"date": DateTime.now().toString(),
"time": DateTime.now().toString(),
"endtime": "00:00:00",
"price": totalPassenger.toStringAsFixed(2),
"price": double.parse(totalPassenger.toString()).toStringAsFixed(2),
"passenger_id": box.read(BoxName.passengerID).toString(),
"driver_id": "0",
"status": "waiting",
@@ -1694,7 +1693,6 @@ class RideLifecycleController extends GetxController {
box.write(BoxName.serverChosen, AppLink.server);
if (newCountry != previousCountry) {
unawaited(getKazanPercent());
}
return newCountry;
@@ -1775,135 +1773,56 @@ class RideLifecycleController extends GetxController {
void applyPromoCodeToPassenger(BuildContext context) async {
if (promoTaken == true) {
MyDialog().getDialog(
'Promo Already Used'.tr,
'You have already used this promo code.'.tr,
() => Get.back(),
);
MyDialog().getDialog('Promo Already Used'.tr, 'You have already used this promo code.'.tr, () => Get.back());
return;
}
if (!promoFormKey.currentState!.validate()) return;
const double minPromoLowSYP = 172;
const double minPromoHighSYP = 200;
try {
final value = await CRUD().get(
link: AppLink.getPassengersPromo,
payload: {'promo_code': promo.text},
);
final res = await CRUD().post(link: AppLink.getPrices, payload: {
'distance': distance.toString(),
'durationToRide': durationToRide.toString(),
'startNameAddress': startNameAddress,
'endNameAddress': endNameAddress,
'destLat': myDestination.latitude.toString(),
'destLng': myDestination.longitude.toString(),
'passengerLat': newMyLocation.latitude.toString(),
'passengerLng': newMyLocation.longitude.toString(),
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
'promo_code': promo.text,
'passenger_id' :box.read(BoxName.passengerID),
'country': box.read(BoxName.countryCode) ?? '',
});
if (value == 'failure') {
MyDialog().getDialog(
'Promo Ended'.tr,
'The promotion period has ended.'.tr,
() => Get.back(),
);
return;
}
final bool eligibleNow = (totalPassengerSpeed >= minPromoLowSYP) ||
(totalPassengerBalash >= minPromoLowSYP) ||
(totalPassengerComfort >= minPromoHighSYP) ||
(totalPassengerElectric >= minPromoHighSYP) ||
(totalPassengerLady >= minPromoHighSYP);
if (!eligibleNow) {
Get.snackbar(
'Lowest Price Achieved'.tr,
'Cannot apply further discounts.'.tr,
backgroundColor: AppColor.yellowColor,
);
return;
}
final decode = jsonDecode(value);
if (decode["status"] != "success") {
MyDialog().getDialog(
'Promo Ended'.tr,
'The promotion period has ended.'.tr,
() => Get.back(),
);
return;
}
Get.snackbar('Promo Code Accepted'.tr, '',
backgroundColor: AppColor.greenColor);
final firstElement = decode["message"][0];
final int discountPercentage =
int.tryParse(firstElement['amount'].toString()) ?? 0;
final double walletVal = double.tryParse(
box.read(BoxName.passengerWalletTotal)?.toString() ?? '0') ??
0.0;
final bool isWalletNegative = walletVal < 0;
double _applyDiscountPerTier({
required double fare,
required double minThreshold,
required bool isWalletNegative,
}) {
if (fare < minThreshold) return fare;
final double discount = fare * (discountPercentage / 100.0);
double result;
if (isWalletNegative) {
double neg = (-1) * walletVal;
result = fare + neg - discount;
if (res != 'failure') {
var response = jsonDecode(res);
if (response['status'] == 'success') {
var data = response['data'];
totalPassengerSpeed = data['totalPassengerSpeed']?.toString() ?? '0';
totalPassengerBalash = data['totalPassengerBalash']?.toString() ?? '0';
totalPassengerComfort = data['totalPassengerComfort']?.toString() ?? '0';
totalPassengerElectric = data['totalPassengerElectric']?.toString() ?? '0';
totalPassengerLady = data['totalPassengerLady']?.toString() ?? '0';
totalPassengerScooter = data['totalPassengerScooter']?.toString() ?? '0';
totalPassengerVan = data['totalPassengerVan']?.toString() ?? '0';
totalPassengerRayehGai = data['totalPassengerRayehGai']?.toString() ?? '0';
totalPassengerRayehGaiComfort = data['totalPassengerRayehGaiComfort']?.toString() ?? '0';
totalPassengerRayehGaiBalash = data['totalPassengerRayehGaiBalash']?.toString() ?? '0';
promoTaken = true;
update();
Confetti.launch(
context,
options: const ConfettiOptions(particleCount: 100, spread: 70, y: 0.6),
);
} else {
result = fare - discount;
MyDialog().getDialog('Promo Error'.tr, response['message']?.toString() ?? 'Invalid Promo'.tr, () => Get.back());
return;
}
if (result < minThreshold) {
result = minThreshold;
}
return result.clamp(0.0, double.infinity);
}
totalPassengerComfort = _applyDiscountPerTier(
fare: totalPassengerComfort,
minThreshold: minPromoHighSYP,
isWalletNegative: isWalletNegative,
);
totalPassengerElectric = _applyDiscountPerTier(
fare: totalPassengerElectric,
minThreshold: minPromoHighSYP,
isWalletNegative: isWalletNegative,
);
totalPassengerLady = _applyDiscountPerTier(
fare: totalPassengerLady,
minThreshold: minPromoHighSYP,
isWalletNegative: isWalletNegative,
);
totalPassengerSpeed = _applyDiscountPerTier(
fare: totalPassengerSpeed,
minThreshold: minPromoLowSYP,
isWalletNegative: isWalletNegative,
);
totalPassengerBalash = _applyDiscountPerTier(
fare: totalPassengerBalash,
minThreshold: minPromoLowSYP,
isWalletNegative: isWalletNegative,
);
totalDriver = totalDriver - (totalDriver * discountPercentage / 100.0);
promoTaken = true;
update();
Confetti.launch(
context,
options: const ConfettiOptions(particleCount: 100, spread: 70, y: 0.6),
);
Get.back();
Get.back();
await Future.delayed(const Duration(milliseconds: 120));
} catch (e) {
Get.snackbar('Error'.tr, e.toString(),
@@ -1920,236 +1839,49 @@ class RideLifecycleController extends GetxController {
double costForDriver = 0;
Future bottomSheet() async {
const double minFareSYP = 160;
const double minBillableKm = 0.3;
const double ladyFlatAddon = 20;
const double airportAddonSYP = 200;
const double damascusAirportBoundAddon = 1400;
const double electricPerKmUplift = 4;
const double electricFlatAddon = 10;
const double longSpeedThresholdKm = 40.0;
const double longSpeedPerKm = 26.0;
const double mediumDistThresholdKm = 25.0;
const double longDistThresholdKm = 35.0;
const double longTripPerMin = 6.0;
const int minuteCapMedium = 60;
const int minuteCapLong = 80;
const int freeMinutesLong = 10;
const double extraReduction100 = 0.07;
const double maxReductionCap = 0.35;
durationToAdd = Duration(seconds: durationToRide);
hours = durationToAdd.inHours;
minutes = (durationToAdd.inMinutes % 60).round();
final DateTime currentTime = DateTime.now();
newTime = currentTime.add(durationToAdd);
averageDuration = (durationToRide / 60) / distance;
final int waypointSurchargeMinutes = activeMenuWaypointCount * 5;
final int totalMinutes =
(durationToRide / 60).floor() + waypointSurchargeMinutes;
bool _isAirport(String s) {
final t = s.toLowerCase();
return t.contains('airport') ||
s.contains('مطار') ||
s.contains('المطار');
}
bool _isClub(String s) {
final t = s.toLowerCase();
return t.contains('club') ||
t.contains('nightclub') ||
t.contains('night club') ||
s.contains('ديسكو') ||
s.contains('ملهى ليلي');
}
bool _isInsideDamascusAirportBounds(double lat, double lng) {
final double northLat = 33.415313;
final double southLat = 33.400265;
final double eastLng = 36.531505;
final double westLng = 36.499687;
bool isLatInside = (lat <= northLat) && (lat >= southLat);
bool isLngInside = (lng <= eastLng) && (lng >= westLng);
return isLatInside && isLngInside;
}
final double naturePerMin = naturePrice;
final double latePerMin = latePrice;
final double heavyPerMin = heavyPrice;
double _perMinuteByTime(DateTime now, bool clubCtx) {
final h = now.hour;
if (h >= 21 || h < 1) return latePerMin;
if (h >= 1 && h < 5) return clubCtx ? (latePerMin * 2) : latePerMin;
if (h >= 14 && h <= 17) return heavyPerMin;
return naturePerMin;
}
double _applyMinFare(double fare) =>
(fare < minFareSYP) ? minFareSYP : fare;
double _withCommission(double base) =>
(base * (1 + kazan / 100)).ceilToDouble();
final bool airportCtx =
_isAirport(startNameAddress) || _isAirport(endNameAddress);
final bool clubCtx = _isClub(startNameAddress) || _isClub(endNameAddress);
double destLat = 0.0;
double destLng = 0.0;
try {
destLat = myDestination.latitude;
destLng = myDestination.longitude;
} catch (_) {
if (locSearch.coordinatesWithoutEmpty.isNotEmpty) {
destLat = double.tryParse(
locSearch.coordinatesWithoutEmpty.last.split(',')[0]) ??
0.0;
destLng = double.tryParse(
locSearch.coordinatesWithoutEmpty.last.split(',')[1]) ??
0.0;
}
}
final bool damascusAirportBoundCtx =
_isInsideDamascusAirportBounds(destLat, destLng);
final bool isInDamascusAirportBoundCtx = _isInsideDamascusAirportBounds(
newMyLocation.latitude.toDouble(),
newMyLocation.longitude.toDouble(),
);
final double billableDistance =
(distance < minBillableKm) ? minBillableKm : distance;
final bool isLongSpeed = billableDistance > longSpeedThresholdKm;
final double perKmSpeedBaseFromServer = speedPrice;
final double perKmSpeed =
isLongSpeed ? longSpeedPerKm : perKmSpeedBaseFromServer;
double reductionPct40 = 0.0;
if (perKmSpeedBaseFromServer > 0) {
reductionPct40 = (1.0 - (longSpeedPerKm / perKmSpeedBaseFromServer))
.clamp(0.0, maxReductionCap);
}
final double reductionPct100 =
(reductionPct40 + extraReduction100).clamp(0.0, maxReductionCap);
double distanceReduction = 0.0;
if (billableDistance > 100.0) {
distanceReduction = reductionPct100;
} else if (billableDistance > 40.0) {
distanceReduction = reductionPct40;
}
double effectivePerMin = _perMinuteByTime(currentTime, clubCtx);
int billableMinutes = totalMinutes;
if (billableDistance > longDistThresholdKm) {
effectivePerMin = longTripPerMin;
final int capped =
(billableMinutes > minuteCapLong) ? minuteCapLong : billableMinutes;
billableMinutes = capped - freeMinutesLong;
if (billableMinutes < 0) billableMinutes = 0;
} else if (billableDistance > mediumDistThresholdKm) {
effectivePerMin = longTripPerMin;
billableMinutes = (billableMinutes > minuteCapMedium)
? minuteCapMedium
: billableMinutes;
}
final double perKmComfortRaw = comfortPrice;
final double perKmDelivery = deliveryPrice;
final double perKmVanRaw =
(familyPrice > 0 ? familyPrice : (speedPrice + 13));
final double perKmElectricRaw = perKmComfortRaw + electricPerKmUplift;
double perKmComfort = perKmComfortRaw * (1.0 - distanceReduction);
double perKmElectric = perKmElectricRaw * (1.0 - distanceReduction);
double perKmVan = perKmVanRaw * (1.0 - distanceReduction);
perKmComfort = perKmComfort.clamp(0, double.infinity);
perKmElectric = perKmElectric.clamp(0, double.infinity);
perKmVan = perKmVan.clamp(0, double.infinity);
final double perKmBalash = (perKmSpeed - 5).clamp(0, double.infinity);
double _oneWayFare({
required double perKm,
required bool isLady,
double flatAddon = 0,
}) {
double fare = billableDistance * perKm;
fare += billableMinutes * effectivePerMin;
fare += flatAddon;
if (isLady) fare += ladyFlatAddon;
if (airportCtx) fare += airportAddonSYP;
if (damascusAirportBoundCtx || isInDamascusAirportBoundCtx) {
fare += damascusAirportBoundAddon;
}
return _applyMinFare(fare);
}
double _roundTripFare({required double perKm}) {
double distPart =
(billableDistance * 2 * perKm) - ((billableDistance * perKm) * 0.4);
double timePart = (billableMinutes * 2) * effectivePerMin;
double fare = distPart + timePart;
if (airportCtx) fare += airportAddonSYP;
if (damascusAirportBoundCtx || isInDamascusAirportBoundCtx) {
fare += damascusAirportBoundAddon;
}
return _applyMinFare(fare);
}
final double costSpeed = _oneWayFare(perKm: perKmSpeed, isLady: false);
final double costBalash = _oneWayFare(perKm: perKmBalash, isLady: false);
final double costComfort = _oneWayFare(perKm: perKmComfort, isLady: false);
final double costElectric = _oneWayFare(
perKm: perKmElectric, isLady: false, flatAddon: electricFlatAddon);
final double costDelivery =
_oneWayFare(perKm: perKmDelivery, isLady: false);
final double costLady = _oneWayFare(perKm: perKmComfort, isLady: true);
final double costVan = _oneWayFare(perKm: perKmVan, isLady: false);
final double costRayehGai = _roundTripFare(perKm: perKmSpeed);
final double costRayehGaiComfort = _roundTripFare(perKm: perKmComfort);
final double costRayehGaiBalash = _roundTripFare(perKm: perKmBalash);
totalPassengerSpeed = _withCommission(costSpeed);
totalPassengerBalash = _withCommission(costBalash);
totalPassengerComfort = _withCommission(costComfort);
totalPassengerElectric = _withCommission(costElectric);
totalPassengerLady = _withCommission(costLady);
totalPassengerScooter = _withCommission(costDelivery);
totalPassengerVan = _withCommission(costVan);
totalPassengerRayehGai = _withCommission(costRayehGai);
totalPassengerRayehGaiComfort = _withCommission(costRayehGaiComfort);
totalPassengerRayehGaiBalash = _withCommission(costRayehGaiBalash);
totalPassenger = totalPassengerSpeed;
totalCostPassenger = totalPassenger;
try {
final walletStr = box.read(BoxName.passengerWalletTotal).toString();
final walletVal = double.tryParse(walletStr) ?? 0.0;
if (walletVal < 0) {
final neg = (-1) * walletVal;
totalPassenger += neg;
totalPassengerComfort += neg;
totalPassengerElectric += neg;
totalPassengerLady += neg;
totalPassengerBalash += neg;
totalPassengerScooter += neg;
totalPassengerRayehGai += neg;
totalPassengerRayehGaiComfort += neg;
totalPassengerRayehGaiBalash += neg;
totalPassengerVan += neg;
final res = await CRUD().post(link: AppLink.getPrices, payload: {
'distance': distance.toString(),
'durationToRide': durationToRide.toString(),
'startNameAddress': startNameAddress,
'endNameAddress': endNameAddress,
'destLat': myDestination.latitude.toString(),
'destLng': myDestination.longitude.toString(),
'passengerLat': newMyLocation.latitude.toString(),
'passengerLng': newMyLocation.longitude.toString(),
'walletVal': box.read(BoxName.passengerWalletTotal)?.toString() ?? '0',
'activeMenuWaypointCount': activeMenuWaypointCount.toString(),
'passenger_id': box.read(BoxName.passengerID) ?? '',
'country': box.read(BoxName.countryCode) ?? '',
});
if (res != 'failure') {
var response = jsonDecode(res);
if (response['status'] == 'success') {
var data = response['data'];
totalPassengerSpeed = data['totalPassengerSpeed']?.toString() ?? '0';
totalPassengerBalash = data['totalPassengerBalash']?.toString() ?? '0';
totalPassengerComfort = data['totalPassengerComfort']?.toString() ?? '0';
totalPassengerElectric = data['totalPassengerElectric']?.toString() ?? '0';
totalPassengerLady = data['totalPassengerLady']?.toString() ?? '0';
totalPassengerScooter = data['totalPassengerScooter']?.toString() ?? '0';
totalPassengerVan = data['totalPassengerVan']?.toString() ?? '0';
totalPassengerRayehGai = data['totalPassengerRayehGai']?.toString() ?? '0';
totalPassengerRayehGaiComfort = data['totalPassengerRayehGaiComfort']?.toString() ?? '0';
totalPassengerRayehGaiBalash = data['totalPassengerRayehGaiBalash']?.toString() ?? '0';
totalPassenger = totalPassengerSpeed;
totalCostPassenger = totalPassenger;
}
}
} catch (e) {
Log.print("Error: $e");
Log.print("Error fetching prices: $e");
}
update();
@@ -2694,7 +2426,7 @@ class RideLifecycleController extends GetxController {
"end_location": '${endLoc.latitude},${endLoc.longitude}',
"date": DateTime.now().toString(),
"time": DateTime.now().toString(),
"price": totalPassenger.toStringAsFixed(2),
"price": double.parse(totalPassenger.toString()).toStringAsFixed(2),
'passenger_id': box.read(BoxName.passengerID).toString(),
'status': 'waiting',
'carType': box.read(BoxName.carType),
@@ -2720,144 +2452,9 @@ class RideLifecycleController extends GetxController {
double familyPrice = 55;
double deliveryPrice = 1.2;
Future<void> getKazanPercent() async {
var res = await CRUD().get(
link: AppLink.getKazanPercent,
payload: {'country': box.read(BoxName.countryCode).toString()},
);
if (res != 'failure') {
var json = jsonDecode(res);
var dataList = json['data'] ?? json['message'];
if (dataList != null && dataList is List && dataList.isNotEmpty) {
var firstRow = dataList[0];
kazan = double.parse(firstRow['kazan'].toString());
naturePrice = double.parse(firstRow['naturePrice'].toString());
heavyPrice = double.parse(firstRow['heavyPrice'].toString());
latePrice = double.parse(firstRow['latePrice'].toString());
comfortPrice = double.parse(firstRow['comfortPrice'].toString());
speedPrice = double.parse(firstRow['speedPrice'].toString());
deliveryPrice = double.parse(firstRow['deliveryPrice'].toString());
mashwariPrice = double.parse(firstRow['freePrice'].toString());
familyPrice = double.parse(firstRow['familyPrice'].toString());
fuelPrice = double.parse(firstRow['fuelPrice'].toString());
}
}
}
Future<void> getPassengerRate() async {
var res = await CRUD().get(
link: AppLink.getPassengerRate,
payload: {'passenger_id': box.read(BoxName.passengerID)});
if (res != 'failure') {
var json = jsonDecode(res);
var message = json['data'] ?? json['message'];
if (message['rating'] == null) {
passengerRate = 5.0;
} else {
var rating = message['rating'];
if (rating is String) {
passengerRate = double.tryParse(rating) ?? 5.0;
} else if (rating is num) {
passengerRate = rating.toDouble();
} else {
passengerRate = 5.0;
}
}
} else {
passengerRate = 5.0;
}
}
Future<void> addFingerPrint() async {
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await CRUD().postWallet(link: AppLink.addFingerPrint, payload: {
'token': (box.read(BoxName.tokenFCM.toString())),
'passengerID': box.read(BoxName.passengerID).toString(),
"fingerPrint": fingerPrint
});
}
Future<void> firstTimeRunToGetCoupon() async {
if (box.read(BoxName.isFirstTime).toString() == '0' &&
box.read(BoxName.isInstall).toString() == '1' &&
box.read(BoxName.isGiftToken).toString() == '0') {
var promoCode, discount, validity;
var resPromo = await CRUD().get(link: AppLink.getPromoFirst, payload: {
"passengerID": box.read(BoxName.passengerID).toString(),
});
if (resPromo != 'failure') {
var d1 = jsonDecode(resPromo);
promoCode = d1['message']['promo_code'];
discount = d1['message']['amount'];
validity = d1['message']['validity_end_date'];
}
box.write(BoxName.isFirstTime, '1');
Get.dialog(
AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
width: 300,
child: PromoBanner(
promoCode: promoCode,
discountPercentage: discount,
validity: validity,
),
),
),
);
}
}
Future<void> detectAndCacheDeviceTier() async {
bool isHighEnd = await DevicePerformanceManager.isHighEndDevice();
Log.print("Device Analysis - Is Flagship/HighEnd? $isHighEnd");
box.write(BoxName.lowEndMode, !isHighEnd);
}
Future<void> initilizeGetStorage() async {
if (box.read(BoxName.addWork) == null) {
box.write(BoxName.addWork, 'addWork');
}
if (box.read(BoxName.addHome) == null) {
box.write(BoxName.addHome, 'addHome');
}
if (box.read(BoxName.lowEndMode) == null) {
detectAndCacheDeviceTier();
}
}
Future<void> selectDriverAndCarForMishwariTrip() async {
double latitudeOffset = 0.1;
double longitudeOffset = 0.12;
double southwestLat = passengerLocation.latitude - latitudeOffset;
double northeastLat = passengerLocation.latitude + latitudeOffset;
double southwestLon = passengerLocation.longitude - longitudeOffset;
double northeastLon = passengerLocation.longitude + longitudeOffset;
var payload = {
'southwestLat': southwestLat.toString(),
'northeastLat': northeastLat.toString(),
'southwestLon': southwestLon.toString(),
'northeastLon': northeastLon.toString(),
};
Future selectDriverAndCarForMishwariTrip() async {
try {
var res = await CRUD().get(
link: AppLink.selectDriverAndCarForMishwariTrip, payload: payload);
if (res != 'failure') {
try {
var d = jsonDecode(res);
driversForMishwari = d['message'];
Log.print('driversForMishwari: $driversForMishwari');
update();
} catch (e) {
Log.print("Error decoding JSON: $e");
}
}
// Logic for mishwari trip driver selection
} catch (e) {
Log.print("Error Mishwari select: $e");
}
@@ -4311,7 +3908,6 @@ class RideLifecycleController extends GetxController {
Future<void> _stagePricingAndState() async {
try {
await getKazanPercent();
} catch (e) {
Log.print("Error: $e");
}
@@ -4663,4 +4259,68 @@ class RideLifecycleController extends GetxController {
mapEngine.update();
update();
}
initilizeGetStorage() async {
if (box.read(BoxName.addWork) == null) {
box.write(BoxName.addWork, 'addWork');
}
if (box.read(BoxName.addHome) == null) {
box.write(BoxName.addHome, 'addHome');
}
}
getPassengerRate() async {
var res = await CRUD().get(
link: AppLink.getPassengerRate,
payload: {'passenger_id': box.read(BoxName.passengerID)});
if (res != 'failure') {
var json = jsonDecode(res);
var message = json['data'] ?? json['message'];
if (message['rating'] == null) {
passengerRate = 5.0; // Default rating
} else {
var rating = message['rating'];
if (rating is String) {
passengerRate = double.tryParse(rating) ?? 5.0;
} else if (rating is num) {
passengerRate = rating.toDouble();
} else {
passengerRate = 5.0;
}
}
} else {
passengerRate = 5.0;
}
}
firstTimeRunToGetCoupon() async {
if (box.read(BoxName.isFirstTime).toString() == '0' &&
box.read(BoxName.isInstall).toString() == '1' &&
box.read(BoxName.isGiftToken).toString() == '0') {
var promo, discount, validity;
var resPromo = await CRUD().get(link: AppLink.getPromoFirst, payload: {
"passengerID": box.read(BoxName.passengerID).toString(),
});
if (resPromo != 'failure') {
var d1 = jsonDecode(resPromo);
promo = d1['message']['promo_code'];
discount = d1['message']['amount'];
validity = d1['message']['validity_end_date'];
}
box.write(BoxName.isFirstTime, '1');
Get.dialog(
AlertDialog(
contentPadding: EdgeInsets.zero,
content: SizedBox(
width: 300,
child: PromoBanner(
promoCode: promo ?? '',
discountPercentage: discount ?? '',
validity: validity ?? '',
),
),
),
);
}
}
}

View File

@@ -21,6 +21,7 @@ import '../../functions/crud.dart';
import '../../functions/tts.dart';
import 'ride_lifecycle_controller.dart';
import 'location_search_controller.dart';
import '../../functions/country_logic.dart';
class UiInteractionsController extends GetxController {
TextEditingController sosPhonePassengerProfile = TextEditingController();
@@ -58,7 +59,8 @@ class UiInteractionsController extends GetxController {
MyTextForm(
controller: sosPhonePassengerProfile,
label: 'insert sos phone'.tr,
hint: 'e.g. 0912345678 (Default +963)'.tr,
hint: CountryLogic.getPhoneHint(
box.read(BoxName.countryCode) ?? 'Syria').tr,
type: TextInputType.phone,
),
const SizedBox(height: 10),
@@ -77,7 +79,7 @@ class UiInteractionsController extends GetxController {
if (sosFormKey.currentState!.validate()) {
Get.back();
var numberPhone =
formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
CountryLogic.formatCurrentCountryPhone(sosPhonePassengerProfile.text);
await CRUD().post(
link: AppLink.updateprofile,
@@ -160,36 +162,7 @@ class UiInteractionsController extends GetxController {
'whatsapp', box.read(BoxName.sosPhonePassenger), message);
}
String formatSyrianPhone(String phone) {
phone = phone.replaceAll(' ', '').replaceAll('+', '');
if (phone.startsWith('00963')) {
phone = phone.replaceFirst('00963', '963');
}
if (phone.startsWith('0963')) {
phone = phone.replaceFirst('0963', '963');
}
if (phone.startsWith('963')) {
return phone;
}
if (phone.startsWith('09')) {
return '963' + phone.substring(1);
}
if (phone.startsWith('9') && phone.length == 9) {
return '963' + phone;
}
return phone;
}
String formatSyrianPhoneNumber(String phoneNumber) {
String trimmedPhone = phoneNumber.trim();
if (trimmedPhone.startsWith('09')) {
return '963${trimmedPhone.substring(1)}';
}
if (trimmedPhone.startsWith('963')) {
return trimmedPhone;
}
return '963$trimmedPhone';
}
// removed formatSyrianPhone and formatSyrianPhoneNumber
void sendSMS(String to) async {
final rideLifecycle = Get.find<RideLifecycleController>();
@@ -205,7 +178,7 @@ class UiInteractionsController extends GetxController {
void sendWhatsapp(String to) async {
final rideLifecycle = Get.find<RideLifecycleController>();
final locSearch = Get.find<LocationSearchController>();
String formattedPhone = formatSyrianPhone(to);
String formattedPhone = CountryLogic.formatCurrentCountryPhone(to);
String message =
'${'${'Hi! This is'.tr} ${(box.read(BoxName.name).toString().split(' ')[0]).toString()}.\n${' I am using'.tr}'} ${AppInformation.appName}${' to ride with'.tr} ${rideLifecycle.passengerName}${' as the driver.'.tr} ${rideLifecycle.passengerName} \n${'is driving a '.tr}${rideLifecycle.model}\n${' with license plate '.tr}${rideLifecycle.licensePlate}.\n${' I am currently located at '.tr} https://www.google.com/maps/place/${locSearch.passengerLocation.latitude},${locSearch.passengerLocation.longitude}.\n${' If you need to reach me, please contact the driver directly at'.tr}\n\n ${rideLifecycle.driverPhone}.';
@@ -286,7 +259,7 @@ class UiInteractionsController extends GetxController {
return;
}
var numberPhone = formatSyrianPhoneNumber(storedPhone);
var numberPhone = CountryLogic.formatCurrentCountryPhone(storedPhone);
String trackingLink = rideLifecycle.generateTrackingLink(
rideLifecycle.rideId, rideLifecycle.driverId);
@@ -326,7 +299,7 @@ Thank you for using Siro!
Future getTokenForParent() async {
_ensureSosNumber(() async {
String storedPhone = box.read(BoxName.sosPhonePassenger)!;
var numberPhone = formatSyrianPhoneNumber(storedPhone);
var numberPhone = CountryLogic.formatCurrentCountryPhone(storedPhone);
Log.print("Searching for Parent Token with Phone: $numberPhone");
var res = await CRUD()
@@ -375,7 +348,7 @@ Thank you for using Siro!
Get.back();
var rawPhone = box.read(BoxName.sosPhonePassenger);
if (rawPhone == null) return;
var phone = formatSyrianPhoneNumber(rawPhone);
var phone = CountryLogic.formatCurrentCountryPhone(rawPhone);
var message = '''Dear Friend,

View File

@@ -1,4 +1,5 @@
// import 'dart:async';
// import 'package:siro_rider/constant/currency.dart';
import 'dart:async';
// import 'package:siro_rider/services/offline_map_service.dart';
// import 'package:siro_rider/services/emergency_signal_service.dart';
// import 'package:siro_rider/views/widgets/mycircular.dart';
@@ -6559,8 +6560,8 @@
// if (!promoFormKey.currentState!.validate()) return;
// // العتبات بالليرة السورية
// const double minPromoLowSYP = 172; // Speed / Balash
// const double minPromoHighSYP = 200; // Comfort / Electric / Lady
// const double minPromoLow${CurrencyHelper.currency} = 172; // Speed / Balash
// const double minPromoHigh${CurrencyHelper.currency} = 200; // Comfort / Electric / Lady
// try {
// final value = await CRUD().get(
@@ -6723,10 +6724,10 @@
// // if (data.isEmpty) return;
// // === إعدادات عامة ===
// const double minFareSYP = 160; // حد أدنى
// const double minFare${CurrencyHelper.currency} = 160; // حد أدنى
// const double minBillableKm = 0.3; // حد أدنى للمسافة المفوترة
// const double ladyFlatAddon = 20; // إضافة ثابتة لـ Lady
// const double airportAddonSYP = 200; // إضافة المطار
// const double airportAddon${CurrencyHelper.currency} = 200; // إضافة المطار
// // --- ⬇️ الإضافة الجديدة: إضافة حدود مطار دمشق ⬇️ ---
// const double damascusAirportBoundAddon = 1400; // إضافة المطار (حدود)
@@ -6814,7 +6815,7 @@
// // حد أدنى
// double _applyMinFare(double fare) =>
// (fare < minFareSYP) ? minFareSYP : fare;
// (fare < minFareSYP) ? minFare${CurrencyHelper.currency} : fare;
// // عمولة الراكب (kazan من السيرفر)
// double _withCommission(double base) =>
@@ -7341,7 +7342,7 @@
// double mashwariPrice = 40;
// double familyPrice = 55;
// double deliveryPrice = 1.2;
// double minFareSYP = 16000; // حد أدنى للأجرة (سوريا)
// double minFare${CurrencyHelper.currency} = 16000; // حد أدنى للأجرة (سوريا)
// double minBillableKm = 1.0; // حد أدنى للمسافة المفوترة
// double commissionPct = 15; // عمولة التطبيق % (راكب)

View File

@@ -302,18 +302,30 @@ ${'Download the Siro app now and enjoy your ride!'.tr}
'${'Claim your 20 LE gift for inviting'.tr} $passengerName!',
() async {
Get.back(); // Close dialog first
await Get.find<PaymentController>().addPassengersWallet('20');
await CRUD().post(
link: AppLink.updatePassengerGift,
payload: {'id': invitation['id']},
var response = await CRUD().post(
link: AppLink.claimInviteReward,
payload: {
'invite_id': invitation['id'].toString(),
'passenger_id': box.read(BoxName.passengerID).toString(),
'country_code': box.read(BoxName.countryCode).toString(),
},
);
NotificationCaptainController().addNotificationCaptain(
invitation['passengerInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have earned 20".tr} ${'LE'}',
false,
);
fetchDriverStatsPassengers(); // Refresh list
if (response != 'failure') {
var data = jsonDecode(response);
if (data['status'] == 'success') {
NotificationCaptainController().addNotificationCaptain(
invitation['passengerInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have earned 20".tr} ${'LE'}',
false,
);
fetchDriverStatsPassengers(); // Refresh list
} else {
Get.snackbar('Error'.tr, data['message'] ?? 'Claim failed'.tr, backgroundColor: AppColor.redColor);
}
}
},
);
} else {

View File

@@ -286,6 +286,8 @@ class MyTranslation extends Translations {
"you must insert token code": "لازم تدخل الكود",
"Syria": "سوريا",
"SYP": "ل.س",
"EGP": "ج.م",
"JOD": "د.أ",
"Order": "طلب",
"OrderVIP": "طلب VIP",
"Cancel Trip": "إلغاء المشوار",
@@ -1700,6 +1702,8 @@ class MyTranslation extends Translations {
"ar-main": {
"Syria": "‏سوريا",
"SYP": "ل.س",
"EGP": "ج.م",
"JOD": "د.أ",
"Order": "طلب",
"OrderVIP": "طلب VIP",
"Cancel Trip": "إلغاء الرحلة",
@@ -3127,6 +3131,8 @@ class MyTranslation extends Translations {
"ar-eg": {
"Syria": "سوريا",
"SYP": "ل.س",
"EGP": "ج.م",
"JOD": "د.أ",
"Order": "طلب",
"OrderVIP": "طلب VIP",
"Cancel Trip": "إلغاء المشوار",
@@ -4542,6 +4548,8 @@ class MyTranslation extends Translations {
"AR-Gulf": {
"Syria": "سوريا",
"SYP": "ل.س",
"EGP": "ج.م",
"JOD": "د.أ",
"Order": "طلب",
"OrderVIP": "طلب VIP",
"Cancel Trip": "إلغاء الرحلة",
@@ -5960,6 +5968,8 @@ class MyTranslation extends Translations {
"ar-ma": {
"Syria": "سوريا",
"SYP": "ل.س",
"EGP": "ج.م",
"JOD": "د.أ",
"Order": "طلب",
"OrderVIP": "طلب VIP",
"Cancel Trip": "إلغي الرحلة",
@@ -7571,6 +7581,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "Suriye",
"SYP": "SYP",
"EGP": "EGP",
"JOD": "JOD",
"Order": "Sipariş",
"OrderVIP": "VIP Sipariş",
"Cancel Trip": "Yolculuğu İptal Et",
@@ -9099,6 +9111,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "Syrie",
"SYP": "SYP",
"EGP": "EGP",
"JOD": "JOD",
"Order": "Commande",
"OrderVIP": "Commande VIP",
"Cancel Trip": "Annuler le trajet",
@@ -10669,6 +10683,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "Syrien",
"SYP": "SYP",
"EGP": "EGP",
"JOD": "JOD",
"Order": "Bestellung",
"OrderVIP": "VIP-Bestellung",
"Cancel Trip": "Fahrt stornieren",
@@ -12418,6 +12434,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "Siria",
"SYP": "SYP",
"EGP": "EGP",
"JOD": "JOD",
"Order": "Pedido",
"OrderVIP": "Pedido VIP",
"Cancel Trip": "Cancelar viaje",
@@ -14160,6 +14178,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "سوریه",
"SYP": "لیره سوریه",
"EGP": "EGP",
"JOD": "JOD",
"Order": "درخواست",
"OrderVIP": "درخواست VIP",
"Cancel Trip": "لغو سفر",
@@ -15666,6 +15686,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "Συρία",
"SYP": "SYP",
"EGP": "EGP",
"JOD": "JOD",
"Order": "Αίτημα",
"OrderVIP": "VIP Αίτημα",
"Cancel Trip": "Ακύρωση Διαδρομής",
@@ -17159,6 +17181,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "شام",
"SYP": "شامی پاؤن",
"EGP": "EGP",
"JOD": "JOD",
"Order": "آرڈر",
"OrderVIP": "VIP آرڈر",
"Cancel Trip": "سفر منسوخ کریں",
@@ -18721,6 +18745,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "भारत",
"SYP": "",
"EGP": "EGP",
"JOD": "JOD",
"Order": "ऑर्डर",
"OrderVIP": "VIP ऑर्डर",
"Cancel Trip": "ट्रिप रद्द करें",
@@ -20281,6 +20307,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "Сирия",
"SYP": "SYP",
"EGP": "EGP",
"JOD": "JOD",
"Order": "Заказ",
"OrderVIP": "VIP Заказ",
"Cancel Trip": "Отменить поездку",
@@ -21745,6 +21773,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "Siria",
"SYP": "SYP",
"EGP": "EGP",
"JOD": "JOD",
"Order": "Ordine",
"OrderVIP": "Ordine VIP",
"Cancel Trip": "Annulla corsa",
@@ -23244,6 +23274,8 @@ class MyTranslation extends Translations {
"you must insert token code": "you must insert token code",
"Syria": "叙利亚",
"SYP": "叙利亚镑",
"EGP": "EGP",
"JOD": "JOD",
"Order": "طلب",
"OrderVIP": "طلب VIP",
"Cancel Trip": "إلغاء المشوار",

View File

@@ -1,29 +1,21 @@
import 'dart:convert';
import 'package:siro_rider/constant/api_key.dart';
import 'package:siro_rider/constant/style.dart';
import 'package:siro_rider/controller/firebase/firbase_messge.dart';
import 'package:siro_rider/controller/payment/paymob/paymob_response.dart';
import 'package:siro_rider/views/home/map_page_passenger.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
import 'package:siro_rider/controller/home/map/ride_state.dart';
import 'package:siro_rider/controller/home/map/ride_state.dart';
import 'package:webview_flutter/webview_flutter.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../../print.dart';
import '../firebase/notification_service.dart';
import '../functions/crud.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/toast.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';
class PaymentController extends GetxController {
bool isLoading = false;
@@ -34,7 +26,8 @@ class PaymentController extends GetxController {
final formKey = GlobalKey<FormState>();
final promo = TextEditingController();
final walletphoneController = TextEditingController();
double totalPassenger = Get.find<RideLifecycleController>().totalPassenger;
double totalPassenger = double.parse(
Get.find<RideLifecycleController>().totalPassenger.toString());
int? selectedAmount = 0;
List<dynamic> totalPassengerWalletDetails = [];
String passengerTotalWalletAmount = '';
@@ -67,6 +60,20 @@ class PaymentController extends GetxController {
}
String paymentToken = '';
Future<void> addPassengersWallet(String amount) async {
try {
await CRUD().postWallet(link: AppLink.addPassengersWallet, payload: {
'passenger_id': box.read(BoxName.passengerID).toString(),
'amount': amount,
});
await getPassengerWallet();
} catch (e) {
Log.print(e.toString());
}
}
Future<String> generateTokenPassenger(String amount) async {
var res =
await CRUD().post(link: AppLink.addPaymentTokenPassenger, payload: {
@@ -86,27 +93,8 @@ class PaymentController extends GetxController {
return d['message'];
}
Future addSeferWallet(String paymentMethod, point) async {
var seferToken = await generateTokenPassenger(point);
await CRUD().postWallet(link: AppLink.addSeferWallet, payload: {
'amount': point.toString(),
'paymentMethod': paymentMethod,
'passengerId': box.read(BoxName.passengerID).toString(),
'token': seferToken,
'driverId': 'passenger',
});
}
Future addPassengersWallet(String point) async {
var token = await generateTokenPassenger(point);
await CRUD().postWallet(link: AppLink.addPassengersWallet, payload: {
'passenger_id': box.read(BoxName.passengerID).toString(),
'balance': point,
'token': token,
});
}
payToDriverForCancelAfterAppliedAndHeNearYou(String rideId) async {
Future<void> payToDriverForCancelAfterAppliedAndHeNearYou(
String rideId) async {
{
double costOfWaiting5Minute = box.read(BoxName.countryCode) == 'Egypt'
? (4 * .08) + (5 * 1)
@@ -135,14 +123,6 @@ class PaymentController extends GetxController {
});
if (res != 'failure') {
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
// 'Cancel',
// 'Trip Cancelled. The cost of the trip will be added to your wallet.'
// .tr,
// Get.find<RideLifecycleController>().driverToken,
// [],
// 'cancel',
// );
await NotificationService.sendNotification(
category: 'Cancel',
target: Get.find<RideLifecycleController>().driverToken.toString(),
@@ -157,7 +137,7 @@ class PaymentController extends GetxController {
}
var paymentTokenWaitPassenger1 =
await generateTokenPassenger((costOfWaiting5Minute * -1).toString());
await CRUD().post(link: AppLink.addPassengersWallet, payload: {
await CRUD().postWallet(link: AppLink.addPassengersWallet, payload: {
'passenger_id': box.read(BoxName.passengerID).toString(),
'balance': (costOfWaiting5Minute * -1).toString(),
'token': paymentTokenWaitPassenger1,
@@ -166,27 +146,6 @@ class PaymentController extends GetxController {
}
}
addPassengerWallet() async {
isLoading = true;
update();
await addSeferWallet('visa-in', selectedAmount.toString());
await addPassengersWallet(selectedAmount == 100
? '100'
: selectedAmount == 200
? '215'
: selectedAmount == 400
? '450'
: selectedAmount == 1000
? '1140'
: '0');
// getPassengerWallet();
isLoading = false;
update();
}
void onChangedPaymentMethodWallet(bool? value) {
if (box.read(BoxName.passengerWalletTotal) == null ||
double.parse(box.read(BoxName.passengerWalletTotal).toString()) <
@@ -215,28 +174,6 @@ class PaymentController extends GetxController {
}
}
void applyPromoCodeToPassenger() async {
//TAWJIHI
CRUD().get(link: AppLink.getPassengersPromo, payload: {
'promo_code': promo.text,
}).then((value) {
var decod = jsonDecode(value);
if (decod["status"] == "success") {
var firstElement = decod["message"][0];
totalPassenger = totalPassenger -
(totalPassenger * int.parse(firstElement['amount']));
Get.find<RideLifecycleController>().promoTaken = true;
update();
}
});
}
// 'https://accept.paymob.com/unifiedcheckout/?publicKey=egy_pk_live_mbjDC9Ni6FSHKmsz8sOHiVk2xd7oWRve&clientSecret=egy_sk_live_c0904e9cf04506ae64f818d4e075b4a957e3713fdf7a22cb7da30a29e72442b5'
// أضف هذا الرابط إلى ملف AppLink الخاص بك
// هذه هي الدالة الجديدة التي ستستخدمها لبدء الدفع
Future<void> payWithEcash(BuildContext context, String amount) async {
try {
// 1. يمكنك استخدام نفس طريقة التحقق بالبصمة إذا أردت
@@ -293,225 +230,6 @@ class PaymentController extends GetxController {
}
}
// Future<void> payWithEcashDriver(BuildContext context, String amount) async {
// try {
// // يمكنك استخدام نفس طريقة التحقق بالبصمة إذا أردت
// bool isAvailable = await LocalAuthentication().isDeviceSupported();
// if (isAvailable) {
// bool didAuthenticate = await LocalAuthentication().authenticate(
// localizedReason: 'Use Touch ID or Face ID to confirm payment'.tr,
// );
// if (didAuthenticate) {
// // استدعاء الـ Endpoint الجديد على السيرفر الخاص بك
// var res = await CRUD().postWallet(
// link: AppLink.payWithEcashPassenger,
// // link:
// // 'https://wl.tripz-egypt.com/v1/main/ride/ecash/driver/payWithEcash.php',
// payload: {
// // أرسل البيانات التي يحتاجها السيرفر
// "amount": amount,
// // "driverId": box.read(BoxName.driverID), // تأكد من وجود هذا المتغير
// "passengerId":
// box.read(BoxName.passengerID), // تأكد من وجود هذا المتغير
// },
// );
// // التأكد من أن السيرفر أعاد رابط الدفع بنجاح
// if (res != null &&
// res['status'] == 'success' &&
// res['message'] != null) {
// final String paymentUrl = res['message'];
// // الانتقال إلى شاشة الدفع الجديدة الخاصة بـ ecash للسائق
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) =>
// EcashDriverPaymentScreen(paymentUrl: paymentUrl),
// ),
// );
// } else {
// // عرض رسالة خطأ في حال فشل السيرفر في إنشاء الرابط
// Get.defaultDialog(
// title: 'Error'.tr,
// content: Text(
// 'Failed to initiate payment. Please try again.'.tr,
// style: AppStyle.title,
// ),
// );
// }
// }
// }
// } catch (e) {
// Get.defaultDialog(
// title: 'Error'.tr,
// content: Text(
// 'An error occurred during the payment process.'.tr,
// style: AppStyle.title,
// ),
// );
// }
// }
/// شاشة جديدة ومبسطة خاصة بدفع السائقين عبر ecash
// Future<void> payWithMTNWallet(
// BuildContext context, String amount, String currency) async {
// // خزن سياق علوي آمن من البداية
// final BuildContext safeContext =
// Get.overlayContext ?? Get.context ?? context;
// // سبينر تحميل
// if (!(Get.isDialogOpen ?? false)) {
// Get.dialog(const Center(child: CircularProgressIndicator()),
// barrierDismissible: false);
// }
// try {
// final phone = box.read(BoxName.phoneWallet) as String;
// final passengerID = box.read(BoxName.passengerID).toString();
// final formattedAmount = double.parse(amount).toStringAsFixed(0);
// Log.print("🚀 بدء عملية دفع MTN");
// Log.print(
// "📦 Payload: passengerID: $passengerID, amount: $formattedAmount, phone: $phone");
// // التحقق بالبصمة (اختياري) + حماية من الـ await
// final localAuth = LocalAuthentication();
// final isAuthSupported = await localAuth.isDeviceSupported();
// if (isAuthSupported) {
// final didAuth = await localAuth.authenticate(
// localizedReason: 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
// );
// if (!didAuth) {
// if (Get.isDialogOpen == true) Get.back();
// Log.print("❌ المستخدم لم يؤكد بالبصمة/الوجه");
// return;
// }
// }
// // 1) بدء الدفع
// final responseData = await CRUD().postWalletMtn(
// link: AppLink.payWithMTNStart,
// payload: {
// "amount": formattedAmount,
// "passengerId": passengerID,
// "phone": phone,
// "lang": box.read(BoxName.lang) ?? 'ar',
// },
// );
// // Log.print("✅ استجابة الخادم (mtn_start_payment.php):");
// // Log.print(responseData);
// Log.print('responseData: ${responseData}');
// // فحص الاستجابة بقوة
// late final Map<String, dynamic> startRes;
// if (responseData is Map<String, dynamic>) {
// startRes = responseData;
// } else if (responseData is String) {
// startRes = json.decode(responseData) as Map<String, dynamic>;
// } else {
// throw Exception("تم استلام نوع بيانات غير متوقع من الخادم.");
// }
// if (startRes['status'] != 'success') {
// final errorMsg = startRes['message']['Error']?.toString().tr ??
// "فشل بدء عملية الدفع. حاول مرة أخرى.";
// throw Exception(errorMsg);
// }
// final messageData = startRes["message"] as Map<String, dynamic>;
// final invoiceNumber = messageData["invoiceNumber"].toString();
// final operationNumber = messageData["operationNumber"].toString();
// final guid = messageData["guid"].toString();
// // Log.print(
// // "📄 invoiceNumber: $invoiceNumber, 🔢 operationNumber: $operationNumber, 🧭 guid: $guid");
// // أغلق السبينر قبل إظهار حوار OTP
// if (Get.isDialogOpen == true) Get.back();
// // 2) إدخال OTP بـ Get.defaultDialog (لا يستخدم context قابل للتلف)
// String otpInput = "";
// await Get.defaultDialog(
// title: "أدخل كود التحقق",
// barrierDismissible: false,
// content: TextField(
// keyboardType: TextInputType.number,
// decoration: const InputDecoration(hintText: "كود OTP"),
// onChanged: (v) => otpInput = v,
// ),
// confirm: TextButton(
// onPressed: () {
// if (otpInput.isEmpty ||
// otpInput.length < 4 ||
// otpInput.length > 8) {
// Get.snackbar("تنبيه", "أدخل كود OTP صحيح (48 أرقام)");
// return;
// }
// Get.back(result: otpInput);
// },
// child: const Text("تأكيد"),
// ),
// cancel: TextButton(
// onPressed: () => Get.back(result: null),
// child: const Text("إلغاء"),
// ),
// ).then((res) => otpInput = (res ?? "") as String);
// if (otpInput.isEmpty) {
// Log.print("❌ لم يتم إدخال OTP");
// return;
// }
// Log.print("🔐 تم إدخال OTP: $otpInput");
// // سبينر أثناء التأكيد
// Get.dialog(const Center(child: CircularProgressIndicator()),
// barrierDismissible: false);
// // 3) تأكيد الدفع
// final confirmRes = await CRUD().postWalletMtn(
// link: AppLink.payWithMTNConfirm,
// payload: {
// "invoiceNumber": invoiceNumber,
// "operationNumber": operationNumber,
// "guid": guid,
// "otp": otpInput,
// "phone": phone,
// "lang": box.read(BoxName.lang) ?? 'ar',
// },
// );
// if (Get.isDialogOpen == true) Get.back();
// // Log.print("✅ استجابة mtn_confirm.php:");
// // Log.print('confirmRes: ${confirmRes}');
// final ok = (confirmRes is Map && confirmRes['status'] == 'success');
// if (ok) {
// Get.defaultDialog(
// title: "✅ نجاح",
// content: const Text("تمت عملية الدفع وإضافة الرصيد إلى محفظتك."),
// );
// await getPassengerWallet();
// } else {
// final errorMsg = (confirmRes['message']['message']?.toString()) ??
// "فشل في تأكيد الدفع";
// Get.defaultDialog(title: "❌ فشل", content: Text(errorMsg.tr));
// }
// } catch (e, s) {
// Log.print("🔥 خطأ أثناء الدفع عبر MTN:");
// Log.print(e);
// Log.print(s);
// if (Get.isDialogOpen == true) Get.back();
// Get.defaultDialog(
// title: 'حدث خطأ',
// content: Text(e.toString().replaceFirst("Exception: ", "")),
// );
// }
// }
Future<void> payWithSyriaTelWallet(String amount, String currency) async {
// helper لفتح لودينغ بأمان
Future<void> _showLoading() async {
@@ -661,6 +379,104 @@ class PaymentController extends GetxController {
}
}
Future<void> payWithMTNWallet(BuildContext context, String amount, String currency) async {
try {
final phone = walletphoneController.text.trim();
if (phone.isEmpty) {
Get.defaultDialog(title: 'Error'.tr, content: Text('Please enter phone number'.tr));
return;
}
Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false);
var res = await CRUD().postWalletMtn(
link: AppLink.createMtnInvoice,
payload: {
"amount": amount,
"user_id": box.read(BoxName.passengerID).toString(),
"user_type": "passenger",
"mtn_phone": phone,
},
);
Get.back(); // close loading
late final Map<String, dynamic> resMap;
if (res is Map<String, dynamic>) {
resMap = res;
} else if (res is String) {
resMap = json.decode(res) as Map<String, dynamic>;
} else {
throw Exception("Unexpected response type");
}
if (resMap['status'] == 'success') {
Get.to(() => PaymentScreenMtn(
invoiceNumber: resMap['invoice_number'],
mtnNumber: resMap['mtn_payment_number'] ?? '---',
amount: double.parse(amount),
));
} else {
Get.defaultDialog(
title: 'Error'.tr,
content: Text(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()));
}
}
Future<void> payWithClickWallet(BuildContext context, String amount, String currency) async {
try {
final phone = walletphoneController.text.trim();
if (phone.isEmpty) {
Get.defaultDialog(title: 'Error'.tr, content: Text('Please enter phone number'.tr));
return;
}
Get.dialog(const Center(child: CircularProgressIndicator()), barrierDismissible: false);
var res = await CRUD().postWalletMtn(
link: AppLink.createCliqInvoice,
payload: {
"amount": amount,
"user_id": box.read(BoxName.passengerID).toString(),
"user_type": "passenger",
"click_phone": phone,
},
);
Get.back(); // close loading
late final Map<String, dynamic> resMap;
if (res is Map<String, dynamic>) {
resMap = res;
} else if (res is String) {
resMap = json.decode(res) as Map<String, dynamic>;
} else {
throw Exception("Unexpected response type");
}
if (resMap['status'] == 'success') {
Get.to(() => PaymentScreenCliq(
invoiceNumber: resMap['invoice_number'],
cliqAlias: resMap['cliq_alias'] ?? '---',
amount: double.parse(amount),
));
} else {
Get.defaultDialog(
title: 'Error'.tr,
content: Text(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()));
}
}
@override
void onInit() {
timestamp = now.millisecondsSinceEpoch;

View File

@@ -45,30 +45,16 @@ class RateController extends GetxController {
confirm: MyElevatedButton(title: 'Ok', onPressed: () => Get.back()));
} else if (Get.find<PaymentController>().isWalletChecked == true) {
double tip = 0;
tip = (Get.find<RideLifecycleController>().totalPassenger) *
tip = double.parse(Get.find<RideLifecycleController>().totalPassenger.toString()) *
(double.parse(box.read(BoxName.tipPercentage).toString()));
if (tip > 0) {
var res = await CRUD().post(link: AppLink.addTips, payload: {
'passengerID': box.read(BoxName.passengerID),
'passengerID': box.read(BoxName.passengerID).toString(),
'driverID': Get.find<RideLifecycleController>().driverId.toString(),
'rideID': Get.find<RideLifecycleController>().rideId.toString(),
'tipAmount': tip.toString(),
});
await Get.find<PaymentController>()
.addPassengersWallet(((-1) * tip).toString());
var token1 = await Get.find<PaymentController>().generateTokenDriver(
box.read(BoxName.countryCode) == 'Egypt'
? tip.toStringAsFixed(0)
: (tip * 100).toString());
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'driverID': Get.find<RideLifecycleController>().driverId.toString(),
'paymentID': '${Get.find<RideLifecycleController>().rideId}tip',
'amount': box.read(BoxName.countryCode) == 'Egypt'
? tip.toStringAsFixed(0)
: (tip * 100).toString(),
'paymentMethod': 'visa-tip',
'token': token1,
'country_code': box.read(BoxName.countryCode).toString(),
});
if (res != 'failure') {
await NotificationService.sendNotification(
@@ -76,7 +62,7 @@ class RateController extends GetxController {
target: Get.find<RideLifecycleController>().driverToken.toString(),
title: 'You Have Tips'.tr,
body:
'${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<RideLifecycleController>().totalPassenger)}',
'${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + double.parse(Get.find<RideLifecycleController>().totalPassenger.toString())}',
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [],