first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class AuthController extends GetxController {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<User?> signInWithApple() async {
try {
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
final oAuthProvider = OAuthProvider('apple.com');
final credential = oAuthProvider.credential(
idToken: appleCredential.identityToken,
accessToken: appleCredential.authorizationCode,
);
UserCredential userCredential =
await _auth.signInWithCredential(credential);
return userCredential.user;
} catch (error) {
return null;
}
}
void signOut() async {
await _auth.signOut();
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,609 @@
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/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 Intaleq as a driver using my referral code!
Use code: $driverCouponCode
Download the Intaleq Driver app now and earn rewards!
''';
await Share.share(shareText);
}
}
Future<void> sharePassengerCode() async {
if (couponCode != null) {
final String shareText = '''Get a discount on your first Intaleq ride!
Use my referral code: $couponCode
Download the Intaleq app now and enjoy your ride!
''';
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) {
// mySnackeBarError('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) {
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) {
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.
void selectPhone(String phone) {
// Format the selected phone number and update the text field.
invitePhoneController.text = _formatSyrianPhoneNumber(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();
await CRUD().post(
link: AppLink.updateInviteDriver,
payload: {'id': (driverInvitationData[index]['id'])});
await Get.find<CaptainWalletController>().addDriverPayment(
'paymentMethod',
('500'),
'',
);
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor(
'paymentMethod',
(driverInvitationData[index]['driverInviterId']),
('500'),
);
NotificationCaptainController().addNotificationCaptain(
driverInvitationData[index]['driverInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have 500".tr} ${'SYP'.tr}',
false);
NotificationController().showNotification(
"You have got a gift for invitation".tr,
'${"You have 500".tr} ${'SYP'.tr}',
'tone1',
'');
} 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} ${'SYP'.tr}',
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());
}
},
);
}
/// Formats a phone number to the standard Syrian international format (+963...).
String _formatSyrianPhoneNumber(String input) {
String digitsOnly = input.replaceAll(RegExp(r'\D'), '');
if (digitsOnly.startsWith('09') && digitsOnly.length == 10) {
return '963${digitsOnly.substring(1)}';
}
if (digitsOnly.length == 9 && digitsOnly.startsWith('9')) {
return '963$digitsOnly';
}
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 =
normalizeSyrianPhone(invitePhoneController.text);
if (formattedPhoneNumber.length != 12) {
mySnackeBarError('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') {
var d = (response);
mySnackbarSuccess('Invite sent successfully'.tr);
String message = '${'*Intaleq 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'
'_*${d['message']['inviteCode'].toString()}*_\n\n'
'${"Install our app:".tr}\n'
'*Android:* https://play.google.com/store/apps/details?id=com.intaleq_driver \n\n\n'
'*iOS:* https://apps.apple.com/st/app/intaleq-driver/id6482995159';
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
} else {
mySnackeBarError("Invite code already used".tr);
}
}
/// Sends an invitation to a potential new passenger.
void sendInviteToPassenger() 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 = 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,
},
);
if (response != 'failure') {
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'
'${"The period of this code is 24 hours".tr}\n\n'
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
'_*${d['message']['inviteCode'].toString()}*_\n\n'
'${"Install our app:".tr}\n'
'*Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n\n\n'
'*iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179';
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
} else {
mySnackeBarError("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,
});
}

View File

@@ -0,0 +1,696 @@
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/captin/cards/sms_signup.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/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 FlutterSecureStorage _storage = const FlutterSecureStorage();
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 {
box.write(BoxName.countryCode, 'Syria');
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
// await getAppTester();
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 {
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 = '';
getJwtWallet() async {
if (box.read(BoxName.security_check).toString() != 'passed') {
Log.print('Security check failed');
return;
}
Log.print('Security check passed');
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
dev = Platform.isAndroid ? 'android' : 'ios';
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');
await box.write(BoxName.hmac, hmac);
return jwt.toString();
}
getJWT() async {
await EncryptionHelper.initialize();
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 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 AppInitializer().getKey();
}
}
}
Future<void> getLocationPermission() async {
var status = await Permission.locationAlways.status;
if (!status.isGranted) {
await Permission.locationAlways.request();
}
update();
}
String generateUniqueIdFromEmail(String email) {
// Step 1: Extract the local part of the email
String localPart = email.split('@')[0];
// Step 2: Replace invalid characters (if any)
String cleanLocalPart = localPart.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '');
// Step 3: Ensure it does not exceed 24 characters
if (cleanLocalPart.length > 24) {
cleanLocalPart = cleanLocalPart.substring(0, 24);
}
// Step 4: Generate a random suffix if needed
String suffix = generateRandomSuffix(24 - cleanLocalPart.length);
return cleanLocalPart + suffix;
}
String generateRandomSuffix(int length) {
const String chars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
Random random = Random();
return List.generate(length, (index) => chars[random.nextInt(chars.length)])
.join('');
}
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');
}
}
}
loginWithGoogleCredential(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: {
// 'email': email ?? 'yet',
'id': driverID,
});
Log.print('loginWithGoogleCredential: ${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...');
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);
if (d['status'].toString() != 'yet') {
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());
// 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);
if ((serverData['data'][0]['token'].toString()) !=
box.read(BoxName.tokenDriver).toString() ||
serverData['data'][0]['fingerPrint'].toString() !=
fingerPrint.toString()) {
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: {
'email': email ?? 'yet',
'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;
isGoogleLogin = true;
update();
var res = await CRUD()
.get(link: AppLink.loginUsingCredentialsWithoutGoogle, payload: {
'email': (email),
'password': password,
});
box.write(BoxName.emailDriver, (email).toString());
// print(res);
if (res == 'failure') {
//Failure
if (box.read(BoxName.phoneVerified).toString() == '1') {
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
} else {
Get.offAll(() => SmsSignupEgypt());
}
isloading = false;
update();
} 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.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'])}');
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()) > 2017) {
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()) < 2017) {
box.write(BoxName.carTypeOfDriver, 'Speed');
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
updateAppTester(AppInformation.appName);
var fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
var token = await CRUD().get(
link: AppLink.getDriverToken,
payload: {'captain_id': box.read(BoxName.driverID).toString()});
if (token != 'failure') {
if ((jsonDecode(token)['data'][0]['token']) !=
(box.read(BoxName.tokenDriver))) {
// Get.put(FirebaseMessagesController()).sendNotificationToDriverMAP(
// 'token change'.tr,
// 'change device'.tr,
// (jsonDecode(token)['data'][0]['token']).toString(),
// [],
// 'ding.wav');
NotificationService.sendNotification(
target: (jsonDecode(token)['data'][0]['token']).toString(),
title: 'token change'.tr,
body: 'token change'.tr,
isTopic: false, // Important: this is a token
tone: 'cancel',
driverList: [], category: 'token change',
);
Get.defaultDialog(
title: 'you will use this device?'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () async {
await CRUD()
.post(link: AppLink.addTokensDriver, payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id': box.read(BoxName.driverID).toString(),
'fingerPrint': (fingerPrint).toString()
});
Get.back();
}));
}
}
Get.off(() => HomeCaptain());
// Get.off(() => LoginCaptin());
} else {
Get.offAll(() => SmsSignupEgypt());
isloading = false;
update();
}
} else {
mySnackeBarError('');
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()
});
}
}

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();
// }
// }

View File

@@ -0,0 +1,126 @@
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/firbase_messge.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/token_passenger/driver/send_otp_driver.php',
payload: {
'receiver': phone,
// '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/token_passenger/driver/verify_otp_driver.php',
payload: {
'phone_number': phone,
'otp': otpCode.value,
'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.paymentServer}/auth/token/update_driver_auth.php',
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 {
mySnackeBarError('OTP is incorrect or expired'.tr);
}
} catch (e) {
mySnackeBarError(e.toString());
} finally {
isVerifying.value = false;
}
}
}

View File

@@ -0,0 +1,204 @@
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 '../../../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 _baseUrl = '${AppLink.server}/auth/syria/driver/';
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': fixedPhone},
);
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 {
// mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
// return false;
// }
} else {
mySnackeBarError('Server error. Please try again.'.tr);
return false;
}
} catch (e) {
// mySnackeBarError('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 = formatSyrianPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {
'phone_number': fixedPhone,
'otp': otpCode,
},
);
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>().loginWithGoogleCredential(
driver['id'].toString(), driver['email'].toString());
} else {
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
// mySnackbarSuccess('Phone verified. Please complete registration.');
// Get.offAll(() => SyrianCardAI());
Get.to(() => RegistrationView());
}
} else {
mySnackeBarError(data['message'] ?? 'Verification failed.');
}
} else {
mySnackeBarError('Server error. Please try again.');
}
} catch (e) {
mySnackeBarError('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 {
mySnackeBarError(
"User with this phone number or email already exists.".tr);
}
} catch (e) {
mySnackeBarError('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
}
}

View File

@@ -0,0 +1,418 @@
import 'dart:convert';
import 'dart:math';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.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/controller/functions/ocr_controller.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/auth/captin/login_captin.dart';
import 'package:siro_driver/views/auth/captin/verify_email_captain.dart';
import '../../../constant/colors.dart';
import '../../../views/auth/captin/ai_page.dart';
import '../../../views/auth/syria/registration_view.dart';
import '../../../views/home/Captin/home_captain/home_captin.dart';
import '../../functions/sms_egypt_controller.dart';
class RegisterCaptainController extends GetxController {
final formKey = GlobalKey<FormState>();
final formKey3 = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController verifyCode = TextEditingController();
String birthDate = 'Birth Date'.tr;
String gender = 'Male'.tr;
bool isLoading = false;
bool isSent = false;
late String name;
late String licenseClass;
late String documentNo;
late String address;
late String height;
late String postalCode;
late String sex;
late String stateCode;
late String expireDate;
late String dob;
getBirthDate() {
Get.defaultDialog(
title: 'Select Date'.tr,
content: SizedBox(
width: 300,
child: CalendarDatePicker(
initialDate: DateTime.now().subtract(const Duration(days: 18 * 365)),
firstDate: DateTime.parse('1940-06-01'),
lastDate: DateTime.now().subtract(const Duration(days: 18 * 365)),
onDateChanged: (date) {
// Get the selected date and convert it to a DateTime object
DateTime dateTime = date;
// Call the getOrders() function from the controller
birthDate = dateTime.toString().split(' ')[0];
update();
Get.back();
},
// onDateChanged: (DateTime value) {},
),
),
);
}
@override
void onInit() {
// Get.put(SmsEgyptController());
super.onInit();
}
void changeGender(String value) {
gender = value;
update();
}
bool isValidEgyptianPhoneNumber(String phoneNumber) {
// Remove any non-digit characters (spaces, dashes, etc.)
phoneNumber = phoneNumber.replaceAll(RegExp(r'\D+'), '');
// Check if the phone number has exactly 11 digits
if (phoneNumber.length != 11) {
return false;
}
// Check if the phone number starts with 010, 011, 012, or 015
RegExp validPrefixes = RegExp(r'^01[0125]\d{8}$');
return validPrefixes.hasMatch(phoneNumber);
}
sendOtpMessage() async {
SmsEgyptController smsEgyptController = Get.put(SmsEgyptController());
isLoading = true;
update();
isLoading = true;
update();
if (formKey3.currentState!.validate()) {
if (box.read(BoxName.countryCode) == 'Egypt') {
if (isValidEgyptianPhoneNumber(phoneController.text)) {
var responseCheker = await CRUD()
.post(link: AppLink.checkPhoneNumberISVerfiedDriver, payload: {
'phone_number': ('+2${phoneController.text}'),
});
if (responseCheker != 'failure') {
var d = jsonDecode(responseCheker);
if (d['message'][0]['is_verified'].toString() == '1') {
Get.snackbar('Phone number is verified before'.tr, '',
backgroundColor: AppColor.greenColor);
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phone, ('+2${phoneController.text}'));
await Get.put(LoginDriverController()).loginWithGoogleCredential(
box.read(BoxName.driverID).toString(),
(box.read(BoxName.emailDriver).toString()),
);
} else {
await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: {
'phone_number': ('+2${phoneController.text}'),
"driverId": box.read(BoxName.driverID),
"email": (box.read(BoxName.emailDriver)),
});
isSent = true;
isLoading = false;
update();
}
} else {
await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: {
'phone_number': ('+2${phoneController.text}'),
"driverId": box.read(BoxName.driverID),
"email": box.read(BoxName.emailDriver),
});
isSent = true;
isLoading = false;
update();
}
} else {
mySnackeBarError(
'Phone Number wrong'.tr,
);
}
}
}
isLoading = false;
update();
}
DateTime? lastOtpSentTime; // Store the last OTP sent time
int otpResendInterval = 300; // 5 minutes in seconds
// Main function to handle OTP sending
// sendOtpMessage() async {
// if (_isOtpResendAllowed()) {
// isLoading = true;
// update();
// if (formKey3.currentState!.validate()) {
// String countryCode = box.read(BoxName.countryCode);
// String phoneNumber = phoneController.text;
// if (countryCode == 'Egypt' && isValidEgyptianPhoneNumber(phoneNumber)) {
// await _checkAndSendOtp(phoneNumber);
// } else {
// _showErrorMessage('Phone Number is not Egypt phone '.tr);
// }
// }
// isLoading = false;
// update();
// } else {
// _showCooldownMessage();
// }
// }
// Check if the resend OTP request is allowed (5 minutes cooldown)
// bool _isOtpResendAllowed() {
// if (lastOtpSentTime == null) return true;
// final int elapsedTime =
// DateTime.now().difference(lastOtpSentTime!).inSeconds;
// return elapsedTime >= otpResendInterval;
// }
// // Show message when user tries to resend OTP too soon
// void _showCooldownMessage() {
// int remainingTime = otpResendInterval -
// DateTime.now().difference(lastOtpSentTime!).inSeconds;
// Get.snackbar(
// 'Please wait ${remainingTime ~/ 60}:${(remainingTime % 60).toString().padLeft(2, '0')} minutes before requesting again',
// '',
// backgroundColor: AppColor.redColor,
// );
// }
// // Check if the phone number has been verified, and send OTP if not verified
// _checkAndSendOtp(String phoneNumber) async {
// var responseChecker = await CRUD().post(
// link: AppLink.checkPhoneNumberISVerfiedDriver,
// payload: {
// 'phone_number': '+2$phoneNumber',
// },
// );
// if (responseChecker != 'failure') {
// var responseData = jsonDecode(responseChecker);
// if (_isPhoneVerified(responseData)) {
// _handleAlreadyVerified();
// } else {
// await _sendOtpAndSms(phoneNumber);
// }
// } else {
// await _sendOtpAndSms(phoneNumber);
// }
// }
// Check if the phone number is already verified
bool _isPhoneVerified(dynamic responseData) {
return responseData['message'][0]['is_verified'].toString() == '1';
}
// Handle case where phone number is already verified
_handleAlreadyVerified() {
mySnackbarSuccess('Phone number is already verified'.tr);
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phone, ('+2${phoneController.text}'));
Get.put(LoginDriverController()).loginWithGoogleCredential(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(),
);
}
// Send OTP and SMS
_sendOtpAndSms(String phoneNumber) async {
SmsEgyptController smsEgyptController = Get.put(SmsEgyptController());
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(
link: AppLink.sendVerifyOtpMessage,
payload: {
'phone_number': ('+2$phoneNumber'),
'token_code': (randomNumber.toString()),
'driverId': box.read(BoxName.driverID),
'email': box.read(BoxName.emailDriver),
},
);
await smsEgyptController.sendSmsEgypt(phoneNumber);
lastOtpSentTime = DateTime.now(); // Update the last OTP sent time
isSent = true;
isLoading = false;
update();
}
verifySMSCode() async {
// var loginDriverController = Get.put(LoginDriverController());
if (formKey3.currentState!.validate()) {
var res = await CRUD().post(link: AppLink.verifyOtpDriver, payload: {
'phone_number': ('+2${phoneController.text}'),
'token_code': (verifyCode.text.toString()),
});
if (res != 'failure') {
// var dec = jsonDecode(res);
box.write(BoxName.phoneDriver, ('+2${phoneController.text}'));
box.write(BoxName.phoneVerified, '1');
// loginDriverController.isGoogleLogin == true
// ? await loginDriverController.loginUsingCredentialsWithoutGoogle(
// loginDriverController.passwordController.text.toString(),
// box.read(BoxName.emailDriver).toString(),
// )
// : await loginDriverController.loginUsingCredentials(
// box.read(BoxName.driverID).toString(),
// box.read(BoxName.emailDriver).toString(),
// );
// Get.offAll(() => SyrianCardAI());
Get.to(() => RegistrationView());
// } else {
// Get.snackbar('title', 'message');
// }
}
} else {
mySnackeBarError('you must insert token code '.tr);
}
}
sendVerifications() async {
var res = await CRUD().post(link: AppLink.verifyEmail, payload: {
'email': emailController.text.isEmpty
? (Get.find<LoginDriverController>().emailController.text.toString())
: (emailController.text),
'token': (verifyCode.text),
});
if (res != 'failure') {
if (Get.find<LoginDriverController>().emailController.text.toString() !=
'') {
Get.offAll(() => HomeCaptain());
} else {
// Get.to(() => CarLicensePage());
}
}
}
void nextToAIDetection() async {
//Todo dont forget this
if (formKey.currentState!.validate()) {
isLoading = true;
update();
Get.to(() => AiPage());
}
}
Map<String, dynamic> payloadLisence = {};
void getFromController() {
name = Get.find<ScanDocumentsByApi>().name;
licenseClass = Get.find<ScanDocumentsByApi>().licenseClass.toString();
documentNo = Get.find<ScanDocumentsByApi>().documentNo.toString();
address = Get.find<ScanDocumentsByApi>().address.toString();
height = Get.find<ScanDocumentsByApi>().height.toString();
postalCode = Get.find<ScanDocumentsByApi>().address.toString();
sex = Get.find<ScanDocumentsByApi>().sex.toString();
stateCode = Get.find<ScanDocumentsByApi>().postalCode.toString();
expireDate = Get.find<ScanDocumentsByApi>().expireDate.toString();
dob = Get.find<ScanDocumentsByApi>().dob.toString();
update();
}
Future addLisence() async {
getFromController();
var res = await CRUD().post(link: AppLink.addLicense, payload: {
'name': name,
'licenseClass': licenseClass,
'documentNo': documentNo,
'address': address,
'height': height,
'postalCode': postalCode,
'sex': sex,
'stateCode': stateCode,
'expireDate': expireDate,
'dateOfBirth': dob,
});
isLoading = false;
update();
if (jsonDecode(res)['status'] == 'success') {
// Get.to(() => AiPage()); //todo rplace this
}
}
void addRegisrationCarForDriver(String vin, make, model, year, color, owner,
expirationDate, registrationDate) async {
getFromController();
var res = await CRUD().post(link: AppLink.addRegisrationCar, payload: {
'vin': vin,
'make': make,
'model': model,
'year': year,
'expirationDate': expirationDate,
'color': color,
'owner': owner,
'registrationDate': registrationDate,
});
box.write(BoxName.vin, vin);
box.write(BoxName.make, make);
box.write(BoxName.model, model);
box.write(BoxName.year, year);
box.write(BoxName.expirationDate, expirationDate);
box.write(BoxName.color, color);
box.write(BoxName.owner, owner);
box.write(BoxName.registrationDate, registrationDate);
isLoading = false;
update();
if (jsonDecode(res)['status'] == 'success') {
Get.offAll(() => LoginCaptin()); //todo replace this
}
}
Future register() async {
getFromController();
if (formKey.currentState!.validate()) {
isLoading = true;
update();
var res = await CRUD().post(link: AppLink.signUpCaptin, payload: {
'first_name': name.split(' ')[1],
'last_name': name.split(' ')[0],
'email': emailController.text,
'phone': phoneController.text,
'password': passwordController.text,
'gender': sex,
'site': address,
'birthdate': dob,
});
isLoading = false;
update();
if (jsonDecode(res)['status'] == 'success') {
box.write(BoxName.driverID, jsonDecode(res)['message']);
box.write(BoxName.dobDriver, dob);
box.write(BoxName.sexDriver, sex);
box.write(BoxName.phoneDriver, phoneController.text);
box.write(BoxName.lastNameDriver, name.split(' ')[0]);
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
'email': emailController.text,
'token': randomNumber.toString(),
});
Get.to(() => VerifyEmailCaptainPage());
}
}
}
}

View File

@@ -0,0 +1,30 @@
// import 'package:firebase_auth/firebase_auth.dart';
// import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
// class FacebookSignIn {
// Future<UserCredential?> signInWithFacebook() async {
// final LoginResult result = await FacebookAuth.instance.login();
// if (result.status == LoginStatus.success) {
// // Create a credential from the access token
// final OAuthCredential credential =
// FacebookAuthProvider.credential(result.accessToken!.tokenString);
// // Once signed in, return the UserCredential
// return await FirebaseAuth.instance.signInWithCredential(credential);
// }
// return null;
// }
// Future<void> signOut() async {
// try {
// await FacebookAuth.instance.logOut();
// print('Facebook Sign Out Successful');
// } catch (e) {
// print('Error during Facebook Sign Out: $e');
// }
// }
// Future<bool> isSignedIn() async {
// final accessToken = await FacebookAuth.instance.accessToken;
// return accessToken != null;
// }
// }

View File

@@ -0,0 +1,98 @@
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/auth/captin/cards/sms_signup.dart';
import 'package:siro_driver/views/home/on_boarding_page.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:get/get.dart';
import 'package:google_sign_in/google_sign_in.dart';
import '../../views/auth/captin/ai_page.dart';
import '../functions/add_error.dart';
import '../functions/encrypt_decrypt.dart';
class GoogleSignInHelper {
static final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
// متغير ثابت لحفظ حالة المستخدم محلياً كبديل لخاصية currentUser المحذوفة
static GoogleSignInAccount? _cachedUser;
static Future<GoogleSignInAccount?> signIn() async {
try {
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') {
Get.to(() => AiPage());
}
}
return googleUser;
} catch (error) {
return null;
}
}
Future<GoogleSignInAccount?> signInFromLogin() async {
try {
final GoogleSignInAccount? googleUser =
await _googleSignIn.authenticate();
if (googleUser != null) {
_cachedUser = googleUser; // حفظ الجلسة في الكاش المحلي
await _handleSignUp(googleUser);
final driverID =
(box.read(BoxName.driverID)?.toString()) ?? 'Unknown ID';
final emailDriver =
(box.read(BoxName.emailDriver)?.toString()) ?? 'Unknown Email';
print('Driver ID: $driverID');
print('Driver Email: $emailDriver');
await Get.find<LoginDriverController>()
.loginWithGoogleCredential(driverID, emailDriver);
}
return googleUser;
} catch (error, stackTrace) {
mySnackeBarError('$error');
CRUD.addError(error.toString(), stackTrace.toString(),
'GoogleSignInAccount?> signInFromLogin()');
return null;
}
}
static Future<void> _handleSignUp(GoogleSignInAccount user) async {
box.write(BoxName.driverID, (user.id) ?? 'Unknown ID');
box.write(BoxName.emailDriver, (user.email) ?? 'Unknown Email');
}
static Future<void> signOut() async {
try {
await _googleSignIn.signOut();
} catch (error) {
// التعامل مع الخطأ بصمت إذا كانت جلسة جوجل فارغة مسبقاً
} finally {
_cachedUser = null; // مسح الكاش المحلي بشكل إلزامي
await _handleSignOut();
}
}
static Future<void> _handleSignOut() async {
box.erase();
storage.deleteAll();
Get.offAll(OnBoardingPage());
}
// استخدام الكاش المحلي بدلاً من استدعاء المكتبة
static GoogleSignInAccount? getCurrentUser() {
return _cachedUser;
}
}

View File

@@ -0,0 +1,117 @@
import 'dart:convert';
import 'dart:math';
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/controller/functions/secure_storage.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/auth/verify_email_page.dart';
import '../functions/encrypt_decrypt.dart';
class LoginController extends GetxController {
final formKey = GlobalKey<FormState>();
final formKeyAdmin = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController adminPasswordController = TextEditingController();
TextEditingController adminNameController = TextEditingController();
bool isAgreeTerms = false;
bool isloading = false;
final FlutterSecureStorage _storage = const FlutterSecureStorage();
void changeAgreeTerm() {
isAgreeTerms = !isAgreeTerms;
update();
}
void saveAgreementTerms() {
box.write(BoxName.agreeTerms, 'agreed');
update();
}
void saveCountryCode(String countryCode) {
box.write(BoxName.countryCode, countryCode);
update();
}
void login() async {
isloading = true;
update();
var res = await CRUD().get(link: AppLink.login, payload: {
'email': emailController.text,
'phone': phoneController.text,
'password': passwordController.text
});
isloading = false;
update();
if (res == 'failure') {
//Failure
mySnackeBarError('');
} else {
var jsonDecoeded = jsonDecode(res);
if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success') {
if (jsonDecoeded['data'][0]['verified'] == 1) {
box.write(BoxName.driverID, jsonDecoeded['data'][0]['id']);
box.write(BoxName.emailDriver, (jsonDecoeded['data'][0]['email']));
box.write(
BoxName.nameDriver,
jsonDecoeded['data'][0]['first_name'] +
' ' +
jsonDecoeded['data'][0]['last_name']);
box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']);
SecureStorage().saveData(BoxName.password, passwordController.text);
// Get.offAll(() => const MapPagePassenger());
isloading = false;
update();
await CRUD().post(link: AppLink.addTokens, payload: {
'token': box.read(BoxName.tokenFCM),
'passengerID': box.read(BoxName.passengerID).toString()
});
} else {
isloading = false;
update();
Get.defaultDialog(
title: 'You must Verify email !.'.tr,
middleText: '',
backgroundColor: Colors.yellow[300],
onConfirm: () async {
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
'email': emailController.text,
'token': randomNumber.toString(),
});
Get.to(() => const VerifyEmailPage());
},
);
}
} else if (jsonDecoeded['status'] == 'Failure') {
mySnackeBarError(jsonDecoeded['data']);
isloading = false;
update();
}
} else {
isloading = false;
update();
}
}
}
goToMapPage() {
if (box.read(BoxName.email) != null) {
// Get.offAll(() => const MapPagePassenger());
}
}
@override
void onInit() {
super.onInit();
}
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/main.dart';
import '../../models/model/onboarding_model.dart';
import '../../views/auth/captin/login_captin.dart';
abstract class OnBoardingController extends GetxController {
next();
onPageChanged(int index);
}
class OnBoardingControllerImp extends OnBoardingController {
late PageController pageController;
int currentPage = 0;
@override
next() {
currentPage++;
if (currentPage > onBoardingList.length - 1) {
box.write(BoxName.onBoarding, 'yes');
Get.offAll(() => LoginCaptin());
} else {
pageController.animateToPage(currentPage,
duration: const Duration(milliseconds: 900), curve: Curves.easeInOut);
}
}
@override
onPageChanged(int index) {
currentPage = index;
update();
}
@override
void onInit() {
pageController = PageController();
super.onInit();
}
}

View File

@@ -0,0 +1,95 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import '../../views/auth/captin/login_captin.dart';
import '../../views/auth/verify_email_page.dart';
class RegisterController extends GetxController {
final formKey = GlobalKey<FormState>();
TextEditingController firstNameController = TextEditingController();
TextEditingController lastNameController = TextEditingController();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController siteController = TextEditingController();
TextEditingController verfyCode = TextEditingController();
String birthDate = 'Birth Date'.tr;
String gender = 'Male'.tr;
@override
void onInit() {
super.onInit();
}
getBirthDate() {
Get.defaultDialog(
title: 'Select Date'.tr,
titleStyle: AppStyle.title,
content: SizedBox(
width: 300,
child: CalendarDatePicker(
initialDate:
DateTime.now().subtract(const Duration(days: 14 * 365)),
firstDate: DateTime.parse('1940-06-01'),
lastDate: DateTime.now().subtract(const Duration(days: 14 * 365)),
onDateChanged: (date) {
// Get the selected date and convert it to a DateTime object
DateTime dateTime = date;
// Call the getOrders() function from the controller
birthDate = dateTime.toString().split(' ')[0];
update();
},
// onDateChanged: (DateTime value) {},
),
),
confirm: MyElevatedButton(title: 'Ok'.tr, onPressed: () => Get.back()));
}
void changeGender(String value) {
gender = value;
update();
}
sendVerifications() async {
var res = await CRUD().post(link: AppLink.verifyEmail, payload: {
'email': emailController.text,
'token': verfyCode.text,
});
var dec = jsonDecode(res);
if (dec['status'] == 'success') {
Get.offAll(() => LoginCaptin());
}
}
void register() async {
if (formKey.currentState!.validate()) {
var res = await CRUD().post(link: AppLink.signUp, payload: {
'first_name': firstNameController.text.toString(),
'last_name': lastNameController.text.toString(),
'email': emailController.text.toString(),
'phone': phoneController.text.toString(),
'password': passwordController.text.toString(),
'gender': 'yet',
'site': siteController.text,
'birthdate': birthDate,
});
if (jsonDecode(res)['status'] == 'success') {
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
'email': emailController.text,
'token': randomNumber.toString(),
});
Get.to(() => const VerifyEmailPage());
}
}
}
}

View File

@@ -0,0 +1,799 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image/image.dart' as img;
import 'package:path/path.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/firebase/notification_service.dart';
import '../../../constant/box_name.dart';
import 'package:path_provider/path_provider.dart';
// --- Final Submission ---
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../functions/crud.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
import '../captin/login_captin_controller.dart';
// You can create a simple enum to manage image types
enum ImageType {
driverLicenseFront,
driverLicenseBack,
carLicenseFront,
carLicenseBack,
}
class RegistrationController extends GetxController {
// Page Controller for managing steps
late PageController pageController;
var currentPage =
0.obs; // Use .obs for reactive updates on the step indicator
// Loading state
var isLoading = false.obs;
var isloading = false;
CroppedFile? croppedFile;
final picker = ImagePicker();
var image;
File? myImage;
String? colorHex; // سيُملى من الدروب داون
// Form Keys for validation
final driverInfoFormKey = GlobalKey<FormState>();
final carInfoFormKey = GlobalKey<FormState>();
// STEP 1: Driver Information Controllers
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;
// STEP 2: Car Information Controllers
final carPlateController = TextEditingController();
final carMakeController = TextEditingController();
final carModelController = TextEditingController();
final carYearController = TextEditingController();
final carColorController = TextEditingController();
final carVinController = TextEditingController(); // Chassis number
final carRegistrationExpiryController = TextEditingController();
DateTime? carRegistrationExpiryDate;
// داخل RegistrationController
// المتغيرات لتخزين القيم المختارة (لإرسالها للـ API لاحقاً)
int? selectedVehicleCategoryId; // سيخزن 1 أو 2 أو 3
int? selectedFuelTypeId; // سيخزن 1 أو 2 أو 3 أو 4
// قائمة أنواع المركبات (مطابقة لقاعدة البيانات)
final List<Map<String, dynamic>> vehicleCategoryOptions = [
{'id': 1, 'name': 'Car'.tr}, // ترجمة: سيارة
{'id': 2, 'name': 'Motorcycle'.tr}, // ترجمة: دراجة نارية
{'id': 3, 'name': 'Van / Bus'.tr}, // ترجمة: فان / باص
];
// قائمة أنواع الوقود
final List<Map<String, dynamic>> fuelTypeOptions = [
{'id': 1, 'name': 'Petrol'.tr}, // ترجمة: بنزين
{'id': 2, 'name': 'Diesel'.tr}, // ترجمة: ديزل
{'id': 3, 'name': 'Electric'.tr}, // ترجمة: كهربائي
{'id': 4, 'name': 'Hybrid'.tr}, // ترجمة: هايبرد
];
// STEP 3: Document Uploads
File? driverLicenseFrontImage;
File? driverLicenseBackImage;
File? carLicenseFrontImage;
File? carLicenseBackImage;
@override
void onInit() {
super.onInit();
pageController = PageController();
// Pre-fill phone number if it exists in storage
// phoneController.text = box.read(BoxName.phoneDriver) ?? '';
}
@override
void onClose() {
pageController.dispose();
// Dispose all other text controllers
super.onClose();
}
// --- Page Navigation ---
void goToNextStep() {
bool isValid = false;
if (currentPage.value == 0) {
// Validate Step 1
isValid = driverInfoFormKey.currentState!.validate();
if (isValid) {
// Optional: Check if license is expired
// if (driverLicenseExpiryDate != null &&
// driverLicenseExpiryDate!.isBefore(DateTime.now())) {
// Get.snackbar('Expired License', 'Your drivers license has expired.'.tr
// ,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// return; // Stop progression
// }
}
} else if (currentPage.value == 1) {
// Validate Step 2
isValid = carInfoFormKey.currentState!.validate();
}
if (isValid) {
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
}
void goToPreviousStep() {
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
// --- Image Picking ---
Future<void> pickImage(ImageType type) async {
try {
final picker = ImagePicker();
final picked = await picker.pickImage(
source: ImageSource.camera,
imageQuality: 95, // جودة أولية من الكاميرا
maxWidth: 3000, // نسمح بصورة كبيرة ثم نصغّر نحن
);
if (picked == null) return;
// قصّ الصورة
final cropped = await ImageCropper().cropImage(
sourcePath: picked.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.accentColor,
toolbarWidgetColor: AppColor.redColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(title: 'Cropper'.tr),
],
);
if (cropped == null) return; // المستخدم ألغى
// قراءة bytes + التصحيح حسب EXIF ثم التصغير
final rawBytes = await File(cropped.path).readAsBytes();
final decoded = img.decodeImage(rawBytes);
if (decoded == null) throw Exception('Decode image failed');
// تصحيح اتجاه الصورة (EXIF)
final fixed = img.bakeOrientation(decoded);
// تصغير لعرض 800px (عدّل عند الحاجة)
final resized = img.copyResize(fixed, width: 800);
// حفظ مؤقت بصيغة JPG
final tmpDir = await getTemporaryDirectory();
final outPath =
'${tmpDir.path}/doc_${DateTime.now().millisecondsSinceEpoch}.jpg';
final outFile = File(outPath);
await outFile.writeAsBytes(img.encodeJpg(resized, quality: 85));
// عيّن الملف في المتغير الصحيح حسب النوع
if (outFile != null) {
switch (type) {
case ImageType.driverLicenseFront:
driverLicenseFrontImage = File(outFile.path);
break;
case ImageType.driverLicenseBack:
driverLicenseBackImage = File(outFile.path);
break;
case ImageType.carLicenseFront:
carLicenseFrontImage = File(outFile.path);
break;
case ImageType.carLicenseBack:
carLicenseBackImage = File(outFile.path);
break;
}
update(); // Use update() to refresh the GetBuilder UI
}
update(); // لتحديث الـ UI
// // الإرسال للذكاء الاصطناعي
// await sendToAI(type, imageFile: outFile);
} catch (e) {
mySnackeBarError('${'An unexpected error occurred:'.tr} $e');
}
}
// ثابت: 20 لون سيارة شائع
static const List<Map<String, String>> kCarColorOptions = [
{'key': 'color.white', 'hex': '#FFFFFF'},
{'key': 'color.black', 'hex': '#000000'},
{'key': 'color.silver', 'hex': '#C0C0C0'},
{'key': 'color.gray', 'hex': '#808080'},
{'key': 'color.gunmetal', 'hex': '#2A3439'},
{'key': 'color.red', 'hex': '#C62828'},
{'key': 'color.blue', 'hex': '#1565C0'},
{'key': 'color.navy', 'hex': '#0D47A1'},
{'key': 'color.green', 'hex': '#2E7D32'},
{'key': 'color.darkGreen', 'hex': '#1B5E20'},
{'key': 'color.beige', 'hex': '#D7CCC8'},
{'key': 'color.brown', 'hex': '#5D4037'},
{'key': 'color.maroon', 'hex': '#800000'},
{'key': 'color.burgundy', 'hex': '#800020'},
{'key': 'color.yellow', 'hex': '#F9A825'},
{'key': 'color.orange', 'hex': '#EF6C00'},
{'key': 'color.gold', 'hex': '#D4AF37'},
{'key': 'color.bronze', 'hex': '#CD7F32'},
{'key': 'color.champagne', 'hex': '#EFE1C6'},
{'key': 'color.purple', 'hex': '#6A1B9A'},
];
Color hexToColor(String hex) {
var v = hex.replaceAll('#', '');
if (v.length == 6) v = 'FF$v';
return Color(int.parse(v, radix: 16));
}
//uploadSyrianDocs
// دالة مساعدة: تضيف الحقل إذا كان له قيمة
void _addField(Map<String, String> fields, String key, String? value) {
if (value != null && value.toString().isNotEmpty) {
fields[key] = value.toString();
}
}
/// خريطة لتخزين روابط المستندات بعد الرفع
final Map<String, String> docUrls = {
'driver_license_front': '',
'driver_license_back': '',
'car_license_front': '',
'car_license_back': '',
};
/// التصرّف العام لاختيار/قص/ضغط/رفع الصورة حسب type
Future<void> choosImage(String link, String imageType) async {
try {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) return;
image = File(pickedImage.path);
final 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 (croppedFile == null) return;
// صورة للمعاينة داخل التطبيق
myImage = File(croppedFile.path);
isloading = true;
update();
// ضغط (وأيضاً يمكنك إضافة rotateImageIfNeeded قبل/بعد الضغط إن رغبت)
final File compressedImage = await compressImage(File(croppedFile.path));
// تجهيز الحقول
final driverId = box.read(BoxName.driverID);
final payload = <String, String>{
'driverID': driverId,
'imageType': imageType, // مثال: driver_license_front
};
// الرفع وإرجاع الرابط
final String imageUrl = await uploadImage(compressedImage, payload, link);
// حفظ الرابط محلياً حسب النوع
docUrls[imageType] = imageUrl;
Log.print('✅ Uploaded $imageType => $imageUrl');
} catch (e, st) {
Log.print('❌ Error in choosImage: $e\n$st');
mySnackeBarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
/// ترفع الملف وترجع رابط الصورة النهائي كـ String
Future<String> uploadImage(
File file, Map<String, String> data, String link) async {
final uri = Uri.parse(link);
final request = http.MultipartRequest('POST', uri);
// الهيدرز (كما عندك)
final headers = <String, String>{
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
};
request.headers.addAll(headers);
// اسم الملف: driverID.jpg (اختياري)
final forcedName = '${box.read(BoxName.driverID) ?? 'image'}.jpg';
// إضافة الملف (من المسار مباشرة أسلم من الـ stream)
request.files.add(
await http.MultipartFile.fromPath(
'image', // تأكد أنه نفس اسم الحقل على السيرفر
file.path,
filename: forcedName,
),
);
// الحقول الإضافية
data.forEach((k, v) => request.fields[k] = v);
// الإرسال
final streamed = await request.send();
final res = await http.Response.fromStream(streamed);
if (res.statusCode != 200) {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
// نحاول استخراج رابط الصورة من أكثر من مفتاح محتمل
final body = jsonDecode(res.body);
final String? url = body['url'] ??
body['file_link'] ??
body['image_url'] ??
(body['data'] is Map ? body['data']['url'] : null);
if (url == null || url.isEmpty) {
// لو السيرفر يرجع هيكل مختلف، عدّل هنا المفتاح حسب استجابتك الفعلية
throw Exception(
'Upload succeeded but no image URL found in response: ${res.body}');
}
return url;
}
Future<File> compressImage(File file) async {
final dir = await path_provider.getTemporaryDirectory();
final targetPath = "${dir.absolute.path}/temp.jpg";
var result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
targetPath,
quality: 70,
minWidth: 1024,
minHeight: 1024,
);
return File(result!.path);
}
// دالة رفع إلى السيرفر السوري: ترجع file_url (Signed URL)
Future<String> uploadToSyria({
required String docType,
required File file,
required Uri syrianUploadUri,
required String authHeader,
required String hmacHeader,
required String driverId,
Duration timeout = const Duration(seconds: 60),
http.Client? clientOverride,
}) async {
final client = clientOverride ?? http.Client();
try {
final mime = lookupMimeType(file.path) ?? 'image/jpeg';
final parts = mime.split('/');
final req = http.MultipartRequest('POST', syrianUploadUri);
req.headers.addAll({
'Authorization': authHeader,
'X-HMAC-Auth': hmacHeader,
});
req.fields['driver_id'] = driverId;
req.fields['doc_type'] = docType;
req.files.add(
await http.MultipartFile.fromPath(
'file',
file.path,
filename: p.basename(file.path),
contentType: MediaType(parts.first, parts.last),
),
);
// ====== الطباعة قبل الإرسال ======
// Log.print('--- Syrian Upload Request ---');
// Log.print('URL: $syrianUploadUri');
// // Log.print('Method: POST');
// // Log.print('Headers: ${req.headers}');
// Log.print('Fields: ${req.fields}');
// // Log.print(
// // 'File: ${file.path} (${await file.length()} bytes, mime: $mime)');
// Log.print('-----------------------------');
// الإرسال
final streamed = await client.send(req).timeout(timeout);
final resp = await http.Response.fromStream(streamed);
// ====== الطباعة بعد الاستجابة ======
// Log.print('--- Syrian Upload Response ---');
Log.print('Status: ${resp.statusCode}');
// Log.print('Headers: ${resp.headers}');
// Log.print('Body: ${resp.body}');
// Log.print('-------------------------------');
Map<String, dynamic> j = {};
try {
j = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (e) {
Log.print('⚠️ Failed to parse JSON: $e');
}
// التحمّل لشكلين من الـ JSON:
final statusOk = j['status'] == 'success';
final fileUrl = (j['file_url'] ?? j['message']?['file_url'])?.toString();
final fileName =
(j['file_name'] ?? j['message']?['file_name'])?.toString();
if (resp.statusCode == 200 &&
statusOk &&
(fileUrl?.isNotEmpty ?? false)) {
// Log.print(
// '✅ Syrian upload success: $fileUrl (file: ${fileName ?? "-"})');
return fileUrl!;
}
throw Exception(
'❌ Syrian upload failed ($docType): ${j['message'] ?? resp.body}');
} finally {
if (clientOverride == null) client.close();
}
}
Future<void> submitRegistration() async {
// 0) دوال/مساعدات محلية
void _addField(Map<String, String> fields, String key, String? value) {
if (value != null && value.isNotEmpty) {
fields[key] = value;
}
}
// 1) تحقق من وجود الروابط
final driverFrontUrl = docUrls['driver_license_front'];
final driverBackUrl = docUrls['driver_license_back'];
final carFrontUrl = docUrls['car_license_front'];
final carBackUrl = docUrls['car_license_back'];
Log.print(driverFrontUrl.toString());
Log.print(driverBackUrl.toString());
Log.print(carFrontUrl.toString());
Log.print(carBackUrl.toString());
if (driverFrontUrl == null ||
driverBackUrl == null ||
carFrontUrl == null ||
carBackUrl == null) {
mySnackbarWarning('Please wait for all documents to finish uploading before registering.'.tr);
return;
}
isLoading.value = true;
update();
final registerUri = Uri.parse(AppLink.register_driver_and_car);
final client = http.Client();
try {
// ترويسات مشتركة
final bearer =
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}';
final hmac = '${box.read(BoxName.hmac)}';
String fingerPrint =
box.read(BoxName.deviceFingerprint)?.toString() ?? '';
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
String nonce = timestamp; // Simple nonce for now
final req = http.MultipartRequest('POST', registerUri);
req.headers.addAll({
'Authorization': bearer,
// 'X-HMAC-Auth': hmac, // Removed to bypass "Invalid HMAC signature" check
'X-Device-FP': fingerPrint,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
});
final fields = <String, String>{};
// --- Driver Data ---
_addField(fields, 'id', box.read(BoxName.driverID)?.toString());
_addField(fields, 'first_name', firstNameController.text);
_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', 'generated_password_or_token');
_addField(fields, 'status', 'yet');
_addField(fields, 'email', 'Not specified');
_addField(fields, 'gender', 'Male'); // يفضل ربطها بـ Dropdown أيضاً
// --- Car Data ---
_addField(fields, 'vin', 'yet');
_addField(fields, 'car_plate', carPlateController.text);
_addField(fields, 'make', carMakeController.text);
_addField(fields, 'model', carModelController.text);
_addField(fields, 'year', carYearController.text);
_addField(
fields,
'expiration_date',
driverLicenseExpiryController
.text); // تأكد من أن هذا تاريخ انتهاء السيارة وليس الرخصة
_addField(
fields,
'color',
carColorController.text.isNotEmpty
? carColorController.text
: 'White');
_addField(fields, 'color_hex',
(colorHex != null && colorHex!.isNotEmpty) ? colorHex! : '#FFFFFF');
_addField(
fields,
'owner',
'${firstNameController.text} ${lastNameController.text}'
.trim()
.isNotEmpty
? '${firstNameController.text} ${lastNameController.text}'
: 'Driver Owner');
// ============================================================
// 🔥 التعديل الجديد: إرسال الأرقام (IDs) لتصنيف المركبة والوقود
// ============================================================
// 1. إرسال رقم تصنيف المركبة (1=سيارة, 2=دراجة...)
if (selectedVehicleCategoryId != null) {
_addField(fields, 'vehicle_category_id',
selectedVehicleCategoryId.toString());
} else {
_addField(fields, 'vehicle_category_id', '1'); // قيمة افتراضية (سيارة)
}
// 2. إرسال رقم ونوع الوقود
if (selectedFuelTypeId != null) {
// إرسال الرقم (للبحث السريع)
_addField(fields, 'fuel_type_id', selectedFuelTypeId.toString());
// إرسال الاسم نصاً (للتوافق مع العمود القديم 'fuel' إذا لزم الأمر)
// نبحث عن الاسم داخل القائمة بناءً على الرقم المختار
final fuelObj = fuelTypeOptions.firstWhere(
(e) => e['id'] == selectedFuelTypeId,
orElse: () => {'name': 'Petrol'});
_addField(fields, 'fuel', fuelObj['name'].toString());
} else {
_addField(fields, 'fuel_type_id', '1');
_addField(fields, 'fuel', 'Petrol');
}
// --- روابط الصور ---
_addField(fields, 'driver_license_front', driverFrontUrl!);
_addField(fields, 'driver_license_back', driverBackUrl!);
_addField(fields, 'car_license_front', carFrontUrl!);
_addField(fields, 'car_license_back', carBackUrl!);
req.fields.addAll(fields);
// 3) الإرسال
final streamed =
await client.send(req).timeout(const Duration(seconds: 60));
final resp = await http.Response.fromStream(streamed);
// 4) معالجة الاستجابة
Map<String, dynamic>? json;
try {
Log.print('--- Registration Response: ${resp.body} ---');
json = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (_) {}
if (resp.statusCode == 200 && json?['status'] == 'success') {
mySnackbarSuccess('Registration completed successfully!'.tr);
// منطق التوكن والإشعارات وتسجيل الدخول...
final email = box.read(BoxName.emailDriver);
final driverID = box.read(BoxName.driverID);
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await CRUD().post(link: AppLink.addTokensDriver, payload: {
'captain_id': (box.read(BoxName.driverID)).toString(),
'token': (box.read(BoxName.tokenDriver)).toString(),
'fingerPrint': fingerPrint.toString(),
});
NotificationService.sendNotification(
target: 'service',
title: 'New Driver Registration',
body: 'Driver $driverID has submitted registration.',
isTopic: true,
category: 'new_service_request',
);
final c = Get.isRegistered<LoginDriverController>()
? Get.find<LoginDriverController>()
: Get.put(LoginDriverController());
c.loginWithGoogleCredential(driverID, email);
} else {
final msg = (json?['message'] ?? 'Registration failed.').toString();
mySnackeBarError(msg);
}
} catch (e) {
mySnackeBarError('Error: $e');
} finally {
client.close();
isLoading.value = false;
update();
}
}
// // 1) تحقق من الصور
// if (driverLicenseFrontImage == null ||
// driverLicenseBackImage == null ||
// carLicenseFrontImage == null ||
// carLicenseBackImage == null) {
// Get.snackbar(
// 'Missing Documents'.tr, 'Please upload all 4 required documents.'.tr,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.orange,
// colorText: Colors.white);
// return;
// }
// isLoading.value = true;
// final uri = Uri.parse(
// 'https://intaleq.xyz/intaleq/auth/syria/driver/register_driver_and_car.php',
// );
// final client = http.Client();
// try {
// final req = http.MultipartRequest('POST', uri);
// // مهم: لا تضع Content-Type يدويًا، الـ MultipartRequest يتكفّل فيه ببناء boundary.
// final headers = {
// 'Authorization':
// 'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
// 'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
// };
// // 2) الحقول النصية
// final fields = <String, String>{};
// // --- Driver Data ---
// _addField(fields, 'id', box.read(BoxName.driverID)?.toString());
// _addField(fields, 'first_name', firstNameController.text);
// _addField(fields, 'last_name', lastNameController.text);
// _addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
// _addField(fields, 'national_number', nationalIdController.text);
// _addField(fields, 'expiry_date', driverLicenseExpiryController.text);
// _addField(
// fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك
// _addField(fields, 'status', 'yet');
// _addField(fields, 'email',
// 'Not specified'); // سكربت السيرفر سيحوّلها null ويبني ايميل افتراضي
// _addField(fields, 'gender', 'Male');
// // --- Car Data (مطابقة لما يتوقّعه السكربت) ---
// _addField(fields, 'vin', 'carVinController.text);');
// _addField(fields, 'car_plate', carPlateController.text);
// _addField(fields, 'make', carMakeController.text);
// _addField(fields, 'model', carModelController.text);
// _addField(fields, 'year', carYearController.text);
// _addField(fields, 'expiration_date', 'carRegistrationExpiryController');
// _addField(fields, 'color', carColorController.text);
// _addField(fields, 'fuel', 'Gasoline'); // أو حسب اختيارك
// _addField(fields, 'color_hex', colorHex); // مهم
// // لو عندك حقول إضافية مطلوبة بالسكربت (مالك المركبة / الكود اللوني / الوقود) مرّرها:
// _addField(fields, 'owner',
// firstNameController.text + ' ' + lastNameController.text);
// // if (colorHex != null) _addField(fields, 'color_hex', colorHex);
// // if (fuelType != null) _addField(fields, 'fuel', fuelType);
// req.headers.addAll(headers);
// req.fields.addAll(fields);
// // 3) الملفات (4 صور) — مفاتيحها مطابقة للسكربت
// Future<void> addFile(String field, File file) async {
// final mime = lookupMimeType(file.path) ?? 'image/jpeg';
// final parts = mime.split('/');
// final mediaType = MediaType(parts.first, parts.last);
// req.files.add(
// await http.MultipartFile.fromPath(
// field,
// file.path,
// filename: p.basename(file.path),
// contentType: mediaType,
// ),
// );
// }
// await addFile('driver_license_front', driverLicenseFrontImage!);
// await addFile('driver_license_back', driverLicenseBackImage!);
// await addFile('car_license_front', carLicenseFrontImage!);
// await addFile('car_license_back', carLicenseBackImage!);
// // 4) الإرسال
// final streamed =
// await client.send(req).timeout(const Duration(seconds: 60));
// final resp = await http.Response.fromStream(streamed);
// // 5) فحص النتيجة
// Map<String, dynamic>? json;
// try {
// json = jsonDecode(resp.body) as Map<String, dynamic>;
// } catch (_) {}
// if (resp.statusCode == 200 &&
// json != null &&
// json['status'] == 'success') {
// // ممكن يرجّع driverID, carRegID, documents
// final driverID =
// (json['data']?['driverID'] ?? json['driverID'])?.toString();
// if (driverID != null && driverID.isNotEmpty) {
// box.write(BoxName.driverID, driverID);
// }
// Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.green,
// colorText: Colors.white);
// // TODO: انتقل للصفحة التالية أو حدّث الحالة…
// } else {
// final msg =
// (json?['message'] ?? 'Registration failed. Please try again.')
// .toString();
// Get.snackbar('Error'.tr, msg,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// }
// } catch (e) {
// Get.snackbar('Error'.tr, '${'An unexpected error occurred:'.tr} $e',
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// } finally {
// client.close();
// isLoading.value = false;
// }
// }
// Helpers
}

View File

@@ -0,0 +1,38 @@
import 'dart:convert';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
class TokenController extends GetxController {
bool isloading = false;
Future addToken() async {
String? basicAuthCredentials =
await storage.read(key: BoxName.basicAuthCredentials);
isloading = true;
update();
var res = await http.post(
Uri.parse(AppLink.addTokens),
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode(basicAuthCredentials.toString()))}',
},
body: {
'token': box.read(BoxName.tokenFCM.toString()),
'passengerID': box.read(BoxName.passengerID).toString()
},
);
isloading = false;
update();
var jsonToken = jsonDecode(res.body);
if (jsonToken['status'] == 'The token has been updated successfully.') {
mySnackbarSuccess('token updated'.tr);
}
}
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
class VerifyEmailController extends GetxController {
TextEditingController verfyCode = TextEditingController();
@override
void onInit() async {
super.onInit();
}
sendverfications() async {
await CRUD().post(link: AppLink.sendVerifyEmail);
}
}