2026-04-03-pre map libra

This commit is contained in:
Hamza-Ayed
2026-04-04 00:12:33 +03:00
parent 0376a835ce
commit cbb1620874
70 changed files with 933 additions and 787 deletions

View File

@@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_contacts/contact.dart';
@@ -124,24 +125,175 @@ Download the Intaleq app now and enjoy your ride!
/// **MODIFIED**: This function now opens the phone's native contact picker.
Future<void> pickContactFromNativeApp() async {
try {
log('=== START: FETCHING ALL CONTACTS FOR BOTTOM SHEET ===',
name: 'ContactPicker');
if (await FlutterContacts.requestPermission(readonly: true)) {
// 1. Open the native contacts app to select a single contact.
final Contact? contact = await FlutterContacts.openExternalPick();
// عرض شاشة تحميل بسيطة ريثما يتم جلب الأسماء
Get.dialog(const Center(child: CircularProgressIndicator()),
barrierDismissible: false);
// 2. If a contact is selected and has a phone number...
if (contact != null && contact.phones.isNotEmpty) {
String selectedPhone = contact.phones.first.number;
log('Permission granted. Calling FlutterContacts.getContacts()...',
name: 'ContactPicker');
// 3. Format the number and update the text field.
invitePhoneController.text = _formatSyrianPhoneNumber(selectedPhone);
update();
// جلب جميع جهات الاتصال إجبارياً من الصفر مع خصائصها
List<Contact> allContacts =
await FlutterContacts.getContacts(withProperties: true);
log('Total Contacts Fetched from Device: ${allContacts.length}',
name: 'ContactPicker');
// فصل الأسماء لمعرفة الخلل
List<Contact> validContacts = [];
List<Contact> invalidContacts = [];
for (var c in allContacts) {
if (c.phones.isNotEmpty) {
validContacts.add(c);
} else {
invalidContacts.add(c);
}
}
log('Contacts WITH phone numbers: ${validContacts.length}',
name: 'ContactPicker');
log('Contacts WITHOUT phone numbers: ${invalidContacts.length}',
name: 'ContactPicker');
// طباعة أول 20 اسم صالح
log('--- Sample of VALID Contacts ---', name: 'ContactPicker');
for (int i = 0; i < validContacts.length && i < 20; i++) {
log('[$i] Name: ${validContacts[i].displayName}, Phone: ${validContacts[i].phones.first.number}',
name: 'ContactPicker');
}
// طباعة أول 20 اسم غير صالح (بدون أرقام) لفحص المشكلة
log('--- Sample of INVALID Contacts (No Phone) ---',
name: 'ContactPicker');
for (int i = 0; i < invalidContacts.length && i < 20; i++) {
log('[$i] Name: ${invalidContacts[i].displayName}',
name: 'ContactPicker');
}
Get.back(); // إغلاق شاشة التحميل
if (validContacts.isEmpty) {
mySnackeBarError('No contacts with phone numbers found'.tr);
return;
}
// متغيرات للبحث داخل القائمة المنسدلة
RxList<Contact> filteredContacts = validContacts.obs;
TextEditingController searchController = TextEditingController();
// دالة لتنظيف النصوص من أي رموز معطوبة
String sanitizeText(String input) {
if (input.isEmpty) return '';
return input
.replaceAll(
RegExp(r'[^\x00-\x7F\u0600-\u06FF\u08A0-\u08FF\p{L}\p{N}\s]'),
'')
.trim();
}
// فتح دليل هاتف مخصص داخل التطبيق
Get.bottomSheet(
Container(
height: Get.height * 0.85,
decoration: BoxDecoration(
color: Theme.of(Get.context!).scaffoldBackgroundColor,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(top: 10, bottom: 10),
width: 50,
height: 5,
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(10)),
),
Text("Select a Contact".tr,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: "Search name or number...".tr,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)),
contentPadding: const EdgeInsets.symmetric(vertical: 0),
),
onChanged: (value) {
filteredContacts.value = validContacts.where((c) {
final nameMatch = c.displayName
.toLowerCase()
.contains(value.toLowerCase());
final phoneMatch =
c.phones.first.number.contains(value);
return nameMatch || phoneMatch;
}).toList();
},
),
),
Expanded(
child: Obx(() => ListView.builder(
itemCount: filteredContacts.length,
itemBuilder: (context, index) {
Contact c = filteredContacts[index];
var firstPhone = c.phones.first;
String selectedPhone =
firstPhone.normalizedNumber.isNotEmpty
? firstPhone.normalizedNumber
: firstPhone.number;
String safeName = sanitizeText(c.displayName);
if (safeName.isEmpty) safeName = 'Unknown'.tr;
String safePhone = sanitizeText(selectedPhone);
String initial = safeName.isNotEmpty
? safeName[0].toUpperCase()
: '?';
return ListTile(
leading: CircleAvatar(
backgroundColor:
Colors.blueAccent.withOpacity(0.1),
child: Text(initial,
style: const TextStyle(
color: Colors.blueAccent)),
),
title: Text(safeName),
subtitle: Text(safePhone,
textDirection: TextDirection.ltr),
onTap: () {
selectPhone(selectedPhone);
},
);
},
)),
),
],
),
),
isScrollControlled: true,
);
} else {
log('Permission DENIED', name: 'ContactPicker');
mySnackeBarError('Contact permission is required to pick contacts'.tr);
}
} catch (e) {
mySnackeBarError('An error occurred while picking contacts: $e'.tr);
if (Get.isDialogOpen ?? false) Get.back();
log('CRITICAL ERROR: $e', name: 'ContactPicker');
mySnackeBarError('An error occurred while loading contacts: $e'.tr);
}
log('=== END: FETCHING CONTACTS ===', name: 'ContactPicker');
}
/// **FIX**: Added the missing 'selectPhone' method.
@@ -196,11 +348,11 @@ Download the Intaleq app now and enjoy your ride!
Get.back();
} else if (isAvailable) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if ((driverInvitationData[index]['isGiftToken']).toString() ==
'0') {
@@ -210,23 +362,23 @@ Download the Intaleq app now and enjoy your ride!
payload: {'id': (driverInvitationData[index]['id'])});
await Get.find<CaptainWalletController>().addDriverPayment(
'paymentMethod',
('50000'),
('500'),
'',
);
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor(
'paymentMethod',
(driverInvitationData[index]['driverInviterId']),
('50000'),
('500'),
);
NotificationCaptainController().addNotificationCaptain(
driverInvitationData[index]['driverInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have 50000".tr} ${'SYP'.tr}',
'${"You have 500".tr} ${'SYP'.tr}',
false);
NotificationController().showNotification(
"You have got a gift for invitation".tr,
'${"You have 50000".tr} ${'SYP'.tr}',
'${"You have 500".tr} ${'SYP'.tr}',
'tone1',
'');
} else {
@@ -265,21 +417,21 @@ Download the Intaleq app now and enjoy your ride!
Get.back();
} else if (isAvailable) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if (driverInvitationDataToPassengers[index]['isGiftToken']
.toString() ==
'0') {
Get.back();
await Get.find<CaptainWalletController>()
.addDriverWallet('paymentMethod', '20000', '20000');
.addDriverWallet('paymentMethod', '200', '200');
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor('paymentMethod',
driverInvitationData[index]['driverInviterId'], '20000');
driverInvitationData[index]['driverInviterId'], '200');
await CRUD().post(
link: AppLink.updatePassengerGift,
payload: {'id': driverInvitationDataToPassengers[index]['id']},
@@ -288,7 +440,7 @@ Download the Intaleq app now and enjoy your ride!
driverInvitationDataToPassengers[index]['passengerInviterId']
.toString(),
"You have got a gift for invitation".tr,
'${"You have 20000".tr} ${'SYP'.tr}',
'${"You have 200".tr} ${'SYP'.tr}',
false,
);
} else {

View File

@@ -13,19 +13,20 @@ import '../functions/add_error.dart';
import '../functions/encrypt_decrypt.dart';
class GoogleSignInHelper {
static final GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'profile',
],
);
static final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
// متغير ثابت لحفظ حالة المستخدم محلياً كبديل لخاصية currentUser المحذوفة
static GoogleSignInAccount? _cachedUser;
// Method to handle Google Sign-In
static Future<GoogleSignInAccount?> signIn() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
final GoogleSignInAccount? googleUser =
await _googleSignIn.authenticate();
if (googleUser != null) {
_cachedUser = googleUser; // حفظ الجلسة في الكاش المحلي
await _handleSignUp(googleUser);
if (box.read(BoxName.countryCode) == 'Egypt') {
Get.to(() => SmsSignupEgypt());
} else if (box.read(BoxName.countryCode) == 'Jordan') {
@@ -40,32 +41,23 @@ class GoogleSignInHelper {
Future<GoogleSignInAccount?> signInFromLogin() async {
try {
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
final GoogleSignInAccount? googleUser =
await _googleSignIn.authenticate();
if (googleUser != null) {
// Handle sign-up and store user information
_cachedUser = googleUser; // حفظ الجلسة في الكاش المحلي
await _handleSignUp(googleUser);
// Retrieve driverID and emailDriver with added validation
final driverID =
(box.read(BoxName.driverID)!.toString()) ?? 'Unknown ID';
(box.read(BoxName.driverID)?.toString()) ?? 'Unknown ID';
final emailDriver =
(box.read(BoxName.emailDriver)!.toString()) ?? 'Unknown Email';
(box.read(BoxName.emailDriver)?.toString()) ?? 'Unknown Email';
// Debug print statements
print('Driver ID: $driverID');
print('Driver Email: $emailDriver');
// Check if driverID is a valid numeric string
// if (driverID != 'Unknown ID' && int.tryParse(driverID) != null) {
await Get.find<LoginDriverController>()
.loginWithGoogleCredential(driverID, emailDriver);
// }
// else {
// print('Invalid driverID format: $driverID');
// Get.snackbar('Login Error', 'Invalid driver ID format.',
// backgroundColor: AppColor.redColor);
// }
}
return googleUser;
@@ -78,204 +70,29 @@ class GoogleSignInHelper {
}
static Future<void> _handleSignUp(GoogleSignInAccount user) async {
// Store driver information
box.write(BoxName.driverID,
(user.id) ?? 'Unknown ID'); // Ensure there's a fallback value
box.write(BoxName.driverID, (user.id) ?? 'Unknown ID');
box.write(BoxName.emailDriver, (user.email) ?? 'Unknown Email');
}
// Method to handle Google Sign-Out
static Future<void> signOut() async {
try {
await _googleSignIn.signOut();
} catch (error) {
// التعامل مع الخطأ بصمت إذا كانت جلسة جوجل فارغة مسبقاً
} finally {
_cachedUser = null; // مسح الكاش المحلي بشكل إلزامي
await _handleSignOut();
} catch (error) {}
}
}
static Future<void> _handleSignOut() async {
// Clear stored driver information
box.erase();
storage.deleteAll();
Get.offAll(OnBoardingPage());
// Perform any additional sign-out tasks or API calls here
// For example, you can notify your server about the user sign-out
}
// Method to get the current signed-in user
// استخدام الكاش المحلي بدلاً من استدعاء المكتبة
static GoogleSignInAccount? getCurrentUser() {
return _googleSignIn.currentUser;
return _cachedUser;
}
}
// import 'dart:async';
// import 'dart:async';
// import 'dart:convert';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:http/http.dart' as http;
// import 'package:sefer_driver/constant/box_name.dart';
// import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
// import 'package:sefer_driver/main.dart';
// import 'package:sefer_driver/views/auth/captin/ai_page.dart';
// import 'package:sefer_driver/views/auth/captin/cards/sms_signup.dart';
// import 'package:sefer_driver/views/home/on_boarding_page.dart';
// import 'package:sefer_driver/views/widgets/error_snakbar.dart';
// import 'package:url_launcher/url_launcher.dart';
// import '../functions/add_error.dart';
// /// Helper class to manage Google Sign-In via an external browser and polling.
// class GoogleSignInHelper {
// // URLs for your server endpoints
// static const String _startLoginUrl =
// 'https://api.tripz-egypt.com/tripz/auth/google_auth/login.php';
// static const String _checkStatusUrl =
// 'https://api.tripz-egypt.com/tripz/auth/google_auth/check_status.php';
// static Future<void> _initiateSignIn(
// Function(Map<String, dynamic> userData) onSignInSuccess) async {
// try {
// // Show a loading dialog to the user
// Get.dialog(
// const Center(
// child: Material(
// color: Colors.transparent,
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// CircularProgressIndicator(),
// SizedBox(height: 16),
// Text(
// "Waiting for browser sign-in...",
// style: TextStyle(color: Colors.white, fontSize: 16),
// ),
// ],
// ),
// ),
// ),
// barrierDismissible: false,
// );
// // 1. Get the auth URL and login token from the server
// final startResponse = await http.get(Uri.parse(_startLoginUrl));
// if (startResponse.statusCode != 200) {
// throw Exception('Failed to start login process.');
// }
// final startData = jsonDecode(startResponse.body);
// final String authUrl = startData['authUrl'];
// final String loginToken = startData['loginToken'];
// // 2. Launch the URL in an external browser
// if (!await launchUrl(Uri.parse(authUrl),
// mode: LaunchMode.externalApplication)) {
// throw Exception('Could not launch browser.');
// }
// // 3. Start polling the server for status
// await _startPolling(loginToken, onSignInSuccess);
// } catch (e) {
// addError(e.toString(), '_initiateSignIn');
// mySnackeBarError('Sign-in failed: ${e.toString()}');
// // Close the loading dialog on error
// if (Get.isDialogOpen ?? false) {
// Get.back();
// }
// }
// }
// static Future<void> _startPolling(String loginToken,
// Function(Map<String, dynamic> userData) onSignInSuccess) async {
// Timer? poller;
// const int maxAttempts = 30; // Poll for 60 seconds (30 attempts * 2s)
// int attempts = 0;
// poller = Timer.periodic(const Duration(seconds: 2), (timer) async {
// if (attempts >= maxAttempts) {
// timer.cancel();
// if (Get.isDialogOpen ?? false) Get.back();
// mySnackeBarError('Sign-in timed out. Please try again.');
// return;
// }
// attempts++;
// try {
// final statusResponse = await http.post(
// Uri.parse(_checkStatusUrl),
// headers: {'Content-Type': 'application/json'},
// body: jsonEncode({'loginToken': loginToken}),
// );
// if (statusResponse.statusCode == 200) {
// final statusData = jsonDecode(statusResponse.body);
// if (statusData['status'] == 'success') {
// timer.cancel();
// if (Get.isDialogOpen ?? false) Get.back();
// // Success!
// onSignInSuccess(statusData['userData']);
// }
// // If status is 'pending', do nothing and wait for the next poll.
// }
// } catch (e) {
// // Handle polling errors silently or log them
// debugPrint("Polling error: $e");
// }
// });
// }
// /// Triggers the sign-in process for a new user.
// static Future<void> signIn() async {
// await _initiateSignIn((userData) async {
// debugPrint('Sign-in success data: $userData');
// await _handleSignUp(userData);
// if (box.read(BoxName.countryCode) == 'Egypt') {
// Get.offAll(() => SmsSignupEgypt());
// } else if (box.read(BoxName.countryCode) == 'Jordan') {
// Get.offAll(() => AiPage());
// }
// });
// }
// /// Triggers the sign-in process for an existing user.
// static Future<void> signInFromLogin() async {
// await _initiateSignIn((userData) async {
// debugPrint('Sign-in from login success data: $userData');
// await _handleSignUp(userData);
// final driverID = userData['id']?.toString() ?? 'Unknown ID';
// final emailDriver = userData['email']?.toString() ?? 'Unknown Email';
// debugPrint('Driver ID from server: $driverID');
// debugPrint('Driver Email from server: $emailDriver');
// await Get.find<LoginDriverController>()
// .loginWithGoogleCredential(driverID, emailDriver);
// });
// }
// /// Stores user information received from the server.
// static Future<void> _handleSignUp(Map<String, dynamic> userData) async {
// box.write(BoxName.driverID, userData['id'] ?? 'Unknown ID');
// box.write(BoxName.emailDriver, userData['email'] ?? 'Unknown Email');
// }
// /// Clears local data.
// static Future<void> signOut() async {
// box.remove(BoxName.driverID);
// box.remove(BoxName.emailDriver);
// box.remove(BoxName.lang);
// box.remove(BoxName.nameDriver);
// box.remove(BoxName.passengerID);
// box.remove(BoxName.phoneDriver);
// box.remove(BoxName.tokenFCM);
// box.remove(BoxName.tokens);
// box.remove(BoxName.carPlate);
// box.remove(BoxName.lastNameDriver);
// box.remove(BoxName.agreeTerms);
// box.remove(BoxName.tokenDriver);
// box.remove(BoxName.countryCode);
// box.remove(BoxName.accountIdStripeConnect);
// box.remove(BoxName.phoneVerified);
// Get.offAll(() => OnBoardingPage());
// }
// }

View File

@@ -65,9 +65,9 @@ class NotificationController extends GetxController {
print('✅ Notifications initialized with Action Buttons Support');
await _flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
settings: initializationSettings,
);
// إنشاء قناة الأندرويد ذات الأهمية القصوى
@@ -183,10 +183,10 @@ class NotificationController extends GetxController {
// عرض الإشعار
await _flutterLocalNotificationsPlugin.show(
1001, // ID ثابت لاستبدال الإشعار القديم
title,
"$price - مسافة $formattedBigText", // نص مختصر يظهر في البار العلوي
details,
id: 1001, // ID ثابت لاستبدال الإشعار القديم
title: title,
body: "$price - مسافة $formattedBigText", // نص مختصر يظهر في البار العلوي
notificationDetails: details,
payload: jsonEncode({
'type': 'Order',
'data': myListString,
@@ -215,7 +215,7 @@ class NotificationController extends GetxController {
// أ) زر القبول
if (response.actionId == 'ACCEPT_ORDER') {
await _flutterLocalNotificationsPlugin.cancel(1001); // حذف الإشعار
await _flutterLocalNotificationsPlugin.cancel(id: 1001); // حذف الإشعار
_processAcceptOrder(listData);
}
@@ -227,7 +227,7 @@ class NotificationController extends GetxController {
// ج) زر الرفض
else if (response.actionId == 'REJECT_ORDER') {
await _flutterLocalNotificationsPlugin.cancel(1001); // حذف الإشعار
await _flutterLocalNotificationsPlugin.cancel(id: 1001); // حذف الإشعار
_processRejectOrder(listData);
}
@@ -428,7 +428,11 @@ class NotificationController extends GetxController {
NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
await _flutterLocalNotificationsPlugin.show(0, title, message, details,
await _flutterLocalNotificationsPlugin.show(
id: 0,
title: title,
body: message,
notificationDetails: details,
payload: jsonEncode({'title': title, 'data': payLoad}));
}
@@ -490,7 +494,7 @@ class NotificationController extends GetxController {
// ==============================================================================
Future<void> cancelOrderNotification() async {
// 1001 هو نفس الآيدي الذي استخدمناه عند عرض الإشعار
await _flutterLocalNotificationsPlugin.cancel(1001);
await _flutterLocalNotificationsPlugin.cancel(id: 1001);
print("🗑️ Order Notification Cancelled (Taken by another driver)");
}
@@ -522,14 +526,12 @@ class NotificationController extends GetxController {
}
await _flutterLocalNotificationsPlugin.zonedSchedule(
notificationId,
title,
message,
scheduledDate,
details,
androidScheduleMode: AndroidScheduleMode.exact,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
id: notificationId,
title: title,
body: message,
scheduledDate: scheduledDate,
notificationDetails: details,
androidScheduleMode: AndroidScheduleMode.exact, // أو exactAllowWhileIdle
matchDateTimeComponents: null,
);
}

View File

@@ -45,10 +45,10 @@ Future<bool> onStart(ServiceInstance service) async {
print("✅ Background Service: Socket Connected!");
if (service is AndroidServiceInstance) {
flutterLocalNotificationsPlugin.show(
notificationId,
'أنت متصل الآن',
'بانتظار الطلبات...',
const NotificationDetails(
id: notificationId,
title: 'أنت متصل الآن',
body: 'بانتظار الطلبات...',
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId,
'خدمة السائق',
@@ -107,10 +107,10 @@ Future<bool> onStart(ServiceInstance service) async {
if (service is AndroidServiceInstance) {
if (await service.isForegroundService()) {
flutterLocalNotificationsPlugin.show(
notificationId,
'خدمة السائق نشطة',
'بانتظار الطلبات...',
const NotificationDetails(
id: notificationId,
title: 'خدمة السائق نشطة',
body: 'بانتظار الطلبات...',
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId,
'خدمة السائق',

View File

@@ -2,10 +2,8 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:path/path.dart';
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
import 'package:sefer_driver/controller/functions/network/net_guard.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
@@ -16,8 +14,6 @@ import 'package:sefer_driver/env/env.dart';
import 'package:sefer_driver/print.dart';
import '../../constant/api_key.dart';
import '../../constant/char_map.dart';
import '../../constant/info.dart';
import '../../views/widgets/error_snakbar.dart';
import 'gemeni.dart';
import 'upload_image.dart';

View File

@@ -51,7 +51,7 @@ class DeviceController {
final deviceInfo = DeviceInfo(
manufacturer: androidInfo.manufacturer,
model: androidInfo.model,
deviceId: androidInfo.serialNumber,
deviceId: androidInfo.id,
osVersion: androidInfo.version.release,
platform: 'Android',
deviceName: androidInfo.device,

View File

@@ -16,6 +16,7 @@ import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../../print.dart';
import '../home/captin/home_captain_controller.dart';
import '../home/captin/map_driver_controller.dart';
import '../home/payment/captain_wallet_controller.dart';
@@ -74,7 +75,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
@override
Future<void> onInit() async {
super.onInit();
print('🚀 LocationController Starting...');
Log.print('🚀 LocationController Starting...');
// 1. Register Lifecycle Observer
WidgetsBinding.instance.addObserver(this);
@@ -83,7 +84,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// مراقب الحالة (Status Watcher)
box.listenKey(BoxName.statusDriverLocation, (value) {
if (value == 'blocked') {
print("⛔ Driver is Blocked: Force Stopping Location Updates.");
Log.print("⛔ Driver is Blocked: Force Stopping Location Updates.");
stopLocationUpdates();
if (socket != null && socket!.connected) {
socket!.emit('update_location', {
@@ -113,7 +114,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
await startLocationUpdates();
}
print('✅ LocationController Initialized.');
Log.print('✅ LocationController Initialized.');
}
@override
@@ -133,7 +134,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
print("📱 Lifecycle: App is in FOREGROUND");
Log.print("📱 Lifecycle: App is in FOREGROUND");
box.write(BoxName.isAppInForeground, true);
// إيقاف خدمة الخلفية لأننا في الواجهة الآن
@@ -141,7 +142,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// التأكد من أن السوكيت متصل، وإذا لا، نعيد الاتصال فوراً
if (socket == null || !socket!.connected) {
print("🔄 Socket disconnected in background. Reconnecting now...");
Log.print("🔄 Socket disconnected in background. Reconnecting now...");
initSocket();
} else {
// إذا كان متصلاً، ننعش المستمعين فقط للتأكد
@@ -149,7 +150,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
}
} else if (state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) {
print("📱 Lifecycle: App is in BACKGROUND");
Log.print("📱 Lifecycle: App is in BACKGROUND");
box.write(BoxName.isAppInForeground, false);
// تشغيل خدمة الخلفية لضمان بقاء التطبيق حياً
@@ -186,14 +187,15 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// 1. إذا كان السوكيت موجوداً، فقط تأكد من اتصاله
if (socket != null) {
if (!socket!.connected) {
print("🟡 Socket exists but disconnected. Reconnecting...");
Log.print("🟡 Socket exists but disconnected. Reconnecting...");
socket!.connect();
}
_setupSocketListeners(); // تحديث المستمعين
return;
}
print("🟡 [LocationController] Creating NEW Socket for Driver: $driverId");
Log.print(
"🟡 [LocationController] Creating NEW Socket for Driver: $driverId");
// 2. إنشاء الاتصال
socket = IO.io(
@@ -222,20 +224,27 @@ class LocationController extends GetxController with WidgetsBindingObserver {
socket!.off('ride_cancelled');
socket!.onConnect((_) {
print('✅ Socket Connected! ID: ${socket?.id}');
Log.print('✅ Socket Connected! ID: ${socket?.id}');
isSocketConnected = true;
_startHeartbeat();
});
socket!.onDisconnect((_) {
print('❌ Socket Disconnected');
Log.print('❌ Socket Disconnected');
isSocketConnected = false;
_stopHeartbeat();
});
socket!.onConnectError((err) {
Log.print('❌ Socket Connect Error: $err');
});
socket!.onError((err) {
Log.print('❌ Socket General Error: $err');
});
// 🔥 الاستماع للطلبات الجديدة
socket!.on('new_ride_request', (data) {
print("🔔 Socket: New Ride Request Arrived!");
Log.print("🔔 Socket: New Ride Request Arrived!");
// نستخدم Future.microtask لضمان عدم حظر الـ UI Thread
Future.microtask(() {
@@ -259,7 +268,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
handleIncomingOrder(convertedMap, "Socket");
}
} catch (e) {
print("❌ Error processing socket data: $e");
Log.print("❌ Error processing socket data: $e");
}
}
});
@@ -267,7 +276,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// 🔥 الاستماع للإلغاء
socket!.on('cancel_ride', (data) {
print("🚫 Socket: Ride Cancelled Event Received");
Log.print("🚫 Socket: Ride Cancelled Event Received");
String reason = data['reason'] ?? 'No reason provided';
if (Get.isRegistered<MapDriverController>()) {
Get.find<MapDriverController>()
@@ -280,13 +289,13 @@ class LocationController extends GetxController with WidgetsBindingObserver {
Future<void> handleIncomingOrder(
Map<String, dynamic> rideData, String source) async {
print("📦 Socket Order Received from ($source)");
Log.print("📦 Socket Order Received from ($source)");
// 🔴 1. التحقق من حالة التطبيق قبل أي شيء 🔴
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
if (!isAppInForeground) {
print(
Log.print(
"🛑 التطبيق في الخلفية. السوكيت سيتجاهل التوجيه ويترك المهمة للـ Overlay.");
return; // 👈 هذا السطر يمنع السوكيت من إكمال العمل وفتح الصفحة
}
@@ -294,7 +303,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
try {
// 2. التحقق من صحة البيانات
if (rideData.isEmpty || !rideData.containsKey('16')) {
print("❌ Socket Error: Invalid Ride Data.");
Log.print("❌ Socket Error: Invalid Ride Data.");
return;
}
@@ -311,25 +320,26 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// 4. إغلاق النافذة (إن وجدت بالخطأ) والتنقل
try {
if (await TripOverlayPlugin.isOverlayActive()) {
print("📲 Closing Overlay because App took control via Socket");
Log.print("📲 Closing Overlay because App took control via Socket");
await TripOverlayPlugin.hideOverlay();
}
} catch (e) {
print("Overlay check error: $e");
Log.print("Overlay check error: $e");
}
if (Get.currentRoute != '/OrderRequestPage') {
print("🚀 Socket: Navigating to OrderRequestPage...");
Log.print("🚀 Socket: Navigating to OrderRequestPage...");
Get.toNamed('/OrderRequestPage', arguments: {
'myListString': jsonEncode(driverList),
'DriverList': driverList,
'body': 'New Trip Request via Socket ⚡'
});
} else {
print("⚠️ User is already on OrderRequestPage. Skipping navigation.");
Log.print(
"⚠️ User is already on OrderRequestPage. Skipping navigation.");
}
} catch (e) {
print("❌ Socket Navigation Error: $e");
Log.print("❌ Socket Navigation Error: $e");
}
}
@@ -374,8 +384,8 @@ class LocationController extends GetxController with WidgetsBindingObserver {
}
}
// Debug print to verify
// print('🚀 Emitting Location: $payload');
// DebugLog.print to verify
//Log.print('🚀 Emitting Location: $payload');
if (socket != null && socket!.connected) {
socket!.emit('update_location', payload);
@@ -437,11 +447,11 @@ class LocationController extends GetxController with WidgetsBindingObserver {
update();
emitLocationToSocket(pos, heading, speed);
await _saveBehaviorIfMoved(pos, now, currentSpeed: speed);
}, onError: (e) => print('❌ Location Stream Error: $e'));
}, onError: (e) => Log.print('❌ Location Stream Error: $e'));
}
Future<void> stopLocationUpdates() async {
print("🛑 Stopping Location Updates...");
Log.print("🛑 Stopping Location Updates...");
_locSub?.cancel();
_locSub = null;
@@ -525,7 +535,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
payload: {'driver_id': driverId, 'batch_data': jsonEncode(batch)},
);
} catch (e) {
print('❌ Failed to upload batch: $e');
Log.print('❌ Failed to upload batch: $e');
}
}
@@ -559,7 +569,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
}, TableName.behavior);
_lastSqlLoc = pos;
} catch (e) {
print('SQLite Error: $e');
Log.print('SQLite Error: $e');
}
}
@@ -594,7 +604,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
interval: 1000,
distanceFilter: 10);
} catch (e) {
print("Warning: $e");
Log.print("Warning: $e");
}
}
}
@@ -633,7 +643,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
return await location.getLocation();
}
} catch (e) {
print('❌ FAILED to get single location: $e');
Log.print('❌ FAILED to get single location: $e');
}
return null;
}

View File

@@ -28,7 +28,7 @@ class NetGuard {
// اختباري خفيف عبر TCP (80/443) — 400ms timeout
final port = mustReach.scheme == 'http' ? 80 : 443;
final socket = await Socket.connect(host, port,
timeout: const Duration(milliseconds: 400));
timeout: const Duration(seconds: 1));
socket.destroy();
} catch (_) {
return false;

View File

@@ -433,14 +433,58 @@ class HomeCaptainController extends GetxController {
return totalDuration;
}
void startPeriodicExecution() {
Timer.periodic(const Duration(seconds: 30), (timer) async {
await getCaptainDurationOnToday();
Timer? _localDurationTimer;
RxString totalDurationDisplay = "00:00:00".obs; // لعرض الوقت في الواجهة
Duration _currentDuration = Duration.zero; // لتخزين الوقت ككائن Duration
void startPeriodicExecution() async {
await getCaptainDurationOnToday();
String? initialDurationStr = totalDurationToday;
if (initialDurationStr != null) {
// تحويل النص (01:20:30) إلى كائن Duration
List<String> parts = initialDurationStr.split(':');
_currentDuration = Duration(
hours: int.parse(parts[0]),
minutes: int.parse(parts[1]),
seconds: int.parse(parts[2]),
);
// بدء العداد المحلي
_startLocalClock();
}
// Timer.periodic(const Duration(seconds: 30), (timer) async {
// await getCaptainDurationOnToday();
// });
}
void _startLocalClock() {
_localDurationTimer?.cancel();
_localDurationTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// زيادة ثانية واحدة محلياً
_currentDuration += const Duration(seconds: 1);
// تحديث النص المعروض في الواجهة (Formatting)
totalDurationDisplay.value = _formatDuration(_currentDuration);
// اختيارياً: كل 5 دقائق فقط، قم بتحديث القيمة من السيرفر للتأكد من المزامنة
if (timer.tick % 300 == 0) {
getCaptainDurationOnToday();
}
});
}
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String hours = twoDigits(duration.inHours);
String minutes = twoDigits(duration.inMinutes.remainder(60));
String seconds = twoDigits(duration.inSeconds.remainder(60));
return "$hours:$minutes:$seconds";
}
void stopTimer() {
timer?.cancel();
_localDurationTimer?.cancel();
}
getlocation() async {

View File

@@ -22,11 +22,11 @@ class PaymobPayout extends GetxController {
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
// var dec = await CRUD()
// .postWallet(link: AppLink.paymobPayoutDriverWallet, payload: {
@@ -116,11 +116,11 @@ class PaymobPayout extends GetxController {
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
var body = {
"issuer": "bank_card",
@@ -187,11 +187,11 @@ class PaymobPayout extends GetxController {
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
await payToDriverWallet(amount, issuer, msisdn);
} else {
@@ -213,11 +213,11 @@ class PaymobPayout extends GetxController {
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
await payToDriverBankAccount(amount, bankCardNumber, bankCode);
} else {

View File

@@ -438,11 +438,11 @@ class PaymentController extends GetxController {
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
final PaymobResponse? response = await PaymobPayment.instance.pay(
context: context,
@@ -589,11 +589,12 @@ class PaymentController extends GetxController {
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
// )
);
if (didAuthenticate) {
final PaymobResponseWallet? response =
await PaymobPaymentWallet.instance.pay(

View File

@@ -32,10 +32,10 @@ class _PayoutScreenState extends State<PayoutScreen> {
// 1. طلب المصادقة البيومترية
bool didAuthenticate = await _localAuth.authenticate(
localizedReason: 'استخدم بصمة الإصبع لتأكيد عملية السحب',
options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
),
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
// ),
);
if (didAuthenticate && mounted) {

View File

@@ -310,12 +310,12 @@ class WalletCaptainRefactored extends StatelessWidget {
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason:
'Use Touch ID or Face ID to confirm payment'.tr,
options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
localizedReason:
'Use Touch ID or Face ID to confirm payment'.tr,
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if (double.parse(controller.amountFromBudgetController.text) <
double.parse(controller.totalAmountVisa)) {