25-12-1/1
This commit is contained in:
@@ -11,6 +11,7 @@ class BoxName {
|
||||
"rideArgumentsFromBackground";
|
||||
static const String FCM_PRIVATE_KEY = "FCM_PRIVATE_KEY";
|
||||
static const String hmac = "hmac";
|
||||
static const String ttsEnabled = "ttsEnabled";
|
||||
static const String walletType = "walletType";
|
||||
static const String fingerPrint = "fingerPrint";
|
||||
static const String updateInterval = "updateInterval";
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.');
|
||||
|
||||
@@ -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');
|
||||
// }
|
||||
|
||||
@@ -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 driver’s 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 driver’s 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'); // عدّل حسب منطقك
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -6603,7 +6603,7 @@ const List<Country> countries = [
|
||||
code: "SY",
|
||||
dialCode: "963",
|
||||
minLength: 9,
|
||||
maxLength: 9,
|
||||
maxLength: 10,
|
||||
),
|
||||
Country(
|
||||
name: "Taiwan",
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,9 +356,14 @@ class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
|
||||
validator: (phone) {
|
||||
if (phone == null || phone.number.isEmpty) {
|
||||
return 'Please enter your phone number'.tr;
|
||||
} // Check if the number is a Syrian number
|
||||
if (phone.countryISOCode != 'SY') {
|
||||
return 'Only Syrian phone numbers are allowed'.tr;
|
||||
}
|
||||
// Check if the national number part starts with '0'
|
||||
if (phone.number.startsWith('0')) {
|
||||
if (phone.completeNumber.startsWith('96309') ||
|
||||
phone.completeNumber.startsWith('+9630') ||
|
||||
phone.completeNumber.startsWith('09')) {
|
||||
return 'Please enter the number without the leading 0'.tr;
|
||||
}
|
||||
if (phone.completeNumber.length < 10) {
|
||||
|
||||
@@ -121,6 +121,29 @@ class RegistrationView extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: c.bithdateController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'سنة الميلاد'.tr,
|
||||
hintText: '1999'.tr,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) {
|
||||
return 'Required field'.tr;
|
||||
}
|
||||
if (v.length != 4) {
|
||||
return 'Birth year must be 4 digits'.tr;
|
||||
}
|
||||
// Optional: check if it’s a valid number
|
||||
if (int.tryParse(v) == null) {
|
||||
return 'Enter a valid year'.tr;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: c.driverLicenseExpiryController,
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -39,18 +39,18 @@ class SettingsCaptain extends StatelessWidget {
|
||||
subtitle: 'Change the app language'.tr,
|
||||
onTap: () => Get.to(() => const Language()),
|
||||
),
|
||||
_buildListTile(
|
||||
icon: Icons.flag_outlined,
|
||||
title: 'Change Country'.tr,
|
||||
subtitle: 'Get features for your country'.tr,
|
||||
onTap: () => Get.to(
|
||||
() => MyScafolld(
|
||||
title: 'Change Country'.tr,
|
||||
body: [CountryPickerFromSetting()],
|
||||
isleading: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
// _buildListTile(
|
||||
// icon: Icons.flag_outlined,
|
||||
// title: 'Change Country'.tr,
|
||||
// subtitle: 'Get features for your country'.tr,
|
||||
// onTap: () => Get.to(
|
||||
// () => MyScafolld(
|
||||
// title: 'Change Country'.tr,
|
||||
// body: [CountryPickerFromSetting()],
|
||||
// isleading: true,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -6,6 +7,7 @@ import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
|
||||
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../controller/functions/location_controller.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../Rate/rate_passenger.dart';
|
||||
import '../../widgets/my_textField.dart';
|
||||
import 'mapDriverWidgets/driver_end_ride_bar.dart';
|
||||
@@ -14,114 +16,169 @@ import 'mapDriverWidgets/google_map_app.dart';
|
||||
import 'mapDriverWidgets/passenger_info_window.dart';
|
||||
import 'mapDriverWidgets/sos_connect.dart';
|
||||
|
||||
// Changed: تم إعادة بناء الصفحة بالكامل لتكون أكثر تنظيمًا
|
||||
class PassengerLocationMapPage extends StatelessWidget {
|
||||
PassengerLocationMapPage({super.key});
|
||||
final LocationController locationController = Get.put(LocationController());
|
||||
final MapDriverController mapDriverController =
|
||||
Get.put(MapDriverController());
|
||||
|
||||
// Helper function to show exit confirmation dialog
|
||||
Future<bool> showExitDialog() async {
|
||||
bool? result = await Get.defaultDialog(
|
||||
title: "Warning".tr,
|
||||
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
|
||||
middleText:
|
||||
"You are in an active ride. Leaving this screen might stop tracking. Are you sure you want to exit?"
|
||||
.tr,
|
||||
middleTextStyle: AppStyle.title,
|
||||
barrierDismissible: false,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Stay'.tr,
|
||||
kolor: AppColor.greenColor,
|
||||
onPressed: () => Get.back(result: false), // Return false (Don't pop)
|
||||
),
|
||||
cancel: MyElevatedButton(
|
||||
title: 'Exit'.tr,
|
||||
kolor: AppColor.redColor,
|
||||
onPressed: () => Get.back(result: true), // Return true (Allow pop)
|
||||
),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// New: استخدام addPostFrameCallback لضمان أن تحميل البيانات يتم بعد بناء الواجهة
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
|
||||
mapDriverController.argumentLoading();
|
||||
mapDriverController.startTimerToShowPassengerInfoWindowFromDriver();
|
||||
} else {
|
||||
// في حال عدم وجود arguments، يتم التعامل مع هذا الخطأ
|
||||
Get.snackbar("Error", "No order data found.");
|
||||
Get.back();
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
// 1. الخريطة في الخلفية
|
||||
GoogleDriverMap(locationController: locationController),
|
||||
// ✅ Added PopScope to intercept back button
|
||||
return PopScope(
|
||||
canPop: false, // Prevents immediate popping
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (didPop) {
|
||||
return;
|
||||
}
|
||||
// Show dialog
|
||||
final shouldExit = await showExitDialog();
|
||||
if (shouldExit) {
|
||||
Get.back(); // Manually pop if confirmed
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
body: SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
// 1. Map
|
||||
GoogleDriverMap(locationController: locationController),
|
||||
|
||||
// 2. شريط تعليمات الطريق في الأعلى
|
||||
const InstructionsOfRoads(),
|
||||
// 2. Instructions
|
||||
const InstructionsOfRoads(),
|
||||
|
||||
// 4. نافذة معلومات الراكب في الأسفل (تظهر قبل بدء الرحلة)
|
||||
// 3. Passenger Info
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: PassengerInfoWindow(),
|
||||
),
|
||||
|
||||
PassengerInfoWindow(),
|
||||
// 3. زر إلغاء الرحلة في الأعلى يسارًا
|
||||
// 4. Cancel Widget
|
||||
CancelWidget(mapDriverController: mapDriverController),
|
||||
|
||||
CancelWidget(mapDriverController: mapDriverController),
|
||||
// Changed: تم تعديل تصميم زر الإلغاء ليكون أيقونة بسيطة في الأعلى
|
||||
// 5. شريط معلومات وإنهاء الرحلة (يظهر بعد بدء الرحلة)
|
||||
driverEndRideBar(),
|
||||
// 5. End Ride Bar
|
||||
driverEndRideBar(),
|
||||
|
||||
// 6. أزرار الطوارئ والاتصال
|
||||
SosConnect(),
|
||||
// 6. SOS
|
||||
SosConnect(),
|
||||
|
||||
// 7. دائرة عرض السرعة
|
||||
speedCircle(),
|
||||
GoogleMapApp(),
|
||||
// 8. نافذة عرض السعر النهائي (تظهر بعد انتهاء الرحلة)
|
||||
const PricesWindow(),
|
||||
],
|
||||
),
|
||||
));
|
||||
// 7. Speed
|
||||
speedCircle(),
|
||||
|
||||
// 8. External Map
|
||||
Positioned(
|
||||
bottom: 100,
|
||||
right: 10,
|
||||
child: GoogleMapApp(),
|
||||
),
|
||||
|
||||
// 9. Prices Window
|
||||
const PricesWindow(),
|
||||
],
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// New: تصميم جديد لشريط تعليمات الطريق في أعلى الشاشة
|
||||
// ... The rest of your widgets (InstructionsOfRoads, CancelWidget, etc.) remain unchanged ...
|
||||
// ... Keep the code below exactly as you had it in the previous snippet ...
|
||||
|
||||
class InstructionsOfRoads extends StatelessWidget {
|
||||
const InstructionsOfRoads({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) =>
|
||||
// يتم إظهار التعليمات فقط إذا كانت متوفرة
|
||||
controller.currentInstruction.isNotEmpty
|
||||
? Positioned(
|
||||
bottom: 10,
|
||||
left: MediaQuery.of(context).size.width * 0.15,
|
||||
right: MediaQuery.of(context).size.width * 0.15,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
return Positioned(
|
||||
bottom: 10,
|
||||
left: MediaQuery.of(context).size.width * 0.15,
|
||||
right: MediaQuery.of(context).size.width * 0.15,
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) => controller.currentInstruction.isNotEmpty
|
||||
? AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.directions,
|
||||
color: AppColor.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
controller.currentInstruction,
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.directions, color: AppColor.primaryColor),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
controller.currentInstruction,
|
||||
style: AppStyle.title.copyWith(fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(), // في حالة عدم وجود تعليمات، لا يظهر شيء
|
||||
const SizedBox(width: 10),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
controller.toggleTts();
|
||||
},
|
||||
child: Icon(
|
||||
controller.isTtsEnabled
|
||||
? Icons.volume_up
|
||||
: Icons.volume_off,
|
||||
color: controller.isTtsEnabled
|
||||
? AppColor.greenColor
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Changed: تم تعديل تصميم وموضع زر الإلغاء ليكون أيقونة بسيطة في الأعلى
|
||||
class CancelWidget extends StatelessWidget {
|
||||
const CancelWidget({
|
||||
super.key,
|
||||
@@ -137,7 +194,6 @@ class CancelWidget extends StatelessWidget {
|
||||
left: 10,
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
// يظهر زر الإلغاء فقط قبل انتهاء الرحلة
|
||||
if (controller.isRideFinished) return const SizedBox.shrink();
|
||||
|
||||
return GestureDetector(
|
||||
@@ -199,7 +255,6 @@ class CancelWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// Changed: تم تعديل تصميم نافذة السعر لتكون أكثر وضوحًا
|
||||
class PricesWindow extends StatelessWidget {
|
||||
const PricesWindow({
|
||||
super.key,
|
||||
|
||||
@@ -621,45 +621,95 @@ class FloatingActionButtons extends StatelessWidget {
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
// هذا الكود يوضع داخل الـ Stack في ملف الواجهة (HomeCaptain View)
|
||||
|
||||
box.read(BoxName.rideStatus) == 'Applied' ||
|
||||
box.read(BoxName.rideStatus) == 'Begin'
|
||||
? Positioned(
|
||||
bottom: Get.height * .2,
|
||||
right: 6,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(microseconds: 200),
|
||||
width: homeCaptainController.widthMapTypeAndTraffic,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColor.blueColor),
|
||||
color: AppColor.secondaryColor,
|
||||
borderRadius: BorderRadius.circular(15)),
|
||||
child: GestureDetector(
|
||||
onLongPress: () {
|
||||
box.write(BoxName.rideStatus, 'delete');
|
||||
homeCaptainController.update();
|
||||
},
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
box.read(BoxName.rideStatus) == 'Applied'
|
||||
? {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments:
|
||||
box.read(BoxName.rideArguments)),
|
||||
Get.put(MapDriverController())
|
||||
.changeRideToBeginToPassenger()
|
||||
}
|
||||
: {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments:
|
||||
box.read(BoxName.rideArguments)),
|
||||
Get.put(MapDriverController())
|
||||
.startRideFromStartApp()
|
||||
};
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.directions_rounded,
|
||||
size: 29,
|
||||
color: AppColor.blueColor,
|
||||
// جعلنا الزر يظهر في المنتصف أو يمتد ليكون واضحاً جداً
|
||||
right: 20,
|
||||
left: 20,
|
||||
child: Center(
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(
|
||||
milliseconds:
|
||||
200), // تم تصحيح microseconds إلى milliseconds لحركة أنعم
|
||||
// أزلنا العرض الثابت homeCaptainController.widthMapTypeAndTraffic لكي يتسع للنص
|
||||
// width: homeCaptainController.widthMapTypeAndTraffic,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: AppColor.blueColor,
|
||||
width: 2), // تعريض الإطار قليلاً
|
||||
color: AppColor.secondaryColor, // لون الخلفية
|
||||
borderRadius: BorderRadius.circular(
|
||||
30), // تدوير الحواف ليشبه الأزرار الحديثة
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
)
|
||||
]),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
onLongPress: () {
|
||||
// وظيفة الحذف عند الضغط الطويل (للطوارئ)
|
||||
box.write(BoxName.rideStatus, 'delete');
|
||||
homeCaptainController.update();
|
||||
},
|
||||
onTap: () {
|
||||
// نفس منطقك الأصلي للانتقال
|
||||
if (box.read(BoxName.rideStatus) == 'Applied') {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments: box.read(BoxName.rideArguments));
|
||||
Get.put(MapDriverController())
|
||||
.changeRideToBeginToPassenger();
|
||||
} else {
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments: box.read(BoxName.rideArguments));
|
||||
Get.put(MapDriverController())
|
||||
.startRideFromStartApp();
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min, // حجم الزر على قد المحتوى
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons
|
||||
.directions_car_filled_rounded, // تغيير الأيقونة لسيارة أو اتجاهات لتكون معبرة أكثر
|
||||
size: 24,
|
||||
color: AppColor.blueColor,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10), // مسافة بين الأيقونة والنص
|
||||
Text(
|
||||
"متابعة الرحلة", // النص الواضح للسائق
|
||||
style: const TextStyle(
|
||||
color: AppColor.blueColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily:
|
||||
'Cairo', // تأكد من نوع الخط المستخدم عندك
|
||||
),
|
||||
),
|
||||
if (box.read(BoxName.rideStatus) ==
|
||||
'Begin') ...[
|
||||
const SizedBox(width: 5),
|
||||
// إضافة مؤشر صغير (نقطة حمراء) إذا كانت الرحلة قد بدأت بالفعل (اختياري)
|
||||
const Icon(Icons.circle,
|
||||
size: 8, color: Colors.green)
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -219,9 +219,10 @@ Future<void> checkForPendingOrderFromServer() async {
|
||||
link: AppLink.getArgumentAfterAppliedFromBackground,
|
||||
payload: {'driver_id': driverId},
|
||||
);
|
||||
Log.print('response: ${response}');
|
||||
|
||||
// Assuming the server returns order data if found, or 'failure'/'none' if not
|
||||
if (response != 'failure') {
|
||||
if (response['status'] == 'success') {
|
||||
final Map<String, dynamic> orderInfoFromServer = response['message'];
|
||||
final Map<String, dynamic> rideArguments =
|
||||
_transformServerDataToAppArguments(orderInfoFromServer);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:slide_to_act/slide_to_act.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
import 'dart:io';
|
||||
@@ -11,82 +12,88 @@ import '../../../../controller/home/captin/map_driver_controller.dart';
|
||||
import '../../../widgets/elevated_btn.dart';
|
||||
|
||||
// Changed: إعادة تصميم كاملة للشريط ليصبح شريطًا علويًا عند بدء الرحلة
|
||||
GetBuilder<MapDriverController> driverEndRideBar() {
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) => AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
// New: يظهر الشريط من الأعلى عندما تبدأ الرحلة
|
||||
top: controller.isRideStarted ? 0 : -200,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 10,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// -- معلومات الرحلة --
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildInfoColumn(
|
||||
icon: Icons.social_distance,
|
||||
text: '${controller.distance} ${'KM'.tr}',
|
||||
label: 'Distance'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.timelapse,
|
||||
text: controller.hours > 1
|
||||
? '${controller.hours}h ${controller.minutes}m'
|
||||
: '${controller.minutes}m',
|
||||
label: 'Time'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.money_sharp,
|
||||
text: '${controller.paymentAmount} ${'SYP'.tr}',
|
||||
label: 'Price'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
const Divider(height: 20),
|
||||
// ملف: driver_end_ride_bar.dart
|
||||
|
||||
// -- مؤقت الرحلة المتبقي (إن وجد) --
|
||||
_builtTimerAndCarType(),
|
||||
Widget driverEndRideBar() {
|
||||
// 1. Positioned هي الوالد المباشر (لأنها داخل Stack في الصفحة الرئيسية)
|
||||
return Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
// 2. GetBuilder يكون في الداخل
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) => AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
// 3. نستخدم التحريك (Translation) لإخفاء الشريط وإظهاره بدلاً من تغيير الـ top
|
||||
transform: Matrix4.translationValues(
|
||||
0, controller.isRideStarted ? 0 : -250, 0),
|
||||
child: Card(
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 10,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(bottom: Radius.circular(24)),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_buildInfoColumn(
|
||||
icon: Icons.social_distance,
|
||||
text: '${controller.distance} ${'KM'.tr}',
|
||||
label: 'Distance'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.timelapse,
|
||||
text: controller.hours > 1
|
||||
? '${controller.hours}h ${controller.minutes}m'
|
||||
: '${controller.minutes}m',
|
||||
label: 'Time'.tr,
|
||||
),
|
||||
_buildInfoColumn(
|
||||
icon: Icons.money_sharp,
|
||||
text:
|
||||
'${NumberFormat('#,##0').format(double.tryParse(controller.paymentAmount.toString()) ?? 0)} ${'SYP'.tr}',
|
||||
label: 'Price'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// -- زر إنهاء الرحلة المنزلق --
|
||||
SlideAction(
|
||||
height: 55,
|
||||
borderRadius: 15,
|
||||
elevation: 4,
|
||||
text: 'Slide to End Trip'.tr,
|
||||
textStyle: AppStyle.title.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
// ... بقية الكود كما هو (الأزرار والمؤقت)
|
||||
if (controller.carType != 'Mishwar Vip')
|
||||
const Divider(height: 20),
|
||||
const _builtTimerAndCarType(),
|
||||
const SizedBox(height: 12),
|
||||
SlideAction(
|
||||
height: 55,
|
||||
borderRadius: 15,
|
||||
elevation: 4,
|
||||
text: 'Slide to End Trip'.tr,
|
||||
textStyle: AppStyle.title.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
outerColor: AppColor.redColor,
|
||||
innerColor: Colors.white,
|
||||
sliderButtonIcon: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.redColor,
|
||||
size: 24,
|
||||
),
|
||||
sliderRotate: false,
|
||||
onSubmit: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
controller.finishRideFromDriver();
|
||||
return null;
|
||||
},
|
||||
),
|
||||
outerColor: AppColor.redColor,
|
||||
innerColor: Colors.white,
|
||||
sliderButtonIcon: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: AppColor.redColor,
|
||||
size: 24,
|
||||
),
|
||||
sliderRotate: false,
|
||||
onSubmit: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
controller.finishRideFromDriver();
|
||||
return null; // New: onSubmit now returns null
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -116,100 +123,119 @@ class _builtTimerAndCarType extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.find<MapDriverController>();
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// -- نوع السيارة --
|
||||
Container(
|
||||
decoration: AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text(
|
||||
controller.carType,
|
||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
// -- مؤقت الرحلة --
|
||||
if (controller.carType != 'Comfort' &&
|
||||
controller.carType != 'Mishwar Vip' &&
|
||||
controller.carType != 'Lady') ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor.withOpacity(0.8)
|
||||
: AppColor.greenColor.withOpacity(0.8),
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor
|
||||
: AppColor.greenColor,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: Colors.transparent,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white.withOpacity(0.2)),
|
||||
minHeight: 40,
|
||||
value: controller.progressTimerRideBegin.toDouble(),
|
||||
),
|
||||
Text(
|
||||
controller.stringRemainingTimeRideBegin,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// نستخدم GetBuilder هنا لضمان تحديث العداد في كل ثانية
|
||||
return GetBuilder<MapDriverController>(builder: (controller) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// -- نوع السيارة --
|
||||
Container(
|
||||
decoration:
|
||||
AppStyle.boxDecoration1.copyWith(color: Colors.grey[200]),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text(
|
||||
controller.carType.tr,
|
||||
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
)
|
||||
),
|
||||
|
||||
// -- مؤقت الرحلة --
|
||||
if (controller.carType != 'Comfort' &&
|
||||
controller.carType != 'Mishwar Vip' &&
|
||||
controller.carType != 'Lady') ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor.withOpacity(0.8)
|
||||
: AppColor.greenColor.withOpacity(0.8),
|
||||
controller.remainingTimeTimerRideBegin < 60
|
||||
? AppColor.redColor
|
||||
: AppColor.greenColor,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: Colors.transparent,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white.withOpacity(0.2)),
|
||||
minHeight: 40,
|
||||
// تأكد من أن هذه القيمة بين 0.0 و 1.0 في الكونترولر
|
||||
value: controller.progressTimerRideBegin.toDouble(),
|
||||
),
|
||||
Text(
|
||||
controller.stringRemainingTimeRideBegin,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
],
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Changed: تم تعديل مكان ومظهر دائرة السرعة
|
||||
GetBuilder<MapDriverController> speedCircle() {
|
||||
if (Get.find<MapDriverController>().speed > 100) {
|
||||
if (Platform.isIOS) {
|
||||
HapticFeedback.selectionClick();
|
||||
} else {
|
||||
Vibration.vibrate(duration: 1000);
|
||||
}
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
titleStyle: AppStyle.title,
|
||||
title: 'Speed Over'.tr,
|
||||
middleText: 'Please slow down'.tr,
|
||||
middleTextStyle: AppStyle.title,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'I will slow down'.tr,
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
return controller.isRideStarted
|
||||
? Positioned(
|
||||
// New: تم وضع دائرة السرعة في الأسفل يمينًا
|
||||
bottom: 25,
|
||||
left: 3,
|
||||
child: Container(
|
||||
// غيرنا نوع الإرجاع إلى Widget بدلاً من GetBuilder
|
||||
Widget speedCircle() {
|
||||
// التحقق من السرعة يمكن أن يبقى هنا أو داخل الـ builder
|
||||
// لكن التنبيهات (Vibration/Dialog) يفضل أن تكون داخل الـ builder لتجنب تكرارها أثناء إعادة البناء الخارجية
|
||||
|
||||
return Positioned(
|
||||
// New: Positioned الآن هي الوالد المباشر (يجب وضع هذه الدالة داخل Stack في الصفحة الرئيسية)
|
||||
bottom: 25,
|
||||
left: 3,
|
||||
child: GetBuilder<MapDriverController>(
|
||||
builder: (controller) {
|
||||
// التحقق من التنبيهات هنا
|
||||
if (controller.speed > 100) {
|
||||
// نستخدم addPostFrameCallback لضمان عدم استدعاء الـ Dialog أثناء عملية البناء
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!Get.isDialogOpen!) {
|
||||
// تجنب فتح أكثر من نافذة
|
||||
if (Platform.isIOS) {
|
||||
HapticFeedback.selectionClick();
|
||||
} else {
|
||||
Vibration.vibrate(duration: 1000);
|
||||
}
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
titleStyle: AppStyle.title,
|
||||
title: 'Speed Over'.tr,
|
||||
middleText: 'Please slow down'.tr,
|
||||
middleTextStyle: AppStyle.title,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'I will slow down'.tr,
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return controller.isRideStarted
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
boxShadow: [BoxShadow(blurRadius: 5, color: Colors.black26)],
|
||||
boxShadow: const [
|
||||
BoxShadow(blurRadius: 5, color: Colors.black26)
|
||||
],
|
||||
border: Border.all(
|
||||
width: 4,
|
||||
color: controller.speed > 100
|
||||
@@ -227,13 +253,13 @@ GetBuilder<MapDriverController> speedCircle() {
|
||||
controller.speed.toStringAsFixed(0),
|
||||
style: AppStyle.number.copyWith(fontSize: 24),
|
||||
),
|
||||
Text("km/h", style: TextStyle(fontSize: 10)),
|
||||
const Text("km/h", style: TextStyle(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox();
|
||||
},
|
||||
)
|
||||
: const SizedBox(); // إذا لم تبدأ الرحلة نخفي العنصر وهو داخل الـ Positioned
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_driver/constant/info.dart';
|
||||
import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
|
||||
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
|
||||
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
||||
|
||||
import '../../../../constant/box_name.dart';
|
||||
import '../../../../constant/style.dart';
|
||||
import '../../../../controller/firebase/notification_service.dart';
|
||||
import '../../../../main.dart';
|
||||
import '../../../../print.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
|
||||
// Changed: إعادة تصميم كاملة لتصبح شريط معلومات علوي مدمج
|
||||
class PassengerInfoWindow extends StatelessWidget {
|
||||
PassengerInfoWindow({super.key});
|
||||
final fcm = Get.isRegistered<FirebaseMessagesController>()
|
||||
? Get.find<FirebaseMessagesController>()
|
||||
: Get.put(FirebaseMessagesController());
|
||||
|
||||
// Optimization: defining static styles here avoids rebuilding them every frame
|
||||
final TextStyle _labelStyle =
|
||||
AppStyle.title.copyWith(color: Colors.grey[600], fontSize: 13);
|
||||
final TextStyle _valueStyle =
|
||||
AppStyle.title.copyWith(fontWeight: FontWeight.bold, fontSize: 18);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Get safe area top padding (for Notches/Status bars)
|
||||
final double topPadding = MediaQuery.of(context).padding.top;
|
||||
final double topMargin = topPadding + 10; // Safe area + 10px spacing
|
||||
|
||||
return GetBuilder<MapDriverController>(
|
||||
builder: (controller) => AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInOut,
|
||||
// Changed: تم تغيير الموضع من الأسفل إلى الأعلى
|
||||
top: controller.isPassengerInfoWindow ? 15.0 : -200.0,
|
||||
// FIX: Use calculated top margin to avoid hiding behind status bar
|
||||
top: controller.isPassengerInfoWindow ? topMargin : -250.0,
|
||||
left: 15.0,
|
||||
right: 15.0,
|
||||
child: Card(
|
||||
elevation: 8,
|
||||
shadowColor: Colors.black.withOpacity(0.3),
|
||||
// Optimization: Lower elevation slightly for smoother animation on cheap phones
|
||||
elevation: 4,
|
||||
shadowColor: Colors.black.withOpacity(0.2),
|
||||
color: Colors.white,
|
||||
surfaceTintColor: Colors.white, // Fix for Material 3 tinting
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
@@ -41,14 +47,12 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// New: صف علوي للمعلومات الأساسية
|
||||
_buildTopInfoRow(controller),
|
||||
const Divider(height: 16),
|
||||
|
||||
// Changed: الأزرار الآن في صف أفقي ومدمج
|
||||
if (!controller.isRideBegin) _buildActionButtons(controller),
|
||||
|
||||
// New: مؤشر انتظار الراكب المدمج
|
||||
// Optimization: Only render linear indicator if needed
|
||||
if (controller.remainingTimeInPassengerLocatioWait < 300 &&
|
||||
controller.remainingTimeInPassengerLocatioWait != 0 &&
|
||||
!controller.isRideBegin) ...[
|
||||
@@ -56,7 +60,6 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
_buildWaitingIndicator(controller),
|
||||
],
|
||||
|
||||
// زر الإلغاء بعد انتهاء وقت الانتظار
|
||||
if (controller.isdriverWaitTimeEnd &&
|
||||
!controller.isRideBegin) ...[
|
||||
const SizedBox(height: 10),
|
||||
@@ -70,35 +73,33 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// New: ودجت لعرض المعلومات العلوية بشكل مدمج
|
||||
Widget _buildTopInfoRow(MapDriverController controller) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start, // Align top
|
||||
children: [
|
||||
// معلومات الراكب
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Go to passenger:'.tr, style: _labelStyle),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Go to passenger:'.tr,
|
||||
style: AppStyle.title
|
||||
.copyWith(color: Colors.grey[600], fontSize: 13),
|
||||
),
|
||||
Text(
|
||||
controller.passengerName,
|
||||
style: AppStyle.title
|
||||
.copyWith(fontWeight: FontWeight.bold, fontSize: 18),
|
||||
controller.passengerName ?? 'loading...',
|
||||
style: _valueStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// معلومات المسافة والزمن
|
||||
Row(
|
||||
const SizedBox(width: 10), // Spacing between name and chips
|
||||
Column(
|
||||
// Changed to Column for better layout on small screens
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
_buildInfoChip(Icons.map_outlined, '${controller.distance} km'),
|
||||
const SizedBox(width: 8),
|
||||
const SizedBox(height: 6), // Vertical spacing
|
||||
_buildInfoChip(
|
||||
Icons.timer_outlined,
|
||||
controller.hours > 1
|
||||
@@ -111,10 +112,9 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
// New: ودجت مخصص لعرض المعلومات بشكل أنيق
|
||||
Widget _buildInfoChip(IconData icon, String text) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primaryColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -122,144 +122,164 @@ class PassengerInfoWindow extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: AppColor.primaryColor, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(text,
|
||||
style: TextStyle(
|
||||
color: AppColor.primaryColor, fontWeight: FontWeight.bold)),
|
||||
Icon(icon, color: AppColor.primaryColor, size: 14), // Smaller icon
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: AppColor.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12 // Slightly smaller font for chips
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Changed: إعادة تصميم أزرار الإجراءات لتكون أكثر دمجًا
|
||||
Widget _buildActionButtons(MapDriverController controller) {
|
||||
return Row(
|
||||
children: [
|
||||
if (controller.isArrivedSend)
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.location_on, size: 18),
|
||||
label: Text('I Arrive'.tr),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.yellowColor,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () async {
|
||||
controller.getRoute(
|
||||
origin: controller.latLngPassengerLocation,
|
||||
destination: controller.latLngPassengerDestination,
|
||||
routeColor: Colors.blue // أو أي لون
|
||||
);
|
||||
if (await controller
|
||||
.calculateDistanceBetweenDriverAndPassengerLocation() <
|
||||
140) {
|
||||
// fcm.sendNotificationToDriverMAP(
|
||||
// 'Hi ,I Arrive your site',
|
||||
// 'I Arrive at your site'.tr,
|
||||
// controller.tokenPassenger,
|
||||
// [],
|
||||
// 'ding.wav',
|
||||
// );
|
||||
Log.print(
|
||||
'controller.tokenPassenger: ${controller.tokenPassenger}');
|
||||
flex: 1,
|
||||
child: SizedBox(
|
||||
height: 45, // Fixed height for consistency
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.yellowColor,
|
||||
foregroundColor: Colors.black,
|
||||
padding: EdgeInsets.zero, // Reduce padding to fit text
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () async {
|
||||
// LOGIC FIX: Check distance FIRST
|
||||
double distance = await controller
|
||||
.calculateDistanceBetweenDriverAndPassengerLocation();
|
||||
|
||||
NotificationService.sendNotification(
|
||||
target: controller.tokenPassenger.toString(),
|
||||
title: 'Hi ,I Arrive your site'.tr,
|
||||
body: 'I Arrive at your site'.tr,
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'ding',
|
||||
driverList: [], category: 'Hi ,I Arrive your site',
|
||||
);
|
||||
controller.startTimerToShowDriverWaitPassengerDuration();
|
||||
controller.isArrivedSend = false;
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'You are not near the passenger location'.tr,
|
||||
'Please go to the pickup location exactly'.tr,
|
||||
() => Get.back());
|
||||
}
|
||||
},
|
||||
if (distance < 140) {
|
||||
// Only draw route and send notif if close enough
|
||||
controller.getRoute(
|
||||
origin: controller.latLngPassengerLocation,
|
||||
destination: controller.latLngPassengerDestination,
|
||||
routeColor: Colors.blue);
|
||||
|
||||
NotificationService.sendNotification(
|
||||
target: controller.tokenPassenger.toString(),
|
||||
title: 'Hi ,I Arrive your site'.tr,
|
||||
body: 'I Arrive at your site'.tr,
|
||||
isTopic: false,
|
||||
tone: 'ding',
|
||||
driverList: [],
|
||||
category: 'Hi ,I Arrive your site',
|
||||
);
|
||||
controller.startTimerToShowDriverWaitPassengerDuration();
|
||||
controller.isArrivedSend = false;
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
'You are not near'.tr, // Shortened title
|
||||
'Please go to the pickup location exactly'.tr,
|
||||
() => Get.back());
|
||||
}
|
||||
},
|
||||
// Using Row instead of .icon constructor for better control
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.location_on, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text('I Arrive'.tr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 12))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (controller.isArrivedSend) const SizedBox(width: 8),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.play_arrow_rounded, size: 20),
|
||||
label: Text('Start the Ride'.tr,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.greenColor,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
flex: 2, // Give "Start" button more space
|
||||
child: SizedBox(
|
||||
height: 45,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor.greenColor,
|
||||
foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
MyDialog().getDialog(
|
||||
"Is the Passenger in your Car?".tr,
|
||||
"Don't start trip if passenger not in your car".tr,
|
||||
() async {
|
||||
await controller.startRideFromDriver();
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.play_arrow_rounded, size: 22),
|
||||
const SizedBox(width: 6),
|
||||
Flexible(
|
||||
child: Text('Start the Ride'.tr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
MyDialog().getDialog(
|
||||
"Is the Passenger in your Car?".tr,
|
||||
"Don't start trip if passenger not in your car".tr,
|
||||
() async {
|
||||
await controller.startRideFromDriver();
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Changed: مؤشر الانتظار الآن أكثر دمجًا
|
||||
Widget _buildWaitingIndicator(MapDriverController controller) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
backgroundColor: AppColor.greyColor.withOpacity(0.3),
|
||||
return Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: LinearProgressIndicator(
|
||||
backgroundColor: AppColor.greyColor.withOpacity(0.2),
|
||||
// Ternary for color is fine
|
||||
color: controller.remainingTimeInPassengerLocatioWait < 60
|
||||
? AppColor.redColor
|
||||
: AppColor.greenColor,
|
||||
minHeight: 25,
|
||||
minHeight: 8, // Thinner looks more modern
|
||||
value: controller.progressInPassengerLocationFromDriver.toDouble(),
|
||||
),
|
||||
Text(
|
||||
controller.stringRemainingTimeWaitingPassenger,
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 13,
|
||||
shadows: [
|
||||
Shadow(color: Colors.black.withOpacity(0.5), blurRadius: 2)
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"${'Waiting'.tr}: ${controller.stringRemainingTimeWaitingPassenger}",
|
||||
style: AppStyle.title.copyWith(
|
||||
color: Colors.grey[700],
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// New: زر الإلغاء بعد انتهاء الانتظار
|
||||
Widget _buildCancelAfterWaitButton(MapDriverController controller) {
|
||||
return MyElevatedButton(
|
||||
title: 'You Can Cancel the Trip and get Cost From '.tr +
|
||||
AppInformation.appName.tr,
|
||||
title: 'Cancel Trip & Get Cost'.tr, // Shortened text
|
||||
kolor: AppColor.gold,
|
||||
onPressed: () {
|
||||
MyDialog().getDialog('Are you sure to cancel?'.tr, '', () async {
|
||||
NotificationService.sendNotification(
|
||||
target: controller.tokenPassenger.toString(),
|
||||
title: 'Driver Cancelled Your Trip'.tr,
|
||||
body:
|
||||
'You will need to pay the cost to the driver, or it will be deducted from your next trip',
|
||||
isTopic: false, // Important: this is a token
|
||||
body: 'You will need to pay the cost...',
|
||||
isTopic: false,
|
||||
tone: 'cancel',
|
||||
driverList: [], category: 'Driver Cancelled Your Trip',
|
||||
driverList: [],
|
||||
category: 'Driver Cancelled Your Trip',
|
||||
);
|
||||
box.write(BoxName.rideStatus, 'Cancel');
|
||||
await controller.addWaitingTimeCostFromPassengerToDriverWallet();
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:sefer_driver/constant/api_key.dart';
|
||||
import '../../../../constant/box_name.dart';
|
||||
@@ -213,13 +214,14 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
await _closeOverlay();
|
||||
return;
|
||||
}
|
||||
|
||||
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
var res = await CRUD().post(
|
||||
link: "${AppLink.ride}/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
List<String> bodyToPassenger = [
|
||||
_getData(6).toString(),
|
||||
_getData(8).toString(),
|
||||
@@ -340,16 +342,12 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
_log("Driver ID is null, cannot refuse order");
|
||||
return;
|
||||
}
|
||||
await _crud.post(link: AppLink.addDriverOrder, payload: {
|
||||
_crud.post(link: AppLink.addDriverOrder, payload: {
|
||||
'driver_id': driverId,
|
||||
'order_id': orderID,
|
||||
'status': 'Refused'
|
||||
});
|
||||
await _crud.post(link: AppLink.updateRides, payload: {
|
||||
'id': orderID,
|
||||
'status': 'Refused',
|
||||
'driver_id': driverId,
|
||||
});
|
||||
|
||||
_log("Order $orderID refused successfully");
|
||||
} catch (e) {
|
||||
_log("Error in _apiRefuseOrder for $orderID: $e");
|
||||
@@ -509,9 +507,16 @@ class _OrderOverlayState extends State<OrderOverlay>
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: _buildHighlightInfo("\$${order.price}", "السعر".tr,
|
||||
Icons.monetization_on_rounded, AppColors.priceHighlight,
|
||||
isLarge: true),
|
||||
child: _buildHighlightInfo(
|
||||
// التعديل هنا 👇
|
||||
"${NumberFormat('#,##0').format(order.price)} ل.س",
|
||||
// أو يمكنك استخدام "SYP" بدلاً من "ل.س"
|
||||
|
||||
"السعر".tr,
|
||||
Icons.monetization_on_rounded,
|
||||
AppColors.priceHighlight,
|
||||
isLarge: true,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
|
||||
@@ -248,38 +248,31 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
|
||||
kolor: AppColor.greenColor,
|
||||
title: 'Accept Order'.tr,
|
||||
onPressed: () async {
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
controller.endTimer();
|
||||
controller.changeApplied();
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.updateStausFromSpeed,
|
||||
payload: {
|
||||
'id': (controller.myList[16]),
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
CRUD().post(
|
||||
link:
|
||||
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php",
|
||||
"${AppLink.ride}/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
'id': (controller.myList[16]),
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
|
||||
if (res == 'failure') {
|
||||
MyDialog().getDialog(
|
||||
"This ride is already applied by another driver."
|
||||
.tr,
|
||||
'', () {
|
||||
Get.back();
|
||||
// Get.back();
|
||||
Get.back();
|
||||
});
|
||||
} else {
|
||||
await CRUD().postFromDialogue(
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
controller.endTimer();
|
||||
controller.changeApplied();
|
||||
|
||||
CRUD().postFromDialogue(
|
||||
link: AppLink.addDriverOrder,
|
||||
payload: {
|
||||
'driver_id':
|
||||
@@ -386,33 +379,26 @@ class _OrderRequestPageState extends State<OrderRequestPage> {
|
||||
title: 'Refuse Order'.tr,
|
||||
onPressed: () async {
|
||||
controller.endTimer();
|
||||
List<String> bodyToPassenger = [
|
||||
box.read(BoxName.driverID).toString(),
|
||||
box.read(BoxName.nameDriver).toString(),
|
||||
box.read(BoxName.tokenDriver).toString(),
|
||||
];
|
||||
// List<String> bodyToPassenger = [
|
||||
// box.read(BoxName.driverID).toString(),
|
||||
// box.read(BoxName.nameDriver).toString(),
|
||||
// box.read(BoxName.tokenDriver).toString(),
|
||||
// ];
|
||||
|
||||
// FirebaseMessagesController()
|
||||
// .sendNotificationToPassengerToken(
|
||||
// 'Order Under Review'.tr,
|
||||
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||
// controller.myList[9].toString(),
|
||||
// bodyToPassenger,
|
||||
// 'notification');
|
||||
NotificationService.sendNotification(
|
||||
target: controller.myList[9].toString(),
|
||||
title: 'Order Under Review'.tr,
|
||||
body:
|
||||
'${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'start',
|
||||
driverList: [], category: 'Order Under Review',
|
||||
);
|
||||
// NotificationService.sendNotification(
|
||||
// target: controller.myList[9].toString(),
|
||||
// title: 'Order Under Review'.tr,
|
||||
// body:
|
||||
// '${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||
// isTopic: false, // Important: this is a token
|
||||
// tone: 'start',
|
||||
// driverList: bodyToPassenger,
|
||||
// category: 'Order Under Review',
|
||||
// );
|
||||
|
||||
controller.refuseOrder(
|
||||
EncryptionHelper.instance.encryptData(
|
||||
controller.myList[16].toString()),
|
||||
);
|
||||
// controller.refuseOrder(
|
||||
// (controller.myList[16].toString()),
|
||||
// );
|
||||
controller.addRideToNotificationDriverString(
|
||||
controller.myList[16].toString(),
|
||||
controller.myList[29].toString(),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -66,48 +66,48 @@ class PointsCaptain extends StatelessWidget {
|
||||
color: AppColor.blueColor, size: 70),
|
||||
],
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: paymentController.formKey,
|
||||
child: MyTextForm(
|
||||
controller:
|
||||
paymentController.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963941234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (paymentController.formKey.currentState!
|
||||
.validate()) {
|
||||
box.write(
|
||||
BoxName.phoneWallet,
|
||||
paymentController
|
||||
.walletphoneController.text);
|
||||
await payWithMTNWallet(
|
||||
context, pricePoint.toString(), 'SYP');
|
||||
}
|
||||
}));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Pay by MTN Wallet'.tr),
|
||||
const SizedBox(width: 10),
|
||||
Image.asset(
|
||||
'assets/images/cashMTN.png',
|
||||
width: 70,
|
||||
height: 70,
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
],
|
||||
)),
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// Get.back();
|
||||
// Get.defaultDialog(
|
||||
// barrierDismissible: false,
|
||||
// title: 'Insert Wallet phone number'.tr,
|
||||
// content: Form(
|
||||
// key: paymentController.formKey,
|
||||
// child: MyTextForm(
|
||||
// controller:
|
||||
// paymentController.walletphoneController,
|
||||
// label: 'Insert Wallet phone number'.tr,
|
||||
// hint: '963941234567',
|
||||
// type: TextInputType.phone)),
|
||||
// confirm: MyElevatedButton(
|
||||
// title: 'OK'.tr,
|
||||
// onPressed: () async {
|
||||
// Get.back();
|
||||
// if (paymentController.formKey.currentState!
|
||||
// .validate()) {
|
||||
// box.write(
|
||||
// BoxName.phoneWallet,
|
||||
// paymentController
|
||||
// .walletphoneController.text);
|
||||
// await payWithMTNWallet(
|
||||
// context, pricePoint.toString(), 'SYP');
|
||||
// }
|
||||
// }));
|
||||
// },
|
||||
// child: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Text('Pay by MTN Wallet'.tr),
|
||||
// const SizedBox(width: 10),
|
||||
// Image.asset(
|
||||
// 'assets/images/cashMTN.png',
|
||||
// width: 70,
|
||||
// height: 70,
|
||||
// fit: BoxFit.fill,
|
||||
// ),
|
||||
// ],
|
||||
// )),
|
||||
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
@@ -153,51 +153,26 @@ class PointsCaptain extends StatelessWidget {
|
||||
)),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Insert Wallet phone number'.tr,
|
||||
content: Form(
|
||||
key: paymentController.formKey,
|
||||
child: MyTextForm(
|
||||
controller:
|
||||
paymentController.walletphoneController,
|
||||
label: 'Insert Wallet phone number'.tr,
|
||||
hint: '963941234567',
|
||||
type: TextInputType.phone)),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
if (paymentController.formKey.currentState!
|
||||
.validate()) {
|
||||
box.write(
|
||||
BoxName.phoneWallet,
|
||||
paymentController
|
||||
.walletphoneController.text);
|
||||
// await payWithSyriaTelWallet(
|
||||
// context, pricePoint.toString(), 'SYP');
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication()
|
||||
.isDeviceSupported();
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate =
|
||||
await LocalAuthentication()
|
||||
.authenticate(
|
||||
localizedReason:
|
||||
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
if (!didAuthenticate) {
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
print(
|
||||
"❌ User did not authenticate with biometrics");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Get.to(() => PaymentScreenSmsProvider(
|
||||
amount: pricePoint));
|
||||
}
|
||||
}));
|
||||
// التحقق بالبصمة قبل أي شيء
|
||||
bool isAuthSupported =
|
||||
await LocalAuthentication().isDeviceSupported();
|
||||
|
||||
if (isAuthSupported) {
|
||||
bool didAuthenticate =
|
||||
await LocalAuthentication().authenticate(
|
||||
localizedReason:
|
||||
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
|
||||
);
|
||||
|
||||
if (!didAuthenticate) {
|
||||
print("❌ User did not authenticate with biometrics");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
|
||||
Get.to(
|
||||
() => PaymentScreenSmsProvider(amount: pricePoint));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
@@ -113,9 +113,11 @@ class WalletCaptainRefactored extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'${'Total Points is'.tr} 💎',
|
||||
style: AppStyle.headTitle2
|
||||
.copyWith(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
'رصيد التشغيل 💎',
|
||||
style: AppStyle.headTitle2.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -283,22 +283,14 @@ class RideAvailableCard extends StatelessWidget {
|
||||
// --- Ride Acceptance Logic ---
|
||||
// This logic is copied exactly from your original code.
|
||||
void _acceptRide() async {
|
||||
var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
|
||||
'id': rideInfo['id'],
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
|
||||
CRUD().post(
|
||||
link: '${AppLink.endPoint}rides/updateStausFromSpeed.php',
|
||||
payload: {
|
||||
'id': rideInfo['id'],
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
}
|
||||
var res = await CRUD().post(
|
||||
link: '${AppLink.endPoint}rides/updateStausFromSpeed.php',
|
||||
payload: {
|
||||
'id': rideInfo['id'],
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
|
||||
if (res != "failure") {
|
||||
List<String> bodyToPassenger = [
|
||||
@@ -312,46 +304,23 @@ class RideAvailableCard extends StatelessWidget {
|
||||
'order_id': rideInfo['id'],
|
||||
'status': 'Apply'
|
||||
});
|
||||
await CRUD().post(link: AppLink.updateRides, payload: {
|
||||
'id': rideInfo['id'],
|
||||
'DriverIsGoingToPassenger': DateTime.now().toString(),
|
||||
'status': 'Applied'
|
||||
});
|
||||
// await CRUD().post(link: AppLink.updateRides, payload: {
|
||||
// 'id': rideInfo['id'],
|
||||
// 'DriverIsGoingToPassenger': DateTime.now().toString(),
|
||||
// 'status': 'Applied'
|
||||
// });
|
||||
await CRUD().post(
|
||||
link: AppLink.updateWaitingRide,
|
||||
payload: {'id': rideInfo['id'], 'status': 'Applied'});
|
||||
if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
|
||||
CRUD().postFromDialogue(
|
||||
link: '${AppLink.endPoint}/driver_order/add.php',
|
||||
payload: {
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
'order_id': rideInfo['id'],
|
||||
'status': 'Apply'
|
||||
});
|
||||
CRUD().post(link: '${AppLink.endPoint}/rides/update.php', payload: {
|
||||
'id': rideInfo['id'],
|
||||
'DriverIsGoingToPassenger': DateTime.now().toString(),
|
||||
'status': 'Applied'
|
||||
});
|
||||
CRUD().post(
|
||||
link:
|
||||
"${AppLink.endPoint}/ride/notificationCaptain/updateWaitingTrip.php",
|
||||
payload: {'id': rideInfo['id'], 'status': 'Applied'});
|
||||
}
|
||||
// if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
|
||||
|
||||
// FirebaseMessagesController().sendNotificationToPassengerToken(
|
||||
// "Accepted Ride".tr,
|
||||
// 'your ride is Accepted'.tr,
|
||||
// rideInfo['passengerToken'].toString(),
|
||||
// bodyToPassenger,
|
||||
// 'start.wav');
|
||||
NotificationService.sendNotification(
|
||||
target: rideInfo['passengerToken'].toString(),
|
||||
title: 'Accepted Ride'.tr,
|
||||
body: 'your ride is Accepted'.tr,
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'start',
|
||||
driverList: [], category: 'Accepted Ride',
|
||||
driverList: bodyToPassenger, category: 'Accepted Ride',
|
||||
);
|
||||
Get.back();
|
||||
Get.to(() => PassengerLocationMapPage(), arguments: {
|
||||
@@ -385,18 +354,15 @@ class RideAvailableCard extends StatelessWidget {
|
||||
'totalPassenger': rideInfo['price'].toString(),
|
||||
'carType': rideInfo['carType'].toString(),
|
||||
'kazan': Get.find<HomeCaptainController>().kazan.toString(),
|
||||
'startNameLocation': rideInfo['startName'].toString(),
|
||||
'endNameLocation': rideInfo['endName'].toString(),
|
||||
});
|
||||
} else {
|
||||
MyDialog().getDialog(
|
||||
"This ride is already taken by another driver.".tr, '', () {
|
||||
CRUD().post(
|
||||
link: AppLink.deleteAvailableRide, payload: {'id': rideInfo['id']});
|
||||
if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
|
||||
CRUD().post(
|
||||
link:
|
||||
'${AppLink.endPoint}/ride/notificationCaptain/deleteAvailableRide.php',
|
||||
payload: {'id': rideInfo['id']});
|
||||
}
|
||||
|
||||
Get.back();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class MyDialog extends GetxController {
|
||||
title: Column(
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
title.tr,
|
||||
style: AppStyle.title.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
|
||||
Reference in New Issue
Block a user