feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق
الباك إند: - تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً، وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً. - OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع. - N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر. N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)، ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird. فلاتر: - docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام). - السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب. - طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط. - الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3ab74a13a8
commit
de4bbd00ac
@@ -1,34 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:sefer_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';
|
||||
import '../../functions/encrypt_decrypt.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();
|
||||
}
|
||||
}
|
||||
@@ -1,457 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
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:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:sefer_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 {
|
||||
if (await FlutterContacts.requestPermission(readonly: true)) {
|
||||
// 1. Open the native contacts app to select a single contact.
|
||||
final Contact? contact = await FlutterContacts.openExternalPick();
|
||||
|
||||
// 2. If a contact is selected and has a phone number...
|
||||
if (contact != null && contact.phones.isNotEmpty) {
|
||||
String selectedPhone = contact.phones.first.number;
|
||||
|
||||
// 3. Format the number and update the text field.
|
||||
invitePhoneController.text = _formatSyrianPhoneNumber(selectedPhone);
|
||||
update();
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError('Contact permission is required to pick contacts'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('An error occurred while picking contacts: $e'.tr);
|
||||
}
|
||||
}
|
||||
|
||||
/// **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',
|
||||
('50000'),
|
||||
'',
|
||||
);
|
||||
await Get.find<CaptainWalletController>()
|
||||
.addDriverWalletToInvitor(
|
||||
'paymentMethod',
|
||||
(driverInvitationData[index]['driverInviterId']),
|
||||
('50000'),
|
||||
);
|
||||
NotificationCaptainController().addNotificationCaptain(
|
||||
driverInvitationData[index]['driverInviterId'].toString(),
|
||||
"You have got a gift for invitation".tr,
|
||||
'${"You have 50000".tr} ${'SYP'.tr}',
|
||||
false);
|
||||
NotificationController().showNotification(
|
||||
"You have got a gift for invitation".tr,
|
||||
'${"You have 50000".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', '20000', '20000');
|
||||
await Get.find<CaptainWalletController>()
|
||||
.addDriverWalletToInvitor('paymentMethod',
|
||||
driverInvitationData[index]['driverInviterId'], '20000');
|
||||
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 20000".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 1 hour".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 1 hour".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,
|
||||
});
|
||||
}
|
||||
@@ -1,787 +0,0 @@
|
||||
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:sefer_driver/views/auth/captin/egypt_card_a_i.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
||||
import 'package:location/location.dart';
|
||||
|
||||
import '../../../constant/api_key.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/local_notification.dart';
|
||||
import '../../firebase/notification_service.dart';
|
||||
import '../../functions/encrypt_decrypt.dart';
|
||||
import '../../functions/package_info.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, 'Egypt');
|
||||
// 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 {
|
||||
// 1. أولاً نتأكد هل يوجد رقم هاتف محفوظ محلياً (تجاوز الـ OTP)
|
||||
String? savedPhone = box.read(BoxName.phoneDriver);
|
||||
Log.print('savedPhone: ${savedPhone}');
|
||||
|
||||
if (savedPhone == null || savedPhone.isEmpty) {
|
||||
// لا يوجد رقم محفوظ، نعيد المستخدم لصفحة إدخال الرقم
|
||||
Get.offAll(() => PhoneNumberScreen());
|
||||
return;
|
||||
}
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.isPhoneVerified, payload: {'phone_number': savedPhone});
|
||||
|
||||
Log.print('isPhoneVerified Res: $res');
|
||||
|
||||
if (res != 'failure') {
|
||||
// الحالة 1: السائق موجود ومفعل -> نذهب للصفحة التالية
|
||||
Get.offAll(() => EgyptCardAI());
|
||||
} else {
|
||||
// الحالة 2: الرد فشل (السائق غير موجود في الداتا بيز)
|
||||
if (savedPhone != null && savedPhone.isNotEmpty) {
|
||||
// بما أن الرقم محفوظ، فهذا مستخدم جديد أكمل التحقق
|
||||
// نوجهه لصفحة التسجيل/البطاقة بدلاً من العودة للبداية
|
||||
Log.print('New User Detected, redirecting to Registration...');
|
||||
Get.offAll(() => EgyptCardAI());
|
||||
} else {
|
||||
// لا يوجد رقم محفوظ ولا مستخدم في السيرفر -> عودة للبداية
|
||||
Get.offAll(() => PhoneNumberScreen());
|
||||
}
|
||||
}
|
||||
}
|
||||
// isPhoneVerified() async {
|
||||
// var res = await CRUD().post(
|
||||
// link: AppLink.isPhoneVerified,
|
||||
// payload: {'phone_number': box.read(BoxName.phoneDriver)});
|
||||
// Log.print('isPhoneVerified: ${res}');
|
||||
|
||||
// if (res != 'failure') {
|
||||
// // Get.offAll(() => SyrianCardAI());
|
||||
// Get.offAll(() => EgyptCardAI());
|
||||
|
||||
// // 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();
|
||||
}
|
||||
|
||||
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();
|
||||
// Log.print('fingerPrint: ${fingerPrint}');
|
||||
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.JwtWallet: ${response1.body}');
|
||||
// print(payload);
|
||||
// Log.print('payment["jwt"]: ${jsonDecode(response1.body)['jwt']}');
|
||||
await box.write(BoxName.hmac, jsonDecode(response1.body)['hmac']);
|
||||
return jsonDecode(response1.body)['jwt'].toString();
|
||||
}
|
||||
|
||||
String shortHash(String password) {
|
||||
var bytes = utf8.encode(password);
|
||||
var digest = sha256.convert(bytes);
|
||||
return base64UrlEncode(digest.bytes);
|
||||
}
|
||||
|
||||
getJWT() async {
|
||||
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',
|
||||
};
|
||||
// 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}');
|
||||
|
||||
final jwt = decodedResponse1['jwt'];
|
||||
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',
|
||||
};
|
||||
// print(payload);
|
||||
var response1 = await http.post(
|
||||
Uri.parse(AppLink.loginJwtDriver),
|
||||
body: payload,
|
||||
);
|
||||
// print(response1.request);
|
||||
// print(response1.body);
|
||||
|
||||
if (response1.statusCode == 200) {
|
||||
final decodedResponse1 = jsonDecode(response1.body);
|
||||
// Log.print('decodedResponse1: ${decodedResponse1}');
|
||||
|
||||
final jwt = decodedResponse1['jwt'];
|
||||
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}');
|
||||
box.write(BoxName.isInstall, '1');
|
||||
// ✅ تصحيح 1: التحقق من أن الرد ليس 'failure' وأنه Map قبل القراءة
|
||||
if (res != 'failure' && res is Map && res['status'] == 'success') {
|
||||
isInviteDriverFound = true;
|
||||
update();
|
||||
|
||||
NotificationController().showNotification(
|
||||
"Code approved".tr, "Code approved".tr, 'tone2', '');
|
||||
|
||||
// ✅ تصحيح 2: استخراج التوكن حسب هيكل PHP الجديد (مفتاح 'data')
|
||||
String? targetToken;
|
||||
|
||||
// الهيكل الجديد: {status: success, data: {token: ...}}
|
||||
if (res['data'] != null && res['data']['token'] != null) {
|
||||
targetToken = res['data']['token'].toString();
|
||||
}
|
||||
// دعم الهيكل القديم للاحتياط: {status: success, message: [{token: ...}]}
|
||||
else if (res['message'] is List && res['message'].isNotEmpty) {
|
||||
targetToken = res['message'][0]['token'].toString();
|
||||
}
|
||||
|
||||
if (targetToken != null) {
|
||||
NotificationService.sendNotification(
|
||||
target: targetToken,
|
||||
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!',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// فشل التحديث (الكود غير موجود أو خطأ سيرفر)
|
||||
// لا تفعل شيئاً أو اعرض رسالة خطأ صامتة
|
||||
}
|
||||
}
|
||||
|
||||
loginWithGoogleCredential(String driverID, String email) async {
|
||||
isloading = true;
|
||||
update();
|
||||
|
||||
try {
|
||||
// 1. تجهيز البيانات الإضافية للأمان
|
||||
String platformName = Platform.isIOS ? 'ios' : 'android';
|
||||
String storedPhone = box.read(BoxName.phoneDriver)?.toString() ?? '';
|
||||
|
||||
// 2. إرسال الطلب الموحد (Login + Token + PackageInfo)
|
||||
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
|
||||
'id': driverID,
|
||||
'email': email, // ضروري للمطابقة الأمنية في السيرفر
|
||||
'phone': storedPhone, // اختياري، لزيادة الموثوقية
|
||||
'platform': platformName // لجلب معلومات التحديث المناسبة للجهاز
|
||||
});
|
||||
|
||||
Log.print('loginWithGoogleCredential Response: $res');
|
||||
|
||||
if (res == 'failure') {
|
||||
// فشل الدخول (البيانات غير موجودة أو غير مطابقة)
|
||||
// نحاول التحقق من حالة الهاتف لتوجيهه للتسجيل إذا كان جديداً
|
||||
await isPhoneVerified();
|
||||
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
} else {
|
||||
var jsonResponse = jsonDecode(res);
|
||||
|
||||
if (jsonResponse['status'] == 'success') {
|
||||
// ---------------------------------------------------------
|
||||
// A. معالجة بيانات السائق الأساسية
|
||||
// ---------------------------------------------------------
|
||||
var d = jsonResponse['data'][0];
|
||||
|
||||
if (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']);
|
||||
if (box.read(BoxName.isInstall).toString() == 'null' ||
|
||||
box.read(BoxName.isInstall).toString() == '') {
|
||||
box.write(BoxName.isInstall, d['isInstall']);
|
||||
}
|
||||
Log.print(
|
||||
'isInstall for update invitation: ${box.read(BoxName.isInstall)}');
|
||||
|
||||
// box.write(BoxName.isInstall, d['isInstall']);
|
||||
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']}');
|
||||
int categoryId =
|
||||
int.tryParse(d['vehicle_category_id']?.toString() ?? '0') ?? 0;
|
||||
|
||||
// معالجة سنة السيارة بشكل آمن لتجنب الكراش
|
||||
int carYear = int.tryParse(d['year']?.toString() ?? '0') ?? 0;
|
||||
box.write(BoxName.carYear, carYear);
|
||||
|
||||
// منطق تحديد نوع السيارة
|
||||
|
||||
String gender = d['gender']?.toString() ?? '';
|
||||
|
||||
if (categoryId == 2) {
|
||||
// 2 = دراجة / سكوتر (حسب قاعدة البيانات)
|
||||
if (gender == 'Male') {
|
||||
box.write(BoxName.carTypeOfDriver, 'Scooter');
|
||||
} else {
|
||||
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
|
||||
}
|
||||
} else if (categoryId == 3) {
|
||||
// 3 = فان (إذا كان لديكم فئة فان)
|
||||
box.write(BoxName.carTypeOfDriver, 'Van');
|
||||
} else if (carYear > 2017) {
|
||||
if (gender != 'Male') {
|
||||
box.write(BoxName.carTypeOfDriver, 'Lady');
|
||||
} else {
|
||||
box.write(BoxName.carTypeOfDriver, 'Comfort');
|
||||
}
|
||||
} else if (carYear > 2002 && carYear <= 2017) {
|
||||
box.write(BoxName.carTypeOfDriver, 'Speed');
|
||||
} else if (carYear <= 2002 && carYear > 0) {
|
||||
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// B. معالجة التحديثات (Package Info) من نفس الرد
|
||||
// ---------------------------------------------------------
|
||||
// if (jsonResponse['package_info'] != null &&
|
||||
// (jsonResponse['package_info'] as List).isNotEmpty) {
|
||||
// var pkg = jsonResponse['package_info'][0];
|
||||
// // استدعاء دالة فحص التحديث (تأكد من وجودها أو مرر البيانات لها)
|
||||
// // checkUpdateFromData(pkg);
|
||||
// checkForUpdate(Get.context!);
|
||||
// }
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// C. معالجة كود الدعوة
|
||||
// ---------------------------------------------------------
|
||||
if (box.read(BoxName.isInstall) == null ||
|
||||
box.read(BoxName.isInstall).toString() == '0') {
|
||||
updateInvitationCodeFromRegister();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// D. التحقق الأمني (Device Fingerprint & Token)
|
||||
// ---------------------------------------------------------
|
||||
if (d['status'].toString() != 'yet') {
|
||||
// استخراج التوكن من الرد المجمع (دون طلب جديد)
|
||||
List? tokenList = jsonResponse['driver_token'];
|
||||
|
||||
// توليد بصمة الجهاز الحالية
|
||||
String currentFingerPrint =
|
||||
await DeviceHelper.getDeviceFingerprint();
|
||||
await storage.write(
|
||||
key: BoxName.fingerPrint, value: currentFingerPrint);
|
||||
|
||||
if (tokenList != null && tokenList.isNotEmpty) {
|
||||
var tokenData = tokenList[0];
|
||||
String serverToken = tokenData['token'].toString();
|
||||
String serverFingerPrint = tokenData['fingerPrint'].toString();
|
||||
String localToken =
|
||||
box.read(BoxName.tokenDriver)?.toString() ?? "";
|
||||
|
||||
// التحقق من تطابق التوكن والبصمة
|
||||
if (serverToken != localToken ||
|
||||
serverFingerPrint != currentFingerPrint) {
|
||||
isloading = false;
|
||||
update();
|
||||
|
||||
await Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Device Change Detected'.tr,
|
||||
middleText: 'Please verify your identity'.tr,
|
||||
textConfirm: 'Verify'.tr,
|
||||
confirmTextColor: Colors.white,
|
||||
onConfirm: () {
|
||||
Get.back(); // إغلاق الديالوج
|
||||
// الانتقال لصفحة التحقق مع تمرير البيانات اللازمة
|
||||
Get.to(() => OtpVerificationPage(
|
||||
phone: d['phone'].toString(),
|
||||
deviceToken: currentFingerPrint,
|
||||
// نمرر التوكن القادم من السيرفر ليستعمل في التحقق لاحقاً
|
||||
token: jsonEncode(
|
||||
{'status': 'success', 'data': tokenList}),
|
||||
ptoken: serverToken,
|
||||
));
|
||||
},
|
||||
);
|
||||
return true; // إيقاف التنفيذ هنا (بانتظار التحقق)
|
||||
}
|
||||
}
|
||||
|
||||
// إذا كل شيء سليم، الدخول للرئيسية
|
||||
Get.offAll(() => HomeCaptain());
|
||||
} else {
|
||||
// الحالة "yet" تعني أن السائق لم يكمل رفع الوثائق
|
||||
Get.offAll(() => DriverVerificationScreen());
|
||||
}
|
||||
|
||||
isloading = false;
|
||||
update();
|
||||
return true;
|
||||
} else {
|
||||
// الحساب موجود لكن رقم الهاتف غير مفعل
|
||||
Get.offAll(() => PhoneNumberScreen());
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// الرد من السيرفر ليس success (رغم أنه ليس failure في الاتصال)
|
||||
Get.offAll(() => PhoneNumberScreen());
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Login Critical Error: $e");
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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.loginUsingCredentialsWithoutGoogle, 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()) > 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 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, String email, String phone) async {
|
||||
isloading = true;
|
||||
// تحديد نوع العملية (ليس جوجل)
|
||||
isGoogleLogin = false;
|
||||
update();
|
||||
|
||||
try {
|
||||
String platformName = Platform.isIOS ? 'ios' : 'android';
|
||||
|
||||
// 1. إرسال الطلب (إضافة Platform)
|
||||
var res = await CRUD()
|
||||
.get(link: AppLink.loginUsingCredentialsWithoutGoogle, payload: {
|
||||
'email': email,
|
||||
'password': password,
|
||||
'phone': phone, // <-- يجب إضافة هذا السطر
|
||||
'platform': platformName
|
||||
});
|
||||
|
||||
// حفظ الإيميل لمحاولات الدخول اللاحقة
|
||||
box.write(BoxName.emailDriver, email);
|
||||
|
||||
if (res == 'failure') {
|
||||
// في حال الفشل (كلمة مرور خطأ أو مستخدم غير موجود)
|
||||
// نقوم بفحص حالة الهاتف إذا كان مسجلاً من قبل لتوجيهه
|
||||
await isPhoneVerified();
|
||||
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
} else {
|
||||
var jsonResponse = jsonDecode(res);
|
||||
|
||||
if (jsonResponse['status'] == 'success' &&
|
||||
jsonResponse['data'].isNotEmpty) {
|
||||
var d = jsonResponse['data'][0];
|
||||
|
||||
// 2. التحقق من تفعيل الهاتف
|
||||
if (d['is_verified'].toString() == '1') {
|
||||
// --- A. حفظ البيانات الأساسية ---
|
||||
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.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']}');
|
||||
|
||||
// --- B. منطق نوع السيارة ---
|
||||
int categoryId =
|
||||
int.tryParse(d['vehicle_category_id']?.toString() ?? '0') ?? 0;
|
||||
int carYear = int.tryParse(d['year']?.toString() ?? '0') ?? 0;
|
||||
box.write(BoxName.carYear, carYear);
|
||||
String gender = d['gender']?.toString() ?? '';
|
||||
|
||||
if (categoryId == 2) {
|
||||
if (gender == 'Male') {
|
||||
box.write(BoxName.carTypeOfDriver, 'Scooter');
|
||||
} else {
|
||||
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
|
||||
}
|
||||
} else if (categoryId == 3) {
|
||||
box.write(BoxName.carTypeOfDriver, 'Van');
|
||||
} else if (carYear > 2017) {
|
||||
if (gender != 'Male') {
|
||||
box.write(BoxName.carTypeOfDriver, 'Lady');
|
||||
} else {
|
||||
box.write(BoxName.carTypeOfDriver, 'Comfort');
|
||||
}
|
||||
} else if (carYear > 2002 && carYear <= 2017) {
|
||||
box.write(BoxName.carTypeOfDriver, 'Speed');
|
||||
} else if (carYear <= 2002 && carYear > 0) {
|
||||
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
|
||||
}
|
||||
|
||||
// --- C. كود الدعوة ---
|
||||
if (box.read(BoxName.isInstall) == null ||
|
||||
box.read(BoxName.isInstall).toString() == '0') {
|
||||
updateInvitationCodeFromRegister();
|
||||
}
|
||||
|
||||
// --- D. التحقق الأمني (Device Fingerprint) ---
|
||||
if (d['status'].toString() != 'yet') {
|
||||
// الـ PHP المعدل يعيد driver_token داخل object السائق
|
||||
List? tokenList = d['driver_token'];
|
||||
|
||||
String currentFingerPrint =
|
||||
await DeviceHelper.getDeviceFingerprint();
|
||||
await storage.write(
|
||||
key: BoxName.fingerPrint, value: currentFingerPrint);
|
||||
|
||||
if (tokenList != null && tokenList.isNotEmpty) {
|
||||
// نأخذ أحدث توكن (أو الأول في القائمة)
|
||||
var tokenData = tokenList[0];
|
||||
String serverToken = tokenData['token'].toString();
|
||||
String serverFingerPrint = tokenData['fingerPrint'].toString();
|
||||
String localToken =
|
||||
box.read(BoxName.tokenDriver)?.toString() ?? "";
|
||||
|
||||
// إذا اختلف التوكن أو البصمة => جهاز جديد
|
||||
if (serverToken != localToken ||
|
||||
serverFingerPrint != currentFingerPrint) {
|
||||
isloading = false;
|
||||
update();
|
||||
|
||||
await Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Device Change Detected'.tr,
|
||||
middleText: 'Please verify your identity'.tr,
|
||||
textConfirm: 'Verify'.tr,
|
||||
confirmTextColor: Colors.white,
|
||||
onConfirm: () {
|
||||
Get.back();
|
||||
Get.to(() => OtpVerificationPage(
|
||||
phone: d['phone'].toString(),
|
||||
deviceToken: currentFingerPrint,
|
||||
// تمرير قائمة التوكنات لاستخدامها في التحقق
|
||||
token: jsonEncode(
|
||||
{'status': 'success', 'data': tokenList}),
|
||||
ptoken: serverToken,
|
||||
));
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// الدخول للرئيسية
|
||||
Get.offAll(() => HomeCaptain());
|
||||
} else {
|
||||
// لم يكمل الوثائق
|
||||
Get.offAll(() => DriverVerificationScreen());
|
||||
}
|
||||
|
||||
isloading = false;
|
||||
update();
|
||||
return true;
|
||||
} else {
|
||||
// الحساب موجود لكن غير مفعل
|
||||
Get.offAll(() => PhoneNumberScreen());
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// استجابة غير متوقعة أو data فارغة
|
||||
Get.snackbar("Error".tr, "Login Failed".tr);
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Login Credentials Error: $e");
|
||||
isloading = false;
|
||||
update();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
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:sefer_driver/constant/colors.dart';
|
||||
import 'package:sefer_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();
|
||||
// }
|
||||
// }
|
||||
@@ -1,114 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/print.dart';
|
||||
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../main.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 = await storage.read(key: BoxName.fingerPrint);
|
||||
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') {
|
||||
Log.print('response: ${response}');
|
||||
// Get.back(); // توجه إلى الصفحة التالية
|
||||
|
||||
await NotificationService.sendNotification(
|
||||
target: ptoken.toString(),
|
||||
title: 'token change'.tr,
|
||||
body: 'token change'.tr,
|
||||
isTopic: false, // Important: this is a token
|
||||
tone: 'cancel',
|
||||
driverList: [], category: 'token change',
|
||||
);
|
||||
|
||||
Get.offAll(() => HomeCaptain());
|
||||
} else {
|
||||
Get.snackbar('Verification Failed', 'OTP is incorrect or expired');
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error', e.toString());
|
||||
} finally {
|
||||
isVerifying.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/print.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../main.dart';
|
||||
// تأكد من تعديل هذا المسار لصفحة التسجيل الحالية
|
||||
|
||||
class PhoneAuthHelper {
|
||||
// --- New Endpoints for Egypt Market ---
|
||||
// تم تحديث المسارات بناءً على طلبك لتعمل مع ملفات الكابتن الجديدة
|
||||
static final String _baseUrl = '${AppLink.server}/auth/captin/';
|
||||
static final String _sendOtpUrl = '${_baseUrl}sendOtpMessageDriver.php';
|
||||
static final String _verifyOtpUrl = '${_baseUrl}verifyOtpDriver.php';
|
||||
|
||||
/// دالة تنسيق الرقم للسوق المصري والعالمي
|
||||
static String formatPhoneNumber(String phone) {
|
||||
// 1. إزالة المسافات والرموز الزائدة
|
||||
String cleanPhone = phone.replaceAll(RegExp(r'[ \-\(\)]'), '').trim();
|
||||
|
||||
// 2. التحقق إذا كان الرقم مصرياً (يبدأ بـ 010, 011, 012, 015 أو +20)
|
||||
// إذا بدأ بـ +20، نتركه كما هو (صيغة دولية صحيحة)
|
||||
if (cleanPhone.startsWith('+20')) {
|
||||
return cleanPhone;
|
||||
}
|
||||
|
||||
// إذا بدأ بـ 01 (مثل 010xxxx...)، نحذف الصفر ونضيف +20
|
||||
if (cleanPhone.startsWith('01')) {
|
||||
return '+20${cleanPhone.substring(1)}';
|
||||
}
|
||||
|
||||
// إذا بدأ بـ 1 مباشرة (مثل 10xxxx...) وكان الطول 10 أرقام، نضيف +20
|
||||
if (cleanPhone.startsWith('1') && cleanPhone.length == 10) {
|
||||
return '+20$cleanPhone';
|
||||
}
|
||||
|
||||
// 3. للأرقام الدولية الأخرى (غير مصر):
|
||||
// يجب التأكد من وجود علامة + في البداية ليعرف السيرفر أنه رقم دولي (للواتساب)
|
||||
if (!cleanPhone.startsWith('+')) {
|
||||
return '+$cleanPhone';
|
||||
}
|
||||
|
||||
return cleanPhone;
|
||||
}
|
||||
|
||||
/// إرسال الرمز (SMS للمصريين، واتساب للأجانب - يتم التعامل معه في السيرفر)
|
||||
static Future<bool> sendOtp(String phoneNumber, {String? email}) async {
|
||||
try {
|
||||
final fixedPhone = formatPhoneNumber(phoneNumber);
|
||||
Log.print('Sending OTP to: $fixedPhone');
|
||||
|
||||
// نجهز الإيميل والآيدي الحاليين
|
||||
String currentDriverId = box.read(BoxName.driverID)?.toString() ?? '';
|
||||
String currentEmail =
|
||||
email ?? box.read(BoxName.emailDriver)?.toString() ?? '';
|
||||
|
||||
// إرسال الطلب للسيرفر
|
||||
final response = await CRUD().post(
|
||||
link: _sendOtpUrl,
|
||||
payload: {
|
||||
'phone_number': fixedPhone,
|
||||
'driverId': currentDriverId,
|
||||
'email': currentEmail,
|
||||
},
|
||||
);
|
||||
|
||||
Log.print('Send OTP Response: $response');
|
||||
|
||||
if (response != 'failure') {
|
||||
// فك الترميز في حال كان الرد نصاً
|
||||
final data = response;
|
||||
if (data['status'] == 'success') {
|
||||
// -----------------------------------------------------------
|
||||
// هام جداً: حفظ البيانات التي ولدها السيرفر (ID & Email)
|
||||
// -----------------------------------------------------------
|
||||
if (data['driverId'] != null &&
|
||||
data['driverId'].toString().isNotEmpty) {
|
||||
String newId = data['driverId'].toString();
|
||||
Log.print("New Driver ID Generated by Server: $newId");
|
||||
box.write(BoxName.driverID, newId);
|
||||
}
|
||||
|
||||
if (data['email'] != null && data['email'].toString().isNotEmpty) {
|
||||
String newEmail = data['email'].toString();
|
||||
Log.print("New Email Generated by Server: $newEmail");
|
||||
box.write(BoxName.emailDriver, newEmail);
|
||||
}
|
||||
|
||||
mySnackbarSuccess('Verification code sent successfully'.tr);
|
||||
return true;
|
||||
} else {
|
||||
mySnackeBarError(data['message'] ?? 'Failed to send code'.tr);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError('Server Error, please try again'.tr);
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('Error sending OTP: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// التحقق من الرمز المدخل
|
||||
/// ملاحظة هامة: دالة PHP الجديدة للتحقق لا ترجع بيانات السائق، فقط تقول "تم التحقق".
|
||||
/// لذا نحتاج لمنطق إضافي هنا لتسجيل الدخول أو التسجيل.
|
||||
static Future<void> verifyOtp(String phoneNumber, [String? otpCode]) async {
|
||||
try {
|
||||
if (otpCode == null || otpCode.isEmpty) {
|
||||
mySnackeBarError('يرجى إدخال الكود'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
final fixedPhone = formatPhoneNumber(phoneNumber);
|
||||
Log.print('Verifying OTP for: $fixedPhone with code: $otpCode');
|
||||
|
||||
final response = await CRUD().post(
|
||||
link: _verifyOtpUrl,
|
||||
payload: {
|
||||
'phone_number': fixedPhone,
|
||||
'token_code': otpCode, // ملف PHP الجديد يطلب token_code
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure') {
|
||||
final data = response;
|
||||
|
||||
if (data['status'] == 'success') {
|
||||
// 1. التحقق تم بنجاح في قاعدة بيانات الهاتف
|
||||
// بما أن ملف verifyOtpDriver.php الجديد لا يرجع بيانات السائق،
|
||||
// يجب علينا الآن محاولة تسجيل الدخول بهذا الرقم لمعرفة هل هو مسجل أم لا.
|
||||
box.write(BoxName.firstTimeLoadKey, 'true');
|
||||
box.write(BoxName.phoneDriver, fixedPhone);
|
||||
|
||||
var driverId = box.read(BoxName.driverID);
|
||||
Log.print('driverId: ${driverId}');
|
||||
var email = box.read(BoxName.emailDriver);
|
||||
Log.print('email: ${email}');
|
||||
await Get.put(LoginDriverController()).loginWithGoogleCredential(
|
||||
driverId,
|
||||
email,
|
||||
);
|
||||
} else {
|
||||
mySnackeBarError(data['message'] ?? 'كود التحقق غير صحيح'.tr);
|
||||
}
|
||||
} else {
|
||||
mySnackeBarError('خطأ في الاتصال بالسيرفر'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('حدث خطأ: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// دالة مساعدة لمحاولة تسجيل الدخول أو التوجيه للتسجيل
|
||||
/// هذه الدالة ضرورية لأن ملف التحقق الجديد فصل عملية التحقق عن جلب البيانات
|
||||
static Future<void> _checkUserAndLogin() async {}
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
||||
import 'package:sefer_driver/views/auth/captin/egypt_card_a_i.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/controller/functions/ocr_controller.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_driver/views/auth/captin/login_captin.dart';
|
||||
import 'package:sefer_driver/views/auth/captin/verify_email_captain.dart';
|
||||
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../views/auth/captin/ai_eg_controller.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(() => EgyptCardAI());
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
@@ -1,282 +0,0 @@
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_driver/views/auth/captin/cards/sms_signup.dart';
|
||||
import 'package:sefer_driver/views/auth/captin/egypt_card_a_i.dart';
|
||||
import 'package:sefer_driver/views/home/on_boarding_page.dart';
|
||||
import 'package:sefer_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_eg_controller.dart';
|
||||
import '../functions/add_error.dart';
|
||||
import '../functions/encrypt_decrypt.dart';
|
||||
|
||||
class GoogleSignInHelper {
|
||||
static final GoogleSignIn _googleSignIn = GoogleSignIn(
|
||||
scopes: [
|
||||
'email',
|
||||
'profile',
|
||||
],
|
||||
);
|
||||
|
||||
// Method to handle Google Sign-In
|
||||
static Future<GoogleSignInAccount?> signIn() async {
|
||||
try {
|
||||
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
|
||||
if (googleUser != null) {
|
||||
await _handleSignUp(googleUser);
|
||||
if (box.read(BoxName.countryCode) == 'Egypt') {
|
||||
Get.to(() => SmsSignupEgypt());
|
||||
} else if (box.read(BoxName.countryCode) == 'Jordan') {
|
||||
Get.to(() => EgyptCardAI());
|
||||
}
|
||||
}
|
||||
return googleUser;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<GoogleSignInAccount?> signInFromLogin() async {
|
||||
try {
|
||||
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
|
||||
|
||||
if (googleUser != null) {
|
||||
// Handle sign-up and store user information
|
||||
await _handleSignUp(googleUser);
|
||||
|
||||
// Retrieve driverID and emailDriver with added validation
|
||||
final driverID =
|
||||
(box.read(BoxName.driverID)!.toString()) ?? 'Unknown ID';
|
||||
final emailDriver =
|
||||
(box.read(BoxName.emailDriver)!.toString()) ?? 'Unknown Email';
|
||||
|
||||
// Debug print statements
|
||||
print('Driver ID: $driverID');
|
||||
print('Driver Email: $emailDriver');
|
||||
|
||||
// Check if driverID is a valid numeric string
|
||||
// if (driverID != 'Unknown ID' && int.tryParse(driverID) != null) {
|
||||
await Get.find<LoginDriverController>()
|
||||
.loginWithGoogleCredential(driverID, emailDriver);
|
||||
// }
|
||||
// else {
|
||||
// print('Invalid driverID format: $driverID');
|
||||
// Get.snackbar('Login Error', 'Invalid driver ID format.',
|
||||
// backgroundColor: AppColor.redColor);
|
||||
// }
|
||||
}
|
||||
|
||||
return googleUser;
|
||||
} catch (error, stackTrace) {
|
||||
mySnackeBarError('$error');
|
||||
CRUD.addError(error.toString(), stackTrace.toString(),
|
||||
'GoogleSignInAccount?> signInFromLogin()');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> _handleSignUp(GoogleSignInAccount user) async {
|
||||
// Store driver information
|
||||
box.write(BoxName.driverID,
|
||||
(user.id) ?? 'Unknown ID'); // Ensure there's a fallback value
|
||||
box.write(BoxName.emailDriver, (user.email) ?? 'Unknown Email');
|
||||
}
|
||||
|
||||
// Method to handle Google Sign-Out
|
||||
static Future<void> signOut() async {
|
||||
try {
|
||||
await _googleSignIn.signOut();
|
||||
await _handleSignOut();
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
static Future<void> _handleSignOut() async {
|
||||
// Clear stored driver information
|
||||
box.erase();
|
||||
storage.deleteAll();
|
||||
Get.offAll(OnBoardingPage());
|
||||
// Perform any additional sign-out tasks or API calls here
|
||||
// For example, you can notify your server about the user sign-out
|
||||
}
|
||||
|
||||
// Method to get the current signed-in user
|
||||
static GoogleSignInAccount? getCurrentUser() {
|
||||
return _googleSignIn.currentUser;
|
||||
}
|
||||
}
|
||||
// import 'dart:async';
|
||||
// import 'dart:async';
|
||||
// import 'dart:convert';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:http/http.dart' as http;
|
||||
// import 'package:sefer_driver/constant/box_name.dart';
|
||||
// import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
|
||||
// import 'package:sefer_driver/main.dart';
|
||||
// import 'package:sefer_driver/views/auth/captin/ai_page.dart';
|
||||
// import 'package:sefer_driver/views/auth/captin/cards/sms_signup.dart';
|
||||
// import 'package:sefer_driver/views/home/on_boarding_page.dart';
|
||||
// import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
// import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
// import '../functions/add_error.dart';
|
||||
|
||||
// /// Helper class to manage Google Sign-In via an external browser and polling.
|
||||
// class GoogleSignInHelper {
|
||||
// // URLs for your server endpoints
|
||||
// static const String _startLoginUrl =
|
||||
// 'https://api.tripz-egypt.com/tripz/auth/google_auth/login.php';
|
||||
// static const String _checkStatusUrl =
|
||||
// 'https://api.tripz-egypt.com/tripz/auth/google_auth/check_status.php';
|
||||
|
||||
// static Future<void> _initiateSignIn(
|
||||
// Function(Map<String, dynamic> userData) onSignInSuccess) async {
|
||||
// try {
|
||||
// // Show a loading dialog to the user
|
||||
// Get.dialog(
|
||||
// const Center(
|
||||
// child: Material(
|
||||
// color: Colors.transparent,
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// CircularProgressIndicator(),
|
||||
// SizedBox(height: 16),
|
||||
// Text(
|
||||
// "Waiting for browser sign-in...",
|
||||
// style: TextStyle(color: Colors.white, fontSize: 16),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// barrierDismissible: false,
|
||||
// );
|
||||
|
||||
// // 1. Get the auth URL and login token from the server
|
||||
// final startResponse = await http.get(Uri.parse(_startLoginUrl));
|
||||
// if (startResponse.statusCode != 200) {
|
||||
// throw Exception('Failed to start login process.');
|
||||
// }
|
||||
|
||||
// final startData = jsonDecode(startResponse.body);
|
||||
// final String authUrl = startData['authUrl'];
|
||||
// final String loginToken = startData['loginToken'];
|
||||
|
||||
// // 2. Launch the URL in an external browser
|
||||
// if (!await launchUrl(Uri.parse(authUrl),
|
||||
// mode: LaunchMode.externalApplication)) {
|
||||
// throw Exception('Could not launch browser.');
|
||||
// }
|
||||
|
||||
// // 3. Start polling the server for status
|
||||
// await _startPolling(loginToken, onSignInSuccess);
|
||||
// } catch (e) {
|
||||
// addError(e.toString(), '_initiateSignIn');
|
||||
// mySnackeBarError('Sign-in failed: ${e.toString()}');
|
||||
// // Close the loading dialog on error
|
||||
// if (Get.isDialogOpen ?? false) {
|
||||
// Get.back();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// static Future<void> _startPolling(String loginToken,
|
||||
// Function(Map<String, dynamic> userData) onSignInSuccess) async {
|
||||
// Timer? poller;
|
||||
// const int maxAttempts = 30; // Poll for 60 seconds (30 attempts * 2s)
|
||||
// int attempts = 0;
|
||||
|
||||
// poller = Timer.periodic(const Duration(seconds: 2), (timer) async {
|
||||
// if (attempts >= maxAttempts) {
|
||||
// timer.cancel();
|
||||
// if (Get.isDialogOpen ?? false) Get.back();
|
||||
// mySnackeBarError('Sign-in timed out. Please try again.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// attempts++;
|
||||
|
||||
// try {
|
||||
// final statusResponse = await http.post(
|
||||
// Uri.parse(_checkStatusUrl),
|
||||
// headers: {'Content-Type': 'application/json'},
|
||||
// body: jsonEncode({'loginToken': loginToken}),
|
||||
// );
|
||||
|
||||
// if (statusResponse.statusCode == 200) {
|
||||
// final statusData = jsonDecode(statusResponse.body);
|
||||
// if (statusData['status'] == 'success') {
|
||||
// timer.cancel();
|
||||
// if (Get.isDialogOpen ?? false) Get.back();
|
||||
// // Success!
|
||||
// onSignInSuccess(statusData['userData']);
|
||||
// }
|
||||
// // If status is 'pending', do nothing and wait for the next poll.
|
||||
// }
|
||||
// } catch (e) {
|
||||
// // Handle polling errors silently or log them
|
||||
// debugPrint("Polling error: $e");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// /// Triggers the sign-in process for a new user.
|
||||
// static Future<void> signIn() async {
|
||||
// await _initiateSignIn((userData) async {
|
||||
// debugPrint('Sign-in success data: $userData');
|
||||
// await _handleSignUp(userData);
|
||||
// if (box.read(BoxName.countryCode) == 'Egypt') {
|
||||
// Get.offAll(() => SmsSignupEgypt());
|
||||
// } else if (box.read(BoxName.countryCode) == 'Jordan') {
|
||||
// Get.offAll(() => AiPage());
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
// /// Triggers the sign-in process for an existing user.
|
||||
// static Future<void> signInFromLogin() async {
|
||||
// await _initiateSignIn((userData) async {
|
||||
// debugPrint('Sign-in from login success data: $userData');
|
||||
// await _handleSignUp(userData);
|
||||
|
||||
// final driverID = userData['id']?.toString() ?? 'Unknown ID';
|
||||
// final emailDriver = userData['email']?.toString() ?? 'Unknown Email';
|
||||
|
||||
// debugPrint('Driver ID from server: $driverID');
|
||||
// debugPrint('Driver Email from server: $emailDriver');
|
||||
|
||||
// await Get.find<LoginDriverController>()
|
||||
// .loginWithGoogleCredential(driverID, emailDriver);
|
||||
// });
|
||||
// }
|
||||
|
||||
// /// Stores user information received from the server.
|
||||
// static Future<void> _handleSignUp(Map<String, dynamic> userData) async {
|
||||
// box.write(BoxName.driverID, userData['id'] ?? 'Unknown ID');
|
||||
// box.write(BoxName.emailDriver, userData['email'] ?? 'Unknown Email');
|
||||
// }
|
||||
|
||||
// /// Clears local data.
|
||||
// static Future<void> signOut() async {
|
||||
// box.remove(BoxName.driverID);
|
||||
// box.remove(BoxName.emailDriver);
|
||||
// box.remove(BoxName.lang);
|
||||
// box.remove(BoxName.nameDriver);
|
||||
// box.remove(BoxName.passengerID);
|
||||
// box.remove(BoxName.phoneDriver);
|
||||
// box.remove(BoxName.tokenFCM);
|
||||
// box.remove(BoxName.tokens);
|
||||
// box.remove(BoxName.carPlate);
|
||||
// box.remove(BoxName.lastNameDriver);
|
||||
// box.remove(BoxName.agreeTerms);
|
||||
// box.remove(BoxName.tokenDriver);
|
||||
// box.remove(BoxName.countryCode);
|
||||
// box.remove(BoxName.accountIdStripeConnect);
|
||||
// box.remove(BoxName.phoneVerified);
|
||||
// Get.offAll(() => OnBoardingPage());
|
||||
// }
|
||||
// }
|
||||
@@ -1,117 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:sefer_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:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_driver/controller/functions/secure_storage.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_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();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import '../../models/model/onboarding_model.dart';
|
||||
import '../../views/auth/captin/login_captin.dart';
|
||||
|
||||
abstract class OnBoardingController extends GetxController {
|
||||
void next();
|
||||
void onPageChanged(int index);
|
||||
}
|
||||
|
||||
class OnBoardingControllerImp extends OnBoardingController {
|
||||
late PageController pageController;
|
||||
int currentPage = 0;
|
||||
bool isLastPage = false; // لتغيير نص الزر أو سلوكه
|
||||
|
||||
@override
|
||||
void next() {
|
||||
currentPage++;
|
||||
if (currentPage > onBoardingList.length - 1) {
|
||||
_completeOnBoarding();
|
||||
} else {
|
||||
pageController.animateToPage(
|
||||
currentPage,
|
||||
duration: const Duration(milliseconds: 600), // مدة معتدلة
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _completeOnBoarding() async {
|
||||
await box.write(BoxName.onBoarding, 'yes');
|
||||
// استخدام transition ناعم لمنع الرمش
|
||||
Get.offAll(() => const LoginCaptin(),
|
||||
transition: Transition.fadeIn,
|
||||
duration: const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
@override
|
||||
void onPageChanged(int index) {
|
||||
currentPage = index;
|
||||
isLastPage = index == onBoardingList.length - 1;
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
pageController = PageController();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
pageController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,770 +0,0 @@
|
||||
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:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_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 driver’s license has expired.'.tr
|
||||
// ,
|
||||
// snackPosition: SnackPosition.BOTTOM,
|
||||
// backgroundColor: Colors.red,
|
||||
// colorText: Colors.white);
|
||||
// return; // Stop progression
|
||||
// }
|
||||
}
|
||||
} else if (currentPage.value == 1) {
|
||||
// Validate Step 2
|
||||
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) {
|
||||
Get.snackbar('Error'.tr, '${'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'];
|
||||
|
||||
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)}';
|
||||
|
||||
final req = http.MultipartRequest('POST', registerUri);
|
||||
req.headers.addAll({
|
||||
'Authorization': bearer,
|
||||
'X-HMAC-Auth': hmac,
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
if (colorHex != null && colorHex!.isNotEmpty) {
|
||||
_addField(fields, 'color_hex', colorHex!);
|
||||
}
|
||||
_addField(fields, 'owner',
|
||||
'${firstNameController.text} ${lastNameController.text}');
|
||||
|
||||
// ============================================================
|
||||
// 🔥 التعديل الجديد: إرسال الأرقام (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 {
|
||||
json = jsonDecode(resp.body) as Map<String, dynamic>;
|
||||
} catch (_) {}
|
||||
|
||||
if (resp.statusCode == 200 && json?['status'] == 'success') {
|
||||
Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
|
||||
backgroundColor: Colors.green, colorText: Colors.white);
|
||||
|
||||
// منطق التوكن والإشعارات وتسجيل الدخول...
|
||||
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();
|
||||
Get.snackbar('Error'.tr, msg,
|
||||
backgroundColor: Colors.red, colorText: Colors.white);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('Error'.tr, 'Error: $e',
|
||||
backgroundColor: Colors.red, colorText: Colors.white);
|
||||
} 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
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:sefer_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user