25-12-1/1

This commit is contained in:
Hamza-Ayed
2025-12-01 07:52:54 +03:00
parent b1b8efdd7d
commit 9b1008a0bf
40 changed files with 2471 additions and 2039 deletions

View File

@@ -326,15 +326,45 @@ Download the Intaleq app now and enjoy your ride!
return input; // Fallback for unrecognized formats
}
String normalizeSyrianPhone(String input) {
String phone = input.trim();
// احذف كل شيء غير أرقام
phone = phone.replaceAll(RegExp(r'[^0-9]'), '');
// إذا يبدأ بـ 0 → احذفها
if (phone.startsWith('0')) {
phone = phone.substring(1);
}
// إذا يبدأ بـ 963 مكررة → احذف التكرار
while (phone.startsWith('963963')) {
phone = phone.substring(3);
}
// إذا يبدأ بـ 963 ولكن داخله كمان 963 → خليه مرة واحدة فقط
if (phone.startsWith('963') && phone.length > 12) {
phone = phone.substring(phone.length - 9); // آخر 9 أرقام
}
// الآن إذا كان بلا 963 → أضفها
if (!phone.startsWith('963')) {
phone = '963' + phone;
}
return phone;
}
/// Sends an invitation to a potential new driver.
void sendInvite() async {
if (invitePhoneController.text.isEmpty) {
mySnackeBarError('Please enter a phone number'.tr);
return;
}
// Format Syrian phone number: remove leading 0 and add +963
String formattedPhoneNumber =
_formatSyrianPhoneNumber(invitePhoneController.text);
if (formattedPhoneNumber.length < 13) {
normalizeSyrianPhone(invitePhoneController.text);
if (formattedPhoneNumber.length != 12) {
mySnackeBarError('Please enter a correct phone'.tr);
return;
}
@@ -370,22 +400,32 @@ Download the Intaleq app now and enjoy your ride!
mySnackeBarError('Please enter a phone number'.tr);
return;
}
String formattedPhoneNumber =
_formatSyrianPhoneNumber(invitePhoneController.text);
if (formattedPhoneNumber.length < 13) {
// Format Syrian phone number: remove leading 0 and add +963
String formattedPhoneNumber = invitePhoneController.text.trim();
if (formattedPhoneNumber.startsWith('0')) {
formattedPhoneNumber = formattedPhoneNumber.substring(1);
}
formattedPhoneNumber = '+963$formattedPhoneNumber';
if (formattedPhoneNumber.length < 12) {
// +963 + 9 digits = 12+
mySnackeBarError('Please enter a correct phone'.tr);
return;
}
var response =
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
"driverId": box.read(BoxName.driverID),
"inviterPassengerPhone": formattedPhoneNumber,
});
var response = await CRUD().post(
link: AppLink.addInvitationPassenger,
payload: {
"driverId": box.read(BoxName.driverID),
"inviterPassengerPhone": formattedPhoneNumber,
},
);
if (response != 'failure') {
var d = (response);
var d = response;
mySnackbarSuccess('Invite sent successfully'.tr);
String message = '${'*Intaleq APP CODE*'.tr}\n\n'
'${"Use this code in registration".tr}\n\n'
'${"To get a gift for both".tr}\n\n'

View File

@@ -26,6 +26,7 @@ import '../../../views/auth/captin/otp_page.dart';
import '../../../views/auth/captin/otp_token_page.dart';
import '../../../views/auth/syria/pending_driver_page.dart';
import '../../firebase/firbase_messge.dart';
import '../../firebase/local_notification.dart';
import '../../firebase/notification_service.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
@@ -177,11 +178,11 @@ class LoginDriverController extends GetxController {
Uri.parse(AppLink.loginFirstTimeDriver),
body: payload,
);
// Log.print('response0: ${response0.body}');
// Log.print('request: ${response0.request}');
Log.print('response0: ${response0.body}');
Log.print('request: ${response0.request}');
if (response0.statusCode == 200) {
final decodedResponse1 = jsonDecode(response0.body);
// Log.print('decodedResponse1: ${decodedResponse1}');
Log.print('decodedResponse1: ${decodedResponse1}');
final jwt = decodedResponse1['jwt'];
box.write(BoxName.jwt, c(jwt));
@@ -253,6 +254,40 @@ class LoginDriverController extends GetxController {
.join('');
}
bool isInviteDriverFound = false;
Future updateInvitationCodeFromRegister() async {
var res = await CRUD().post(
link: AppLink.updateDriverInvitationDirectly,
payload: {
"inviterDriverPhone": box.read(BoxName.phoneDriver).toString(),
// "driverId": box.read(BoxName.driverID).toString(),
},
);
Log.print('invite: ${res}');
if (res['status'] != 'failure') {
isInviteDriverFound = true;
update();
// mySnackbarSuccess("Code approved".tr); // Localized success message
box.write(BoxName.isInstall, '1');
NotificationController().showNotification(
"Code approved".tr, "Code approved".tr, 'tone2', '');
NotificationService.sendNotification(
target: (res)['message'][0]['token'].toString(),
title: 'You have received a gift token!'.tr,
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
isTopic: false, // Important: this is a token
tone: 'tone2',
driverList: [], category: 'You have received a gift token!',
);
} else {
// mySnackeBarError(
// "You dont have invitation code".tr); // Localized error message
}
}
loginWithGoogleCredential(String driverID, email) async {
isloading = true;
update();
@@ -263,7 +298,7 @@ class LoginDriverController extends GetxController {
// 'email': email ?? 'yet',
'id': driverID,
});
// Log.print('res: ${res}');
Log.print('loginWithGoogleCredential: ${res}');
if (res == 'failure') {
await isPhoneVerified();
isloading = false; // <--- أضفت هذا أيضاً
@@ -314,6 +349,13 @@ class LoginDriverController extends GetxController {
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
// add invitations
if (box.read(BoxName.isInstall) == null ||
box.read(BoxName.isInstall).toString() == '0') {
updateInvitationCodeFromRegister();
}
// updateAppTester(AppInformation.appName);
if (d['status'].toString() != 'yet') {
var token = await CRUD().get(

View File

@@ -18,14 +18,77 @@ class PhoneAuthHelper {
static final String _sendOtpUrl = '${_baseUrl}sendWhatsAppDriver.php';
static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php';
static final String _registerUrl = '${_baseUrl}register_driver.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');
}
if (phone.startsWith('096309')) {
phone = phone.replaceFirst('096309', '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;
}
/// Sends an OTP to the provided phone number.
static Future<bool> sendOtp(String phoneNumber) async {
try {
final fixedPhone = formatSyrianPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post(
link: _sendOtpUrl,
payload: {'receiver': phoneNumber},
payload: {'receiver': fixedPhone},
);
Log.print('fixedPhone: ${fixedPhone}');
if (response != 'failure') {
final data = (response);
Log.print('data: ${data}');
@@ -49,10 +112,12 @@ class PhoneAuthHelper {
/// Verifies the OTP and logs the user in.
static Future<void> verifyOtp(String phoneNumber) async {
try {
final fixedPhone = formatSyrianPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {
'phone_number': phoneNumber,
'phone_number': fixedPhone,
},
);
@@ -80,7 +145,7 @@ class PhoneAuthHelper {
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
// mySnackbarSuccess('Phone verified. Please complete registration.');
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
Get.to(() => RegistrationView());
}
} else {
mySnackeBarError(data['message'] ?? 'Verification failed.');

View File

@@ -280,7 +280,7 @@ class RegisterCaptainController extends GetxController {
// box.read(BoxName.emailDriver).toString(),
// );
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
Get.to(() => RegistrationView());
// } else {
// Get.snackbar('title', 'message');
// }

View File

@@ -58,6 +58,7 @@ class RegistrationController extends GetxController {
final firstNameController = TextEditingController();
final lastNameController = TextEditingController();
final nationalIdController = TextEditingController();
final bithdateController = TextEditingController();
final phoneController = TextEditingController(); // You can pre-fill this
final driverLicenseExpiryController = TextEditingController();
DateTime? driverLicenseExpiryDate;
@@ -101,14 +102,15 @@ class RegistrationController extends GetxController {
isValid = driverInfoFormKey.currentState!.validate();
if (isValid) {
// Optional: Check if license is expired
if (driverLicenseExpiryDate != null &&
driverLicenseExpiryDate!.isBefore(DateTime.now())) {
Get.snackbar('Expired License', 'Your drivers license has expired.',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.red,
colorText: Colors.white);
return; // Stop progression
}
// if (driverLicenseExpiryDate != null &&
// driverLicenseExpiryDate!.isBefore(DateTime.now())) {
// Get.snackbar('Expired License', 'Your drivers license has expired.'.tr
// ,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// return; // Stop progression
// }
}
} else if (currentPage.value == 1) {
// Validate Step 2
@@ -495,6 +497,7 @@ class RegistrationController extends GetxController {
_addField(fields, 'last_name', lastNameController.text);
_addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
_addField(fields, 'national_number', nationalIdController.text);
_addField(fields, 'birthdate', bithdateController.text);
_addField(fields, 'expiry_date', driverLicenseExpiryController.text);
_addField(
fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
import 'package:sefer_driver/views/home/Captin/orderCaptin/order_speed_request.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
@@ -117,7 +118,7 @@ class FirebaseMessagesController extends GetxController {
driverToken = myList[14].toString();
Get.put(HomeCaptainController()).changeRideId();
update();
Get.to(() => OrderRequestPage(), arguments: {
Get.to(() => OrderSpeedRequest(), arguments: {
'myListString': myListString,
'DriverList': myList,
'body': body

View File

@@ -114,7 +114,7 @@ class AI extends GetxController {
// 'tone2', // Type of notification
// );
NotificationService.sendNotification(
target: jsonDecode(res)['message'][0]['token'].toString(),
target: (res)['message'][0]['token'].toString(),
title: 'You have received a gift token!'.tr,
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
isTopic: false, // Important: this is a token

View File

@@ -8,11 +8,32 @@ void showInBrowser(String url) async {
}
Future<void> makePhoneCall(String phoneNumber) async {
// 1. تنظيف الرقم (إزالة المسافات والفواصل)
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
// 2. التحقق من طول الرقم لتحديد طريقة التنسيق
if (formattedNumber.length > 6) {
// --- التعديل المطلوب ---
if (formattedNumber.startsWith('09')) {
// إذا كان يبدأ بـ 09 (رقم موبايل سوري محلي)
// نحذف أول خانة (الصفر) ونضيف +963
formattedNumber = '+963${formattedNumber.substring(1)}';
} else if (!formattedNumber.startsWith('+')) {
// إذا لم يكن يبدأ بـ + (ولم يكن يبدأ بـ 09)، نضيف + في البداية
// هذا للحفاظ على منطقك القديم للأرقام الدولية الأخرى
formattedNumber = '+$formattedNumber';
}
}
// 3. التنفيذ (Launch)
final Uri launchUri = Uri(
scheme: 'tel',
path: phoneNumber,
path: formattedNumber,
);
await launchUrl(launchUri);
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri);
}
}
void launchCommunication(

View File

@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:sefer_driver/views/home/on_boarding_page.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
@@ -9,12 +7,10 @@ import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/onbording_page.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_textField.dart';
import '../../constant/style.dart';
import 'encrypt_decrypt.dart';
class LogOutController extends GetxController {
TextEditingController checkTxtController = TextEditingController();

View File

@@ -48,7 +48,7 @@ Future<String> getPackageInfo() async {
void showUpdateDialog(BuildContext context) {
final String storeUrl = Platform.isAndroid
? 'https://play.google.com/store/apps/details?id=com.intaleq_driver'
: 'https://apps.apple.com/ae/app/intaleq-driver/id6502189302';
: 'https://apps.apple.com/jo/app/intaleq-driver/id6482995159';
showGeneralDialog(
context: context,

View File

@@ -52,31 +52,58 @@ class DriverBehaviorController extends GetxController {
double totalSpeed = 0;
int hardBrakes = 0;
double totalDistance = 0;
// متغيرات للمقارنة مع النقطة السابقة
double? prevLat, prevLng;
DateTime? prevTime;
// ترتيب البيانات حسب الوقت لضمان دقة الحساب (اختياري لكن مفضل)
// data.sort((a, b) => a['created_at'].compareTo(b['created_at']));
for (var item in data) {
double speed = item['speed'] ?? 0;
double lat = item['lat'] ?? 0;
double lng = item['lng'] ?? 0;
double acc = item['acceleration'] ?? 0;
// 1. قراءة البيانات بالأسماء الصحيحة من الجدول
double lat = item['latitude'] ?? item['lat'] ?? 0.0;
double lng = item['longitude'] ?? item['lng'] ?? 0.0;
double acc = item['acceleration'] ?? 0.0;
if (speed > maxSpeed) maxSpeed = speed;
totalSpeed += speed;
// قراءة الوقت لحساب السرعة
DateTime currentTime =
DateTime.tryParse(item['created_at'].toString()) ?? DateTime.now();
// ✅ Hard brake threshold
double currentSpeed = 0;
// 2. حساب السرعة والمسافة إذا وجدت نقطة سابقة
if (prevLat != null && prevLng != null && prevTime != null) {
double distKm = _calculateDistance(prevLat, prevLng, lat, lng);
int timeDiffSeconds = currentTime.difference(prevTime).inSeconds;
if (timeDiffSeconds > 0) {
// السرعة (كم/س) = (المسافة بالكيلومتر * 3600) / الزمن بالثواني
currentSpeed = (distKm * 3600) / timeDiffSeconds;
}
totalDistance += distKm;
}
// تحديث القيم الإحصائية
if (currentSpeed > maxSpeed) maxSpeed = currentSpeed;
totalSpeed += currentSpeed;
// حساب الفرملة القوية (يعتمد على التسارع المحفوظ مسبقاً)
if (acc.abs() > 3.0) hardBrakes++;
// ✅ Distance between points
if (prevLat != null && prevLng != null) {
totalDistance += _calculateDistance(prevLat, prevLng, lat, lng);
}
// حفظ النقطة الحالية لتكون هي "السابقة" في الدورة التالية
prevLat = lat;
prevLng = lng;
prevTime = currentTime;
}
double avgSpeed = totalSpeed / data.length;
// تجنب القسمة على صفر
double avgSpeed = (data.length > 1) ? totalSpeed / (data.length - 1) : 0;
// حساب تقييم السلوك
double behaviorScore = 100 - (hardBrakes * 5) - ((maxSpeed > 100) ? 10 : 0);
behaviorScore = behaviorScore.clamp(0, 100);
behaviorScore = behaviorScore.clamp(0.0, 100.0);
return {
'max_speed': maxSpeed,

View File

@@ -35,7 +35,7 @@ class DurationController extends GetxController {
getStaticDriver() async {
isLoading = true;
update();
var res = await CRUD().getWallet(
var res = await CRUD().get(
link: AppLink.driverStatistic,
payload: {'driverID': box.read(BoxName.driverID)});
if (res == 'failure') {

View File

@@ -51,7 +51,7 @@ class HomeCaptainController extends GetxController {
String totalMoneyInSEFER = '0';
String totalDurationToday = '0';
Timer? timer;
late LatLng myLocation = const LatLng(32, 36);
late LatLng myLocation = const LatLng(33.5138, 36.2765);
String totalPoints = '0';
String countRefuse = '0';
bool mapType = false;
@@ -99,7 +99,7 @@ class HomeCaptainController extends GetxController {
isActive = !isActive;
if (isActive) {
if (double.parse(totalPoints) > -300) {
if (double.parse(totalPoints) > -30000) {
locationController.startLocationUpdates();
HapticFeedback.heavyImpact();
// locationBackController.startBackLocation();
@@ -188,22 +188,25 @@ class HomeCaptainController extends GetxController {
// late GoogleMapController mapHomeCaptainController;
GoogleMapController? mapHomeCaptainController;
// final locationController = Get.find<LocationController>();
// --- FIX 2: Smart Map Creation ---
void onMapCreated(GoogleMapController controller) {
mapHomeCaptainController = controller;
controller.getVisibleRegion();
// Animate camera to user location (optional)
controller.animateCamera(
CameraUpdate.newLatLng(Get.find<LocationController>().myLocation),
);
}
// قم بإنشائه مباشرة
// final MapController mapController = MapController();
// bool isMapReady = false;
// void onMapReady() {
// isMapReady = true;
// print("Map is ready to be moved!");
// }
// Check actual location before moving camera
var currentLoc = locationController.myLocation;
if (currentLoc.latitude != 0 && currentLoc.longitude != 0) {
controller.animateCamera(
CameraUpdate.newLatLng(currentLoc),
);
} else {
// Optional: Move to default city view instead of ocean
controller.animateCamera(
CameraUpdate.newLatLngZoom(myLocation, 10),
);
}
}
void savePeriod(Duration period) {
final periods = box.read<List<dynamic>>(BoxName.periods) ?? [];
@@ -234,7 +237,14 @@ class HomeCaptainController extends GetxController {
getlocation() async {
isLoading = true;
update();
// This ensures we try to get a fix, but map doesn't crash if it fails
await Get.find<LocationController>().getLocation();
var loc = Get.find<LocationController>().myLocation;
if (loc.latitude != 0) {
myLocation = loc;
}
isLoading = false;
update();
}
@@ -267,7 +277,7 @@ class HomeCaptainController extends GetxController {
void onInit() async {
// await locationBackController.requestLocationPermission();
Get.put(FirebaseMessagesController());
// addToken();
addToken();
await getlocation();
onButtonSelected();
getDriverRate();
@@ -283,61 +293,27 @@ class HomeCaptainController extends GetxController {
getRefusedOrderByCaptain();
box.write(BoxName.statusDriverLocation, 'off');
locationController.addListener(() {
// فقط إذا كان السائق "متصل" والخريطة جاهزة
// Only animate if active, map is ready, AND location is valid (not 0,0)
if (isActive && mapHomeCaptainController != null) {
mapHomeCaptainController!.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: locationController.myLocation, // الموقع الجديد
zoom: 17.5,
tilt: 50.0,
bearing: locationController.heading, // اتجاه السيارة
var loc = locationController.myLocation;
if (loc.latitude != 0 && loc.longitude != 0) {
mapHomeCaptainController!.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: loc,
zoom: 17.5,
tilt: 50.0,
bearing: locationController.heading,
),
),
),
);
);
}
}
});
// LocationController().getLocation();
super.onInit();
}
// void getRefusedOrderByCaptain() async {
// // Get today's date in YYYY-MM-DD format
// String today = DateTime.now().toString().substring(0, 10);
// String driverId = box.read(BoxName.driverID).toString();
// String customQuery = '''
// SELECT COUNT(*) AS count
// FROM ${TableName.driverOrdersRefuse}
// WHERE driver_id = '$driverId'
// AND DATE(created_at) = '$today'
// ''';
// try {
// List<Map<String, dynamic>> results =
// await sql.getCustomQuery(customQuery);
// countRefuse = results[0]['count'].toString();
// update();
// if (int.parse(countRefuse) > 3) {
// box.write(BoxName.statusDriverLocation, 'on');
// locationController.stopLocationUpdates();
// Get.defaultDialog(
// // backgroundColor: CupertinoColors.destructiveRed,
// barrierDismissible: false,
// title: 'You Are Stopped For this Day !'.tr,
// content: Text(
// 'You Refused 3 Rides this Day that is the reason \nSee you Tomorrow!'
// .tr,
// style: AppStyle.title,
// ),
// confirm: MyElevatedButton(
// title: 'Ok , See you Tomorrow'.tr,
// onPressed: () => Get.back()));
// } else {
// box.write(BoxName.statusDriverLocation, 'off');
// }
// } catch (e) {}
// }
addToken() async {
String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
@@ -346,14 +322,8 @@ class HomeCaptainController extends GetxController {
'captain_id': (box.read(BoxName.driverID)).toString(),
'fingerPrint': (fingerPrint).toString()
};
Log.print('payload: ${payload}');
// Log.print('payload: ${payload}');
CRUD().post(link: AppLink.addTokensDriver, payload: payload);
await CRUD().post(
link: "${AppLink.paymentServer}/ride/firebase/addDriver.php",
payload: payload);
// MapDriverController().driverCallPassenger();
// box.write(BoxName.statusDriverLocation, 'off');
}
getPaymentToday() async {
@@ -468,6 +438,7 @@ class HomeCaptainController extends GetxController {
void dispose() {
activeTimer?.cancel();
stopTimer();
mapHomeCaptainController?.dispose(); // Dispose controller
super.dispose();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -188,7 +188,7 @@ class OrderRequestController extends GetxController {
if (remainingTime == 0 && _timerActive) {
if (applied == false) {
endTimer();
refuseOrder(orderID);
//refuseOrder(orderID);
}
}
}
@@ -211,29 +211,6 @@ class OrderRequestController extends GetxController {
}
}
void refuseOrder(
orderID,
) async {
await CRUD().postFromDialogue(link: AppLink.addDriverOrder, payload: {
'driver_id': box.read(BoxName.driverID),
'order_id': (orderID),
'status': 'Refused'
});
await CRUD().post(link: AppLink.updateRides, payload: {
'id': (orderID),
'status': 'Refused',
'driver_id': box.read(BoxName.driverID),
});
// if (AppLink.endPoint != AppLink.seferCairoServer) {
// CRUD().post(link: '${AppLink.endPoint}/rides/update.php', payload: {
// 'id': (orderID),
// 'status': 'Refused',
// 'driver_id': box.read(BoxName.driverID),
// });
// }
update();
}
addRideToNotificationDriverString(
orderID,
String startLocation,

View File

@@ -6603,7 +6603,7 @@ const List<Country> countries = [
code: "SY",
dialCode: "963",
minLength: 9,
maxLength: 9,
maxLength: 10,
),
Country(
name: "Taiwan",

View File

@@ -59,7 +59,16 @@ class MyTranslation extends Translations {
'witout zero': 'بدون صفر',
'You Can Cancel the Trip and get Cost From ':
'يمكنك إلغاء الرحلة واسترداد التكلفة من ',
'Please enter a correct phone': 'يرجى إدخال رقم هاتف صحيح',
'Only Syrian phone numbers are allowed':
'يسمح بأرقام الهواتف السورية فقط',
'Go to passenger:': 'اذهب إلى الراكب:',
'Birth year must be 4 digits':
'يجب أن يكون سنة الميلاد مكونة من 4 أرقام',
'Required field': 'حقل مطلوب',
'You are not near': 'أنت لست بالقرب من',
'Please enter your phone number': 'يرجى إدخال رقم هاتفك',
'Enter a valid year': 'أدخل سنة صحيحة',
'Phone number seems too short': 'يبدو أن رقم الهاتف قصير جدًا',
'You have upload Criminal documents': 'لقد قمت بتحميل وثائق جنائية',
'Close': 'إغلاق',
@@ -658,7 +667,17 @@ Raih Gai: For same-day return trips longer than 50km.
"phone number of driver": "رقم هاتف السائق",
"Transfer budget": "نقل الميزانية",
"Comfort": "كمفورت",
"Speed": "سبيد",
"Speed": "سعر ثابت",
'Insert Emergency Number': 'أدخل رقم الطوارئ',
'Emergency Number': 'رقم الطوارئ',
'Save': 'حفظ',
'Stay': 'ابقى',
'Exit': 'خروج',
'Waiting': 'انتظار',
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month ':
',سيتم مسح بياناتك بعد أسبوعين\nولن تتمكن من العودة لاستخدام التطبيق بعد شهر واحد ',
"You are in an active ride. Leaving this screen might stop tracking. Are you sure you want to exit?":
"أنت في رحلة نشطة. قد يؤدي مغادرة هذه الشاشة إلى إيقاف التتبع. هل أنت متأكد أنك تريد الخروج؟",
"Lady": "ليدي",
"Permission denied": "تم رفض الإذن",
"Contact permission is required to pick a contact":

View File

@@ -1,74 +1,54 @@
// لإضافة هذه الحزمة، قم بتشغيل الأمر التالي في الـ Terminal
// flutter pub add intl
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import '../../../constant/box_name.dart';
import '../../../main.dart';
import '../../../print.dart';
/// خدمة لإدارة عمليات الدفع المتعلقة بنظام الدفع عبر الرسائل القصيرة
// ... (PaymentService class remains unchanged) ...
class PaymentService {
final String _baseUrl = "${AppLink.paymentServer}/sms_webhook";
final String _baseUrl = "${AppLink.paymentServer}/ride/shamcash";
Future<String?> createInvoice({
required String userPhone,
required double amount,
}) async {
final url = "$_baseUrl/create_invoice.php";
Future<String?> createInvoice({required double amount}) async {
final url = "$_baseUrl/create_invoice_shamcash.php";
try {
final response = await CRUD().postWallet(
link: url,
payload: {
'user_phone': userPhone.toString(),
'driverID': box.read(BoxName.driverID),
'amount': amount.toString(),
},
).timeout(const Duration(seconds: 15)); // إضافة مهلة للطلب
).timeout(const Duration(seconds: 15));
if (response != 'failure') {
final data = (response);
final data = response;
if (data['status'] == 'success' && data['invoice_number'] != null) {
debugPrint(
"تم إنشاء الفاتورة بنجاح. الرقم: ${data['invoice_number']}");
return data['invoice_number'].toString();
} else {
debugPrint("فشل في إنشاء الفاتورة من السيرفر: ${data['message']}");
return null;
}
} else {
debugPrint("خطأ في السيرفر عند إنشاء الفاتورة: ${response.statusCode}");
return null;
}
return null;
} catch (e) {
debugPrint("حدث استثناء عند إنشاء الفاتورة: $e");
return null;
}
}
/// دالة للتحقق من حالة فاتورة واحدة
Future<bool> checkInvoiceStatus(String invoiceNumber) async {
final url = "$_baseUrl/check_invoice_status.php";
final url = "$_baseUrl/check_status.php";
try {
final response = await CRUD().postWallet(link: url, payload: {
'invoice_number': invoiceNumber,
}).timeout(const Duration(seconds: 10)); // مهلة للشبكة
}).timeout(const Duration(seconds: 10));
if (response != 'failure') {
final data = (response);
final data = response;
return data['status'] == 'success' &&
data['invoice_status'] == 'completed';
}
return false;
} catch (e) {
debugPrint("خطأ أثناء التحقق من الفاتورة: $e");
return false;
}
}
@@ -86,14 +66,15 @@ class PaymentScreenSmsProvider extends StatefulWidget {
final double amount;
final String providerName;
final String providerLogo;
final String paymentPhoneNumber;
final String qrImagePath;
const PaymentScreenSmsProvider({
super.key,
required this.amount,
this.providerName = 'شام كاش',
this.providerLogo = 'assets/images/shamCash.png',
this.paymentPhoneNumber = '963942542053',
this.qrImagePath = 'assets/images/shamcashsend.png',
// removed paymentPhoneNumber
});
@override
@@ -106,7 +87,6 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
Timer? _pollingTimer;
PaymentStatus _status = PaymentStatus.creatingInvoice;
String? _invoiceNumber;
final String phone = box.read(BoxName.phoneWallet);
@override
void initState() {
@@ -116,17 +96,14 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
@override
void dispose() {
_pollingTimer?.cancel(); // مهم جداً: إلغاء المؤقت عند الخروج من الشاشة
_pollingTimer?.cancel();
super.dispose();
}
void _createAndPollInvoice() async {
setState(() => _status = PaymentStatus.creatingInvoice);
final invoiceNumber = await _paymentService.createInvoice(
userPhone: phone,
amount: widget.amount,
);
final invoiceNumber =
await _paymentService.createInvoice(amount: widget.amount);
if (invoiceNumber != null && mounted) {
setState(() {
@@ -140,7 +117,7 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
}
void _startPolling(String invoiceNumber) {
const timeoutDuration = Duration(minutes: 3);
const timeoutDuration = Duration(minutes: 5);
var elapsed = Duration.zero;
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
@@ -150,64 +127,57 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
if (mounted) setState(() => _status = PaymentStatus.paymentTimeout);
return;
}
debugPrint("Polling... Checking invoice status for: $invoiceNumber");
final isCompleted =
await _paymentService.checkInvoiceStatus(invoiceNumber);
if (isCompleted && mounted) {
timer.cancel();
setState(() => _status = PaymentStatus.paymentSuccess);
// TODO: تحديث رصيد المستخدم أو تنفيذ الإجراءات اللازمة
}
});
}
/// دالة جديدة لمعالجة محاولة الرجوع للخلف
void _onPopInvoked(bool didPop) async {
// إذا كان الرجوع قد تم بالفعل (مثلاً من خلال Navigator.pop)، لا تفعل شيئاً
if (didPop) return;
// إذا كان المستخدم ينتظر الدفع، أظهر له حوار التأكيد
Future<bool> _onPopInvoked() async {
if (_status == PaymentStatus.waitingForPayment) {
final shouldPop = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('هل أنت متأكد؟'),
content: const Text('إذا خرجت الآن، سيتم إلغاء عملية الدفع الحالية.'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('البقاء'),
return (await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('إلغاء العملية؟', textAlign: TextAlign.right),
content: const Text(
'الخروج الآن سيؤدي لإلغاء متابعة عملية الدفع.',
textAlign: TextAlign.right),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('البقاء')),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('خروج',
style: TextStyle(color: Colors.red))),
],
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('الخروج'),
),
],
),
);
// إذا وافق المستخدم على الخروج، قم بإغلاق الشاشة
if (shouldPop ?? false) {
Navigator.of(context).pop();
}
)) ??
false;
}
return true;
}
@override
Widget build(BuildContext context) {
// استخدام PopScope بدلاً من WillPopScope
return PopScope(
// منع الرجوع التلقائي فقط في حالة انتظار الدفع
canPop: _status != PaymentStatus.waitingForPayment,
// استدعاء دالة التحقق عند محاولة الرجوع
onPopInvoked: _onPopInvoked,
return WillPopScope(
onWillPop: _onPopInvoked,
child: Scaffold(
appBar: AppBar(title: Text("الدفع عبر ${widget.providerName}")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: _buildContentByStatus(),
backgroundColor: Colors.grey[50],
appBar: AppBar(
title: Text("دفع عبر ${widget.providerName}"),
centerTitle: true,
elevation: 0,
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(child: _buildContentByStatus()),
),
),
),
@@ -222,7 +192,7 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text("جاري إنشاء فاتورة الدفع...", style: TextStyle(fontSize: 16)),
Text("جاري إنشاء رقم البيان...", style: TextStyle(fontSize: 16)),
],
);
case PaymentStatus.waitingForPayment:
@@ -237,94 +207,195 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
Widget _buildWaitingForPaymentUI() {
final currencyFormat = NumberFormat.decimalPattern('ar_SY');
final invoiceText = _invoiceNumber ?? '------';
final invoiceText = _invoiceNumber ?? '---';
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(widget.providerLogo, width: 96),
const SizedBox(height: 16),
Text("تعليمات الدفع", style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Card(
elevation: 1.5,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
// 1. المبلغ (تصميم مميز)
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade800, Colors.blue.shade600]),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.25),
blurRadius: 15,
offset: const Offset(0, 8))
],
),
child: Column(
children: [
const Text("المبلغ المطلوب",
style: TextStyle(color: Colors.white70, fontSize: 14)),
const SizedBox(height: 8),
Text(
"${currencyFormat.format(widget.amount)} ل.س",
style: const TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.bold),
),
],
),
),
const SizedBox(height: 30),
// 2. التعليمات والنسخ (الجزء الأهم)
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
boxShadow: [
BoxShadow(
color: Colors.grey.shade100,
blurRadius: 10,
offset: const Offset(0, 4))
],
),
child: Column(
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange.shade50, shape: BoxShape.circle),
child: Icon(Icons.priority_high_rounded,
color: Colors.orange.shade800, size: 20),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
"انسخ الرقم أدناه وضعه في خانة (الملاحظات) عند الدفع.",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w600),
),
),
],
),
const SizedBox(height: 20),
InkWell(
onTap: () {
Clipboard.setData(ClipboardData(text: invoiceText));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text("تم نسخ رقم البيان ✅",
textAlign: TextAlign.center),
backgroundColor: Colors.green.shade600,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.all(20),
),
);
},
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(12),
border:
Border.all(color: Colors.blue.shade200, width: 1.5),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("رقم البيان (Invoice ID)",
style: TextStyle(
fontSize: 12, color: Colors.grey)),
Text(invoiceText,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
letterSpacing: 1.5)),
],
),
const Icon(Icons.copy_rounded,
color: Colors.blue, size: 24),
],
),
),
),
],
),
),
const SizedBox(height: 30),
// 3. الـ QR Code (قابل للاختيار/الضغط)
const Text("امسح الرمز للدفع",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87)),
const SizedBox(height: 15),
GestureDetector(
onTap: () {
// تأثير بصري بسيط عند الضغط (أو تكبير الصورة في Dialog)
showDialog(
context: context,
builder: (ctx) => Dialog(
backgroundColor: Colors.transparent,
child: InteractiveViewer(
child: Image.asset(widget.qrImagePath),
),
),
);
},
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.grey.shade200,
blurRadius: 10,
spreadRadius: 2)
],
),
child: Column(
children: [
_StepTile(number: 1, text: "افتح تطبيق محفظتك الإلكترونية."),
_StepTile(number: 2, text: "اختر خدمة تحويل الأموال."),
_StepTile(
number: 3,
text:
"أدخل المبلغ المطلوب: ${currencyFormat.format(widget.amount)} ل.س"),
_StepTile(number: 4, text: "حوّل إلى الرقم التالي:"),
// --- التعديل هنا ---
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
widget.paymentPhoneNumber,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.2),
),
trailing: OutlinedButton.icon(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: widget.paymentPhoneNumber));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("تم نسخ رقم الهاتف")));
}
},
icon: const Icon(Icons.copy, size: 18),
label: const Text("نسخ"),
),
Image.asset(
widget.qrImagePath,
width: 180,
height: 180,
fit: BoxFit.contain,
errorBuilder: (c, o, s) => const Icon(Icons.qr_code_2,
size: 100, color: Colors.grey),
),
// --- نهاية التعديل ---
const SizedBox(height: 8),
_StepTile(
number: 5,
text: "هام: انسخ رقم القسيمة والصقه في خانة \"البيان\"."),
ListTile(
contentPadding: EdgeInsets.zero,
title: Text(invoiceText,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.5)),
trailing: OutlinedButton.icon(
onPressed: _invoiceNumber == null
? null
: () async {
await Clipboard.setData(
ClipboardData(text: invoiceText));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("تم نسخ رقم القسيمة")));
}
},
icon: const Icon(Icons.copy, size: 18),
label: const Text("نسخ"),
),
),
const Text("اضغط للتكبير",
style: TextStyle(fontSize: 10, color: Colors.grey)),
],
),
),
),
const SizedBox(height: 40),
// مؤشر الانتظار
const LinearProgressIndicator(backgroundColor: Colors.white),
const SizedBox(height: 10),
const Text("ننتظر إشعار الدفع تلقائياً...",
style: TextStyle(color: Colors.grey, fontSize: 12)),
const SizedBox(height: 20),
const LinearProgressIndicator(minHeight: 2),
const SizedBox(height: 12),
Text("بانتظار تأكيد الدفع...",
style: TextStyle(color: Colors.grey.shade700)),
const SizedBox(height: 4),
const Text("هذه الشاشة ستتحدث تلقائيًا",
style: TextStyle(color: Colors.grey)),
],
),
);
@@ -334,14 +405,26 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.check_circle, color: Colors.green, size: 80),
const Icon(Icons.verified_rounded, color: Colors.green, size: 100),
const SizedBox(height: 20),
const Text("تم الدفع بنجاح!",
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("العودة"),
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
const Text("تم إضافة الرصيد والمكافأة إلى حسابك",
style: TextStyle(color: Colors.grey)),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
onPressed: () => Navigator.of(context).pop(),
child: const Text("متابعة", style: TextStyle(fontSize: 18)),
),
),
],
);
@@ -351,47 +434,41 @@ class _PaymentScreenSmsProviderState extends State<PaymentScreenSmsProvider> {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 80),
Icon(Icons.error_outline_rounded, color: Colors.red.shade400, size: 80),
const SizedBox(height: 20),
Text(
_status == PaymentStatus.paymentTimeout
? "انتهى الوقت المحدد للدفع"
: "حدث خطأ ما",
? "انتهى الوقت"
: "لم يتم التحقق",
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const Text("يرجى المحاولة مرة أخرى.", style: TextStyle(fontSize: 16)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _createAndPollInvoice,
child: const Text("المحاولة مرة أخرى"),
const SizedBox(height: 15),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Text(
"لم يصلنا إشعار الدفع. هل تأكدت من وضع (رقم البيان) في الملاحظات؟",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, height: 1.5)),
),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
onPressed: _createAndPollInvoice,
icon: const Icon(Icons.refresh),
label: const Text("حاول مرة أخرى"),
),
),
const SizedBox(height: 15),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text("إلغاء", style: TextStyle(color: Colors.grey)),
)
],
);
}
}
// ويدجت مساعد لعرض خطوات التعليمات بشكل أنيق
class _StepTile extends StatelessWidget {
final int number;
final String text;
const _StepTile({required this.number, required this.text});
@override
Widget build(BuildContext context) {
return ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
radius: 12,
backgroundColor: Theme.of(context).primaryColor,
child: Text("$number",
style: const TextStyle(
fontSize: 12,
color: Colors.white,
fontWeight: FontWeight.bold)),
),
title: Text(text),
);
}
}