feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل

قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة
ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ.

الخريطة:
  backend · payment_server · loction_server · ride_server ·
  passenger_server · docker · dashboard · stress_test  → الجذر
  siro_rider  → apps/rider          siro_driver  → apps/driver
  siro_admin  → dashboards/admin    siro_service → dashboards/service
  android_bot → apps/android_bot    socialBot    → apps/socialBot

نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب)
لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً:
كل ما يلي يصير فرقاً مقروءاً مقابل المصدر.

لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز،
سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh
(ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في
مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و
dashboards/transit-web).

⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة:
1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر):
   كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner.
2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist)
   يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً.
3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع →
   يجب ضمّ الحزم داخله أسوة بـ apps/rider.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:14:13 +03:00
co-authored by Claude Opus 5
parent 9909d9b4c1
commit 4d8414c96b
3198 changed files with 766859 additions and 0 deletions
@@ -0,0 +1,60 @@
import 'dart:convert';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart';
import '../../../views/home/Captin/history/history_details_page.dart';
import '../../functions/crud.dart';
class HistoryCaptainController extends GetxController {
bool isloading = false;
Map historyData = {};
Map historyDetailsData = {};
late String orderID;
getOrderId(String orderId) {
orderID = orderId;
update();
}
getHistory() async {
isloading = true;
var res = await CRUD().get(
link: AppLink.getDriverOrder,
payload: {'driver_id': box.read(BoxName.driverID)});
if (res != 'failure') {
historyData = jsonDecode(res);
isloading = false;
update();
} else {
Get.defaultDialog(
title: 'No ride yet'.tr,
middleText: '',
barrierDismissible: false,
confirm: MyElevatedButton(
title: 'Back'.tr,
onPressed: () {
Get.back();
Get.back();
}));
}
}
getHistoryDetails(String orderId) async {
isloading = true;
var res = await CRUD()
.get(link: AppLink.getRideOrderID, payload: {'id': (orderId)});
historyDetailsData = jsonDecode(res);
isloading = false;
update();
Get.to(() => HistoryDetailsPage());
}
@override
void onInit() {
getHistory();
super.onInit();
}
}
@@ -0,0 +1,570 @@
import 'package:siro_driver/constant/currency.dart';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_contacts/contact.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/controller/home/payment/captain_wallet_controller.dart';
import 'package:siro_driver/print.dart';
import 'package:siro_driver/controller/functions/country_logic.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import 'package:share_plus/share_plus.dart';
import '../../firebase/local_notification.dart';
import '../../functions/launch.dart';
import '../../notification/notification_captain_controller.dart';
class InviteController extends GetxController {
final TextEditingController invitePhoneController = TextEditingController();
List driverInvitationData = [];
List driverInvitationDataToPassengers = [];
String? couponCode;
String? driverCouponCode;
// **FIX**: Added the missing 'contacts' and 'contactMaps' definitions.
List<Contact> contacts = [];
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
int selectedTab = 0;
PassengerStats passengerStats = PassengerStats();
void updateSelectedTab(int index) {
selectedTab = index;
update();
}
Future<void> shareDriverCode() async {
if (driverCouponCode != null) {
final String shareText =
'''Join Siro as a driver using my referral code!
Use code: $driverCouponCode
Download the Siro Driver app now and earn rewards:
https://siromove.com/invite.php?code=$driverCouponCode&app=driver
''';
await Share.share(shareText);
}
}
Future<void> sharePassengerCode() async {
if (couponCode != null) {
final String shareText = '''Get a discount on your first Siro ride!
Use my referral code: $couponCode
Download the Siro app now and enjoy your ride:
https://siromove.com/invite.php?code=$couponCode&app=rider
''';
await Share.share(shareText);
}
}
@override
void onInit() {
super.onInit();
// **MODIFIED**: Sync contacts automatically on controller initialization.
syncContactsToServerOnce();
// fetchDriverStats();
}
// --- NEW LOGIC: ONE-TIME CONTACTS SYNC ---
/// **NEW**: Syncs all phone contacts to the server, but only runs once per user.
Future<void> syncContactsToServerOnce() async {
final String syncFlagKey = 'contactsSynced_${box.read(BoxName.driverID)}';
// 1. Check if contacts have already been synced for this user.
if (box.read(syncFlagKey) == true) {
print("Contacts have already been synced for this user.");
return;
}
try {
// 2. Request permission and fetch all contacts.
if (await FlutterContacts.requestPermission(readonly: true)) {
// mySnackbarSuccess('Starting contacts sync in background...'.tr);
final List<Contact> allContacts =
await FlutterContacts.getContacts(withProperties: true);
// **FIX**: Assign fetched contacts to the class variable.
contacts = allContacts;
contactMaps.value = contacts.map((contact) {
return {
'name': contact.displayName,
'phones':
contact.phones.map((phone) => phone.normalizedNumber).toList(),
'emails': contact.emails.map((email) => email.address).toList(),
};
}).toList();
update();
// 3. Loop through contacts and save them to the server.
for (var contact in allContacts) {
if (contact.phones.isNotEmpty) {
// Use the normalized phone number for consistency.
var phone = contact.phones.first.normalizedNumber;
if (phone.isNotEmpty) {
CRUD().post(link: AppLink.savePhonesSyria, payload: {
"driverId": box.read(BoxName.driverID), // Associate with driver
"name": contact.displayName ?? 'No Name',
"phone": phone,
});
}
}
}
// 4. After a successful sync, set the flag to prevent future syncs.
await box.write(syncFlagKey, true);
// mySnackbarSuccess('Contacts sync completed successfully!'.tr);
}
} catch (e) {
// mySnackbarError('An error occurred during contact sync: $e'.tr);
}
}
// --- NEW LOGIC: NATIVE CONTACT PICKER ---
/// **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)) {
// عرض شاشة تحميل بسيطة ريثما يتم جلب الأسماء
Get.dialog(const Center(child: CircularProgressIndicator()),
barrierDismissible: false);
log('Permission granted. Calling FlutterContacts.getContacts()...',
name: 'ContactPicker');
// جلب جميع جهات الاتصال إجبارياً من الصفر مع خصائصها
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) {
mySnackbarError('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');
mySnackbarError('Contact permission is required to pick contacts'.tr);
}
} catch (e) {
if (Get.isDialogOpen ?? false) Get.back();
log('CRITICAL ERROR: $e', name: 'ContactPicker');
mySnackbarError('An error occurred while loading contacts: $e'.tr);
}
log('=== END: FETCHING CONTACTS ===', name: 'ContactPicker');
}
/// **FIX**: Added the missing 'selectPhone' method.
void selectPhone(String phone) {
// Format the selected phone number and update the text field.
invitePhoneController.text = CountryLogic.formatCurrentCountryPhone(phone);
update();
Get.back(); // Close the contacts dialog after selection.
}
void fetchDriverStats() async {
try {
var response = await CRUD().get(link: AppLink.getInviteDriver, payload: {
"driverId": box.read(BoxName.driverID),
});
if (response != 'failure') {
var data = jsonDecode(response);
driverInvitationData = data['message'];
update();
}
} catch (e) {
// Handle error gracefully
}
}
void fetchDriverStatsPassengers() async {
try {
var response = await CRUD()
.get(link: AppLink.getDriverInvitationToPassengers, payload: {
"driverId": box.read(BoxName.driverID),
});
if (response != 'failure') {
var data = jsonDecode(response);
driverInvitationDataToPassengers = data['message'];
update();
}
} catch (e) {
// Handle error gracefully
}
}
void onSelectDriverInvitation(int index) async {
MyDialog().getDialog(
int.parse((driverInvitationData[index]['countOfInvitDriver'])) < 100
? '${'When'.tr} ${(driverInvitationData[index]['invitorName'])} ${"complete, you can claim your gift".tr} '
: 'You deserve the gift'.tr,
'${(driverInvitationData[index]['invitorName'])} ${(driverInvitationData[index]['countOfInvitDriver'])} / 100 ${'Trip'.tr}',
() async {
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (int.parse((driverInvitationData[index]['countOfInvitDriver'])) <
100) {
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,
);
if (didAuthenticate) {
if ((driverInvitationData[index]['isGiftToken']).toString() ==
'0') {
Get.back();
// Server-side claim validation
var response = await CRUD().post(
link: AppLink.claimInviteReward,
payload: {
'invite_id': (driverInvitationData[index]['id']).toString(),
'driver_id': box.read(BoxName.driverID).toString(),
'country_code': box.read(BoxName.countryCode).toString(),
}
);
if (response != 'failure') {
var data = jsonDecode(response);
if (data['status'] == 'success') {
NotificationController().showNotification(
"You have got a gift for invitation".tr,
'${"You have 500".tr} ${CurrencyHelper.currency}',
'tone1',
'');
// refresh stats
fetchDriverStats();
} else {
mySnackbarError(data['message'] ?? 'Claim failed'.tr);
}
}
} else {
Get.back();
MyDialog().getDialog("You have got a gift".tr,
"Share the app with another new driver".tr, () => Get.back());
}
} else {
MyDialog()
.getDialog('Authentication failed'.tr, '', () => Get.back());
}
} else {
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
);
}
void onSelectPassengerInvitation(int index) async {
bool isAvailable = await LocalAuthentication().isDeviceSupported();
MyDialog().getDialog(
int.parse(driverInvitationDataToPassengers[index]['countOfInvitDriver']
.toString()) <
3
? '${'When'.tr} ${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${"complete, you can claim your gift".tr} '
: 'You deserve the gift'.tr,
'${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${driverInvitationDataToPassengers[index]['countOfInvitDriver']} / 3 ${'Trip'.tr}',
() async {
if (int.parse(driverInvitationDataToPassengers[index]
['countOfInvitDriver']
.toString()) <
3) {
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,
);
if (didAuthenticate) {
if (driverInvitationDataToPassengers[index]['isGiftToken']
.toString() ==
'0') {
Get.back();
await Get.find<CaptainWalletController>()
.addDriverWallet('paymentMethod', '200', '200');
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor('paymentMethod',
driverInvitationData[index]['driverInviterId'], '200');
await CRUD().post(
link: AppLink.updatePassengerGift,
payload: {'id': driverInvitationDataToPassengers[index]['id']},
);
NotificationCaptainController().addNotificationCaptain(
driverInvitationDataToPassengers[index]['passengerInviterId']
.toString(),
"You have got a gift for invitation".tr,
'${"You have 200".tr} ${CurrencyHelper.currency}',
false,
);
} else {
Get.back();
MyDialog().getDialog(
"You have got a gift".tr,
"Share the app with another new passenger".tr,
() => Get.back(),
);
}
} else {
MyDialog()
.getDialog('Authentication failed'.tr, '', () => Get.back());
}
} else {
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
);
}
// removed normalizeSyrianPhone
/// Sends an invitation to a potential new driver.
void sendInvite() async {
if (invitePhoneController.text.isEmpty) {
mySnackbarError('Please enter a phone number'.tr);
return;
}
String formattedPhoneNumber =
CountryLogic.formatCurrentCountryPhone(invitePhoneController.text);
if (formattedPhoneNumber.length != 12) {
mySnackbarError('Please enter a correct phone'.tr);
return;
}
var response = await CRUD().post(link: AppLink.addInviteDriver, payload: {
"driverId": box.read(BoxName.driverID),
"inviterDriverPhone": formattedPhoneNumber,
});
if (response != 'failure' && response is Map) {
var d = response;
mySnackbarSuccess('Invite sent successfully'.tr);
String inviteCode = d['message']['inviteCode'].toString();
String message = '${'*Siro DRIVER CODE*'.tr}\n\n'
'${"Use this code in registration".tr}\n'
'${"To get a gift for both".tr}\n\n'
'${"The period of this code is 24 hours".tr}\n\n'
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
'${"Code:".tr} *${inviteCode}*\n\n'
'${"Quick Invite Link:".tr}\n'
'${AppLink.inviteRedirectUrl}?code=$inviteCode&app=driver\n\n'
'${"💡 Note: If the link above is not clickable, please save this contact or reply to this message to activate all links.".tr}';
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
} else {
mySnackbarError("Invite code already used".tr);
}
}
/// Sends an invitation to a potential new passenger.
void sendInviteToPassenger() async {
if (invitePhoneController.text.isEmpty) {
mySnackbarError('Please enter a phone number'.tr);
return;
}
String formattedPhoneNumber = CountryLogic.formatCurrentCountryPhone(invitePhoneController.text);
if (formattedPhoneNumber.length < 12) {
// +963 + 9 digits = 12+
mySnackbarError('Please enter a correct phone'.tr);
return;
}
var response = await CRUD().post(
link: AppLink.addInvitationPassenger,
payload: {
"driverId": box.read(BoxName.driverID),
"inviterPassengerPhone": formattedPhoneNumber,
},
);
if (response != 'failure' && response is Map) {
var d = response;
mySnackbarSuccess('Invite sent successfully'.tr);
String inviteCode = d['message']['inviteCode'].toString();
String message = '${'*Siro APP CODE*'.tr}\n\n'
'${"Use this code in registration".tr}\n\n'
'${"To get a gift for both".tr}\n\n'
'${"The period of this code is 24 hours".tr}\n\n'
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
'${"Code:".tr} *${inviteCode}*\n\n'
'${"Quick Invite Link:".tr}\n'
'${AppLink.inviteRedirectUrl}?code=$inviteCode&app=rider\n\n'
'${"💡 Note: If the link above is not clickable, please save this contact or reply to this message to activate all links.".tr}';
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
} else {
mySnackbarError("Invite code already used".tr);
}
}
}
class PassengerStats {
final int totalInvites;
final int activeUsers;
final double totalEarnings;
PassengerStats({
this.totalInvites = 0,
this.activeUsers = 0,
this.totalEarnings = 0.0,
});
}
@@ -0,0 +1,718 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:permission_handler/permission_handler.dart';
import 'package:siro_driver/views/auth/syria/registration_view.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/home/Captin/home_captain/home_captin.dart';
import 'package:location/location.dart';
import '../../../constant/api_key.dart';
import '../../../constant/info.dart';
import '../../../print.dart';
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/country_logic.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
import '../../functions/secure_storage.dart';
import '../../functions/security_checks.dart';
class LoginDriverController extends GetxController {
final formKey = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController passwordController2 = TextEditingController();
bool isAgreeTerms = false;
bool isGoogleDashOpen = false;
bool isGoogleLogin = false;
bool isloading = false;
late int isTest = 1;
final location = Location();
void changeAgreeTerm() {
isAgreeTerms = !isAgreeTerms;
update();
}
bool isPasswordHidden = true;
void togglePasswordVisibility() {
isPasswordHidden = !isPasswordHidden;
update([
'passwordVisibility'
]); // Use a unique ID to only update the password field
}
void changeGoogleDashOpen() {
isGoogleDashOpen = !isGoogleDashOpen;
update();
}
@override
void onInit() async {
// كشف الدولة تلقائياً عبر الموقع الجغرافي
await CountryLogic.initializeCountry();
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
// await getAppTester();
await getJWT();
super.onInit();
}
// getAppTester() async {
// var res = await CRUD().get(
// link: AppLink.getTesterApp,
// payload: {'appPlatform': AppInformation.appName});
// // Log.print('res: ${res}');
// if (res != 'failure') {
// var d = jsonDecode(res);
// isTest = d['message'][0]['isTest'];
// // Log.print('isTest: ${isTest}');
// box.write(BoxName.isTest, isTest);
// // Log.print('isTest: ${box.read(BoxName.isTest)}');
// update();
// } else {
// isTest = 0;
// box.write(BoxName.isTest, isTest);
// update();
// return false;
// }
// }
// updateAppTester(String appPlatform) async {
// await CRUD().post(
// link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
// }
isPhoneVerified() async {
// If the phone is already verified locally and we have a driver ID, skip the API check
// This prevents asking for OTP again if the user restarts the app during registration.
if (box.read(BoxName.phoneVerified) == '1' &&
box.read(BoxName.driverID) != null) {
Get.offAll(() => RegistrationView());
return;
}
var res = await CRUD().post(
link: AppLink.isPhoneVerified,
payload: {'phone_number': box.read(BoxName.phoneDriver)});
if (res != 'failure') {
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
// isloading = false;
// update();
} else {
Get.offAll(() => PhoneNumberScreen());
}
}
void saveAgreementTerms() {
box.write(BoxName.agreeTerms, 'agreed');
update();
}
void saveCountryCode(String countryCode) {
box.write(BoxName.countryCode, countryCode);
update();
}
String shortHash(String password) {
var bytes = utf8.encode(password);
var digest = sha256.convert(bytes);
return base64UrlEncode(digest.bytes);
}
var dev = '';
Future<String>? _walletJwtFuture;
getJwtWallet() async {
if (_walletJwtFuture != null) {
Log.print('⏳ getJwtWallet: Request already in progress, reusing future.');
return _walletJwtFuture!;
}
_walletJwtFuture = _getJwtWalletInternal();
try {
return await _walletJwtFuture!;
} finally {
_walletJwtFuture = null;
}
}
Future<String> _getJwtWalletInternal() async {
if (box.read(BoxName.security_check).toString() != 'passed') {
Log.print('Security check failed');
return '';
}
// 1. Check GetStorage first to avoid redundant API calls
String? cachedJwt = box.read(BoxName.walletJwt)?.toString();
if (cachedJwt != null && cachedJwt.isNotEmpty) {
bool isTokenValid = false;
try {
final parts = cachedJwt.split('.');
if (parts.length == 3) {
var payload = parts[1];
switch (payload.length % 4) {
case 2:
payload += '==';
break;
case 3:
payload += '=';
break;
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
final exp = decoded['exp'];
if (exp != null) {
// Check if token has at least 10 seconds remaining
isTokenValid =
DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 10000);
}
}
} catch (_) {}
if (isTokenValid) {
Log.print('🔑 Valid Wallet JWT found in storage. Skipping generation.');
return cachedJwt;
}
}
Log.print('Security check passed');
dev = Platform.isAndroid ? 'android' : 'ios';
var fingerPrint = box.read(BoxName.deviceFingerprint)?.toString() ??
await DeviceHelper.getDeviceFingerprint();
var payload = {
'id': box.read(BoxName.driverID),
'password': AK.passnpassenger,
'aud': '${AK.allowedWallet}$dev',
'fingerPrint': fingerPrint
};
var response1 = await http.post(
Uri.parse(AppLink.loginJwtWalletDriver),
body: payload,
);
Log.print('response.request: ${response1.request}');
Log.print('response.body: ${response1.body}');
var decoded = jsonDecode(response1.body);
var jwt = decoded['message'] is Map && decoded['message']['jwt'] != null
? decoded['message']['jwt']
: decoded['jwt'];
var hmac = decoded['message'] is Map && decoded['message']['hmac'] != null
? decoded['message']['hmac']
: decoded['hmac'];
Log.print('payment["jwt"]: $jwt');
if (jwt != null) {
await box.write(BoxName.walletJwt, jwt.toString());
await box.write(BoxName.hmac, hmac);
return jwt.toString();
}
return '';
}
getJWT() async {
await EncryptionHelper.initialize();
// 1. Check secure storage first to avoid redundant API calls
String? secureJwt = await storage.read(key: BoxName.jwt);
if (secureJwt != null && secureJwt.isNotEmpty) {
bool isTokenValid = false;
try {
final parts = secureJwt.split('.');
if (parts.length == 3) {
String payload = parts[1];
switch (payload.length % 4) {
case 2:
payload += '==';
break;
case 3:
payload += '=';
break;
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
final exp = decoded['exp'];
if (exp != null) {
// Check if token is valid with a 30-second buffer
isTokenValid =
DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
}
}
} catch (_) {}
if (isTokenValid) {
Log.print('🔑 Valid JWT found in secure storage. Skipping generation.');
return;
}
}
dev = Platform.isAndroid ? 'android' : 'ios';
Log.print(
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
var payload = {
'id': box.read(BoxName.driverID) ?? AK.newId,
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': box.read(BoxName.deviceFingerprint) ??
await DeviceHelper.getDeviceFingerprint(),
};
// Log.print('payload: ${payload}');
var response0 = await http.post(
Uri.parse(AppLink.loginFirstTimeDriver),
body: payload,
);
Log.print('response0: ${response0.body}');
Log.print('request: ${response0.request}');
if (response0.statusCode == 200) {
final decodedResponse1 = jsonDecode(response0.body);
Log.print('decodedResponse1: ${decodedResponse1}');
String? jwt;
if (decodedResponse1['message'] is Map &&
decodedResponse1['message']['jwt'] != null) {
jwt = decodedResponse1['message']['jwt'];
} else {
jwt = decodedResponse1['jwt'];
}
if (jwt != null) {
// box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: jwt);
}
// ✅ بعد التأكد أن كل المفاتيح موجودة
await EncryptionHelper.initialize();
// await AppInitializer().getKey();
} else {}
} else {
await EncryptionHelper.initialize();
var payload = {
'id': box.read(BoxName.driverID),
'password': box.read(BoxName.emailDriver),
'aud': '${AK.allowed}$dev',
'fingerPrint': box.read(BoxName.deviceFingerprint) ??
await DeviceHelper.getDeviceFingerprint(),
};
// print(payload);
var response1 = await http.post(
Uri.parse(AppLink.loginJwtDriver),
body: payload,
);
Log.print('response1.request: ${response1.request}');
Log.print('response1.body: ${response1.body}');
if (response1.statusCode == 200) {
final decodedResponse1 = jsonDecode(response1.body);
// Log.print('decodedResponse1: ${decodedResponse1}');
String? jwt;
if (decodedResponse1['message'] is Map &&
decodedResponse1['message']['jwt'] != null) {
jwt = decodedResponse1['message']['jwt'];
} else {
jwt = decodedResponse1['jwt'];
}
if (jwt != null) {
// await box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: jwt);
}
// await AppInitializer().getKey();
}
}
}
Future<void> getLocationPermission() async {
var status = await Permission.locationAlways.status;
if (!status.isGranted) {
await Permission.locationAlways.request();
}
update();
}
bool isInviteDriverFound = false;
Future updateInvitationCodeFromRegister() async {
var res = await CRUD().post(
link: AppLink.updateDriverInvitationDirectly,
payload: {
"inviterDriverPhone": box.read(BoxName.phoneDriver).toString(),
},
);
Log.print('invite: ${res}');
// حماية من النوع — res قد يكون String ('failure'/'token_expired') بدل Map
if (res is! Map) return;
if (res['status'] != 'failure') {
isInviteDriverFound = true;
update();
box.write(BoxName.isInstall, '1');
NotificationController().showNotification(
"Code approved".tr, "Code approved".tr, 'tone2', '');
try {
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,
tone: 'tone2',
driverList: [],
category: 'You have received a gift token!',
);
} catch (e) {
Log.print('invite notification error: $e');
}
}
}
loginDriver(String driverID, email) async {
isloading = true;
update();
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
// await getJWT();
var res = await CRUD().get(
link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
Log.print('loginDriver: ${res}');
if (res == 'failure') {
await isPhoneVerified();
isloading = false; // <--- أضفت هذا أيضاً
update();
return false;
// Get.snackbar('Failure', '', backgroundColor: Colors.red);
} else {
var jsonDecoeded = jsonDecode(res);
var d = jsonDecoeded['data'][0];
if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success' &&
d['is_verified'].toString() == '1') {
box.write(BoxName.emailDriver, d['email']);
box.write(BoxName.firstTimeLoadKey, 'false');
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');
box.write(BoxName.gender, (d['gender']));
box.write(BoxName.phoneVerified, d['is_verified'].toString());
box.write(BoxName.phoneDriver, (d['phone']));
box.write(BoxName.is_claimed, d['is_claimed']);
box.write(BoxName.isInstall, d['isInstall']);
// box.write(
// BoxName.isGiftToken, d['isGiftToken']);
box.write(BoxName.nameArabic, (d['name_arabic']));
box.write(BoxName.carYear, d['year']);
box.write(BoxName.bankCodeDriver, (d['bankCode']));
box.write(BoxName.accountBankNumberDriver, (d['accountBank']));
box.write(
BoxName.nameDriver,
'${(d['first_name'])}'
' ${(d['last_name'])}');
if (((d['model']).toString().contains('دراجه') ||
d['make'].toString().contains('دراجه '))) {
if ((d['gender']).toString() == 'Male') {
box.write(BoxName.carTypeOfDriver, 'Scooter');
} else {
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
}
} else if (int.parse(d['year'].toString()) > 2016) {
if (d['gender'].toString() != 'Male') {
box.write(BoxName.carTypeOfDriver, 'Lady');
} else {
box.write(BoxName.carTypeOfDriver, 'Comfort');
}
} else if (int.parse(d['year'].toString()) > 2002 &&
int.parse(d['year'].toString()) < 2016) {
box.write(BoxName.carTypeOfDriver, 'Speed');
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
// ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول
Log.print('🔑 Getting access token after login...');
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
// await storage.write(
// key: BoxName.fingerPrint, value: fingerPrint.toString());
// await getJWT();
// Log.print('🔑 Access token obtained.');
// add invitations
if (box.read(BoxName.isInstall) == null ||
box.read(BoxName.isInstall).toString() == '0') {
updateInvitationCodeFromRegister();
}
// updateAppTester(AppInformation.appName);
final driverStatus = d['status'].toString();
if (driverStatus != 'yet' && driverStatus != 'pending_review') {
var token = await CRUD().get(
link: AppLink.getDriverToken,
payload: {
'captain_id': (box.read(BoxName.driverID)).toString()
});
// print(jsonDecode(token)['data'][0]['token'].toString());
// print(box.read(BoxName.tokenDriver).toString());
// if (box.read(BoxName.emailDriver).toString() !=
// '963992952235@intaleqapp.com') {
if (token != 'failure') {
var serverData = jsonDecode(token);
final serverToken = serverData['data'][0]['token'].toString();
final storedToken =
box.read(BoxName.tokenDriver)?.toString() ?? '';
final serverFp = serverData['data'][0]['fingerPrint'].toString();
final tokenMismatch =
storedToken.isNotEmpty && serverToken != storedToken;
Log.print(
'🔍 [FP_COMPARE] serverFp: $serverFp | localFp: ${fingerPrint.toString()} | tokenMismatch: $tokenMismatch | serverToken: $serverToken | storedToken: $storedToken');
if (tokenMismatch) {
Get.defaultDialog(
barrierDismissible: false,
title: 'Device Change Detected'.tr,
middleText: 'Please verify your identity'.tr,
textConfirm: 'Verify'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
// نغلق الـ Dialog أولاً بشكل صريح
if (Get.isDialogOpen ?? false) {
Get.back();
}
// ثم ننتقل لصفحة OTP
Get.offAll(
() => OtpVerificationPage(
phone: d['phone'].toString(),
deviceToken: fingerPrint.toString(),
token: token.toString(),
ptoken:
jsonDecode(token)['data'][0]['token'].toString(),
),
);
},
);
isloading = false;
update();
return true; // نخرج من الدالة هنا لنسمح لـ OTP بالتعامل مع الأمر
}
// }
}
Get.offAll(() => HomeCaptain()); // افترض أن هذا الكلاس موجود
isloading = false; // <--- أضفت هذا
update(); // <--- أضفت هذا
return true;
} else {
Get.offAll(
() => DriverVerificationScreen()); // افترض أن هذا الكلاس موجود
isloading = false; // <--- أضفت هذا
update(); // <--- أضفت هذا
return false;
}
// Get.off(() => HomeCaptain());
} else {
Get.offAll(() => PhoneNumberScreen());
isloading = false;
update();
return false; // <--- ✅ وهذا السطر موجود للحالات الأخرى
}
} else {
mySnackbarSuccess('');
isloading = false;
update();
}
}
}
logintest(String driverID, email) async {
isloading = true;
update();
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
var res = await CRUD().get(
link: AppLink.loginFromGoogleCaptin, payload: {'driver_id': driverID});
// print('res is $res');
// if (res == 'failure') {
// await isPhoneVerified();
// // Get.snackbar('Failure', '', backgroundColor: Colors.red);
// } else
// {
var jsonDecoeded = jsonDecode(res);
var d = jsonDecoeded['data'][0];
if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success')
// &&
// d['is_verified'].toString() == '1')
{
box.write(BoxName.emailDriver, d['email']);
box.write(BoxName.firstTimeLoadKey, 'false');
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');
box.write(BoxName.gender, (d['gender']));
box.write(BoxName.phoneVerified, d['is_verified'].toString());
box.write(BoxName.phoneDriver, (d['phone']));
box.write(BoxName.is_claimed, d['is_claimed']);
box.write(BoxName.isInstall, d['isInstall']);
// box.write(
// BoxName.isGiftToken, d['isGiftToken']);
box.write(BoxName.nameArabic, (d['name_arabic']));
box.write(BoxName.carYear, d['year']);
box.write(BoxName.bankCodeDriver, (d['bankCode']));
box.write(BoxName.accountBankNumberDriver, (d['accountBank']));
box.write(
BoxName.nameDriver,
'${(d['first_name'])}'
' ${(d['last_name'])}');
if (((d['model']).toString().contains('دراجه') ||
d['make'].toString().contains('دراجه '))) {
if ((d['gender']).toString() == 'Male') {
box.write(BoxName.carTypeOfDriver, 'Scooter');
} else {
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
}
} else if (int.parse(d['year'].toString()) > 2016) {
if (d['gender'].toString() != 'Male') {
box.write(BoxName.carTypeOfDriver, 'Lady');
} else {
box.write(BoxName.carTypeOfDriver, 'Comfort');
}
} else if (int.parse(d['year'].toString()) > 2002 &&
int.parse(d['year'].toString()) < 2016) {
box.write(BoxName.carTypeOfDriver, 'Speed');
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
// updateAppTester(AppInformation.appName);
// var token = await CRUD().get(
// link: AppLink.getDriverToken,
// payload: {'captain_id': (box.read(BoxName.driverID)).toString()});
// String fingerPrint = await DeviceHelper.getDeviceFingerprint();
// await storage.write(
// key: BoxName.fingerPrint, value: fingerPrint.toString());
Get.off(() => HomeCaptain());
// } else {
// Get.offAll(() => PhoneNumberScreen());
// isloading = false;
// update();
// }
// }
// else {
// mySnackbarSuccess('');
// isloading = false;
// update();
}
}
}
loginUsingCredentialsWithoutGoogle(String password, email) async {
isloading = true;
update();
try {
var fingerPrint = await DeviceHelper.getDeviceFingerprint();
var payload = {
'email': email,
'password': password,
'fingerPrint': fingerPrint,
'aud': AppInformation.appName,
};
var response = await http.post(
Uri.parse(AppLink.loginUsingCredentialsWithoutGoogle),
body: payload,
);
if (response.statusCode == 200 || response.statusCode == 201) {
var jsonDecoeded = jsonDecode(response.body);
if (jsonDecoeded['status'] == 'success') {
var d = jsonDecoeded['data'][0];
var jwt = jsonDecoeded['jwt'];
// حفظ التوكن أولاً
if (jwt != null) {
box.write(BoxName.jwt, c(jwt));
await storage.write(key: BoxName.jwt, value: c(jwt));
}
box.write(BoxName.emailDriver, (d['email']));
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');
box.write(BoxName.gender, (d['gender']));
box.write(BoxName.phoneVerified, d['is_verified'].toString());
box.write(BoxName.phoneDriver, (d['phone']));
box.write(BoxName.nameArabic, (d['name_arabic']));
box.write(BoxName.bankCodeDriver, (d['bankCode']));
box.write(BoxName.accountBankNumberDriver, d['accountBank']);
box.write(
BoxName.nameDriver,
'${(d['first_name'])}'
' ${(d['last_name'])}');
await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
Get.off(() => HomeCaptain());
} else {
mySnackbarError('Login failed'.tr);
isloading = false;
update();
}
} else {
mySnackbarError('Server error'.tr);
isloading = false;
update();
}
} catch (e) {
mySnackbarError('Network error'.tr);
isloading = false;
update();
}
}
void loginByBoxData() async {
Get.to(() => HomeCaptain());
await CRUD().post(link: AppLink.addTokensDriver, payload: {
'token': box.read(BoxName.tokenDriver).toString(),
'captain_id': box.read(BoxName.driverID).toString()
});
CRUD().post(
link: "${AppLink.seferAlexandriaServer}/ride/firebase/addDriver.php",
payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id': box.read(BoxName.driverID).toString()
});
CRUD().post(
link: "${AppLink.seferGizaServer}/ride/firebase/addDriver.php",
payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id': box.read(BoxName.driverID).toString()
});
}
}
+93
View File
@@ -0,0 +1,93 @@
import 'dart:io';
import 'package:get/get.dart';
// import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/controller/functions/llama_ai.dart';
// class CarRegistrationRecognizerController extends GetxController {
// @override
// void onInit() {
// // scanText();
// super.onInit();
// }
// // The ImagePicker instance
// final ImagePicker _imagePicker = ImagePicker();
// // The GoogleMlKit TextRecognizer instance
// // final TextRecognizer _textRecognizer = TextRecognizer();
// // The scanned text
// String? scannedText;
// String? jsonOutput;
// final List<Map<String, dynamic>> lines = [];
// Map extracted = {};
// XFile? image;
// CroppedFile? croppedFile;
// // Picks an image from the camera or gallery and extracts the text
// final List<Map<String, dynamic>> extractedTextWithCoordinates = [];
// Future<void> scanText() async {
// // Pick an image from the camera or gallery
// image = await _imagePicker.pickImage(source: ImageSource.gallery);
// update();
// // If no image was picked, return
// if (image == null) {
// return;
// }
// // Crop the image
// croppedFile = await ImageCropper().cropImage(
// sourcePath: image!.path,
// //
// uiSettings: [
// AndroidUiSettings(
// toolbarTitle: 'Cropper'.tr,
// toolbarColor: AppColor.blueColor,
// toolbarWidgetColor: AppColor.yellowColor,
// initAspectRatio: CropAspectRatioPreset.original,
// lockAspectRatio: false),
// IOSUiSettings(
// title: 'Cropper'.tr,
// ),
// ],
// );
// // If no cropped image was obtained, return
// if (croppedFile == null) {
// return;
// }
// // Convert the cropped file to an InputImage object
// final InputImage inputImage = InputImage.fromFile(File(croppedFile!.path));
// // Recognize the text in the image
// final RecognizedText recognizedText =
// await _textRecognizer.processImage(inputImage);
// scannedText = recognizedText.text;
// // Extract the scanned text line by line
// final List<Map<String, dynamic>> lines = [];
// for (var i = 0; i < recognizedText.blocks.length; i++) {
// lines.add({
// i.toString(): recognizedText.blocks[i].text,
// });
// }
// String result = lines.map((map) => map.values.first.toString()).join(' ');
// if (result.length > 2200) {
// result = result.substring(0, 2200);
// }
// Map result2 = await LlamaAi().getCarRegistrationData(result,
// 'vin,make,made,year,expiration_date,color,owner,registration_date'); //
// // Assign the result to the extracted variable
// extracted = result2;
// update();
// }
// }
@@ -0,0 +1,127 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:siro_driver/print.dart';
import 'package:siro_driver/views/home/Captin/home_captain/home_captin.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../firebase/notification_service.dart';
import '../../functions/crud.dart';
class OtpVerificationController extends GetxController {
final String phone;
final String deviceToken;
final String token;
final otpCode = ''.obs;
final isLoading = false.obs;
final isVerifying = false.obs;
var canResend = false.obs;
var countdown = 120.obs;
Timer? _timer;
OtpVerificationController({
required this.phone,
required this.deviceToken,
required this.token,
});
@override
void onInit() {
super.onInit();
sendOtp(); // ترسل تلقائيًا عند فتح الصفحة
startCountdown();
}
void startCountdown() {
canResend.value = false;
countdown.value = 120;
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (countdown.value > 0) {
countdown.value--;
} else {
canResend.value = true;
timer.cancel();
}
});
}
Future<void> sendOtp() async {
isLoading.value = true;
try {
final response = await CRUD().post(
link: '${AppLink.server}/auth/otp/request.php',
payload: {
'receiver': phone,
'context': 'token_change',
'user_type': 'driver',
// 'device_token': deviceToken,
},
);
if (response != 'failure') {
// بإمكانك عرض رسالة نجاح هنا
} else {
// Get.snackbar('Error', 'Failed to send OTP');
}
} catch (e) {
Get.snackbar('Error', e.toString());
} finally {
isLoading.value = false;
}
}
Future<void> verifyOtp(String ptoken) async {
isVerifying.value = true;
var finger = box.read(BoxName.deviceFingerprint);
try {
final response = await CRUD().post(
link: '${AppLink.server}/auth/otp/verify.php',
payload: {
'phone_number': phone,
'token_code': otpCode.value,
'context': 'token_change',
'user_type': 'driver',
'token': box.read(BoxName.tokenDriver).toString(),
'fingerPrint': finger.toString(),
},
);
if (response != 'failure' &&
response != 'token_expired' &&
response != 'no_internet') {
Log.print('response (already decoded): ${response}');
// توجه إلى الصفحة التالية
await CRUD().post(
link: AppLink.addTokensDriver,
payload: {
'token': box.read(BoxName.tokenDriver).toString(),
'fingerPrint': finger.toString(),
'captain_id': box.read(BoxName.driverID).toString(),
});
await NotificationService.sendNotification(
target: ptoken.toString(),
title: 'token change'.tr,
body: 'token change'.tr,
isTopic: false,
tone: 'cancel',
driverList: [],
category: 'token change',
);
Get.offAll(() => HomeCaptain());
} else {
mySnackbarError('OTP is incorrect or expired'.tr);
}
} catch (e) {
mySnackbarError(e.toString());
} finally {
isVerifying.value = false;
}
}
}
@@ -0,0 +1,158 @@
import 'package:get/get.dart';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/print.dart';
import 'package:siro_driver/views/home/on_boarding_page.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:siro_driver/controller/functions/country_logic.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart';
import '../../../views/auth/syria/registration_view.dart';
// --- Helper Class for Phone Authentication ---
class PhoneAuthHelper {
// Define your server URLs
static final String _sendOtpUrl = '${AppLink.server}/auth/otp/request.php';
static final String _verifyOtpUrl = '${AppLink.server}/auth/otp/verify.php';
static final String _registerUrl =
'${AppLink.server}/auth/syria/driver/register_driver.php';
// removed formatSyrianPhone
/// Sends an OTP to the provided phone number.
static Future<bool> sendOtp(String phoneNumber) async {
try {
final detectedCountry = AppLink.detectCountryFromPhone(phoneNumber);
if (detectedCountry.isNotEmpty) {
await box.write(BoxName.countryCode, detectedCountry);
}
final fixedPhone = CountryLogic.formatCurrentCountryPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post(
link: _sendOtpUrl,
payload: {
'receiver': fixedPhone,
'context': 'login',
'user_type': 'driver'
},
);
Log.print('fixedPhone: ${fixedPhone}');
if (response != 'failure') {
final data = (response);
Log.print('data: ${data}');
if (data['status'] == 'success') {
mySnackbarSuccess('An OTP has been sent to your number.'.tr);
return true;
} else {
mySnackbarError(data['message'] ?? 'Failed to send OTP.'.tr);
return false;
}
} else {
mySnackbarError('Server error. Please try again.'.tr);
return false;
}
} catch (e) {
// mySnackbarError('An error occurred: $e');
return false;
}
}
/// Verifies the OTP and logs the user in.
static Future<void> verifyOtp(String phoneNumber, String otpCode) async {
try {
final fixedPhone = CountryLogic.formatCurrentCountryPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {
'phone_number': fixedPhone,
'token_code': otpCode,
'context': 'login',
'user_type': 'driver'
},
);
if (response != 'failure') {
final data = response;
if (data['status'] == 'success') {
final isRegistered = data['message']['isRegistered'] ?? false;
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phoneDriver, phoneNumber);
box.write(BoxName.driverID, data['message']['driverID']);
if (isRegistered) {
// ✅ السائق مسجل مسبقًا - سجل دخوله واذهب إلى الصفحة الرئيسية
final driver = data['message']['driver'];
// mySnackbarSuccess('Welcome back, ${driver['first_name']}!');
// حفظ بيانات السائق إذا أردت:
box.write(BoxName.driverID, driver['id']);
box.write(BoxName.emailDriver, driver['email']);
await Get.find<LoginDriverController>().loginDriver(
driver['id'].toString(), driver['email'].toString());
} else {
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
// mySnackbarSuccess('Phone verified. Please complete registration.');
// Get.offAll(() => SyrianCardAI());
Get.to(() => RegistrationView());
}
} else {
mySnackbarError(data['message'] ?? 'Verification failed.');
}
} else {
mySnackbarError('Server error. Please try again.');
}
} catch (e) {
mySnackbarError('An error occurred: $e');
}
}
static Future<void> registerUser({
required String phoneNumber,
required String firstName,
required String lastName,
String? email,
}) async {
try {
final response = await CRUD().post(
link: _registerUrl,
payload: {
'phone_number': phoneNumber,
'first_name': firstName,
'last_name': lastName,
'email': email ?? '', // Send empty string if null
},
);
final data = (response);
if (data != 'failure') {
// Registration successful, log user in
await _handleSuccessfulLogin(data['message']);
} else {
mySnackbarError(
"User with this phone number or email already exists.".tr);
}
} catch (e) {
mySnackbarError('An error occurred: $e');
}
}
static Future<void> _handleSuccessfulLogin(
Map<String, dynamic> userData) async {
mySnackbarSuccess('Welcome, ${userData['first_name']}!');
// Save user data to local storage (Hive box) using new keys
box.write(BoxName.passengerID, userData['id']);
box.write(BoxName.nameDriver, userData['first_name']);
box.write(BoxName.lastNameDriver, userData['last_name']);
box.write(BoxName.emailDriver, userData['email']);
box.write(BoxName.phoneDriver, userData['phone']);
Get.offAll(() => OnBoardingPage()); // Navigate to home
}
}