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:
Hamza-Ayed
2026-07-18 13:06:46 +03:00
co-authored by Claude Fable 5
parent 3ab74a13a8
commit de4bbd00ac
271 changed files with 1333 additions and 81068 deletions
@@ -1,662 +0,0 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:sefer_driver/controller/firebase/notification_service.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../constant/links.dart';
import '../../../controller/auth/captin/login_captin_controller.dart';
import '../../../controller/firebase/local_notification.dart';
import '../../../controller/functions/crud.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../home/Captin/home_captain/home_captin.dart';
import '../../widgets/mydialoug.dart'; // تأكد من إضافة هذه المكتبة
class AI extends GetxController {
// --- متغيرات الحالة ---
bool isLoading = false;
bool isEgypt = true; // لتحديد جنسية السائق
// --- متغيرات تخزين بيانات الوثائق ---
Map<String, dynamic> responseIdEgyptDriverLicense = {};
Map<String, dynamic> responseIdCardDriverEgyptFront = {};
Map<String, dynamic> responseIdCardDriverEgyptBack = {};
Map<String, dynamic> responseIdEgyptFront = {};
Map<String, dynamic> responseIdEgyptBack = {};
Map<String, dynamic> responseCriminalRecordEgypt = {};
// للمقيمين الأجانب
Map<String, dynamic> responseNonIdCardFront = {};
Map<String, dynamic> responseNonIdCardBack = {};
// --- دالة تغيير الجنسية ---
void changeNationality() {
isEgypt = !isEgypt;
update();
}
// ===========================================================================
// 1. الدالة الأساسية للذكاء الاصطناعي (رفع، تحقق، استخراج)
// ===========================================================================
Future<void> allMethodForAI(String type) async {
isLoading = true;
update();
try {
// 1. التقاط الصورة
final ImagePicker picker = ImagePicker();
final XFile? pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) {
isLoading = false;
update();
return;
}
// 2. معالجة الصورة (قص)
CroppedFile? croppedFile = await ImageCropper().cropImage(
sourcePath: pickedImage.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'ضبط الصورة',
toolbarColor: AppColor.primaryColor,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(
title: 'ضبط الصورة',
doneButtonTitle: 'تم',
cancelButtonTitle: 'إلغاء',
),
],
);
if (croppedFile == null) {
isLoading = false;
update();
return;
}
// 3. ضغط الصورة
File processedImage = File(croppedFile.path);
File compressedImage = await testCompressAndGetFile(
processedImage,
processedImage.path,
);
// 4. تجهيز الطلب
var request = http.MultipartRequest("POST",
Uri.parse('${AppLink.server}/auth/captin/ai_document_egypt.php'));
request.fields['type'] = type;
request.fields['driver_id'] =
box.read(BoxName.driverID)?.toString() ?? 'unknown';
// تأمين الهيدر من القيم الفارغة
String jwt = box.read(BoxName.jwt) ?? '';
String cleanJwt = r(jwt).split(AppInformation.addd)[0];
final headers = <String, String>{
'Authorization': 'Bearer $cleanJwt',
'X-HMAC-Auth': '${box.read(BoxName.hmac) ?? ''}',
};
request.headers.addAll(headers);
var stream = http.ByteStream(compressedImage.openRead());
var length = await compressedImage.length();
var multipartFile = http.MultipartFile('image', stream, length,
filename: basename(compressedImage.path));
request.files.add(multipartFile);
// 5. إرسال الطلب
Log.print("Uploading image... type: $type");
var response = await request.send();
var responseBody = await http.Response.fromStream(response);
Log.print("Response Code: ${response.statusCode}");
Log.print("Raw Response Body: ${responseBody.body}");
if (response.statusCode == 200) {
// تنظيف الرد من أي نصوص زائدة (PHP Logs)
String cleanJsonString = responseBody.body;
int firstBrace = cleanJsonString.indexOf('{');
int lastBrace = cleanJsonString.lastIndexOf('}');
if (firstBrace != -1 && lastBrace != -1) {
cleanJsonString =
cleanJsonString.substring(firstBrace, lastBrace + 1);
} else {
throw Exception("No valid JSON found in response");
}
var jsonResponse = jsonDecode(cleanJsonString);
if (jsonResponse['status'] == 'success') {
// --- التعديل هنا: استخراج البيانات بشكل صحيح ---
Map<String, dynamic> aiData = {};
// 1. محاولة قراءة البيانات من message.data (كما في اللوج الأخير)
if (jsonResponse['message'] is Map &&
jsonResponse['message'].containsKey('data')) {
aiData = jsonResponse['message']['data'];
}
// 2. محاولة قراءة البيانات من data مباشرة (احتياط)
else if (jsonResponse.containsKey('data') &&
jsonResponse['data'] is Map) {
aiData = jsonResponse['data'];
}
// 3. حالة أخيرة: إذا كانت الرسالة نفسها هي البيانات
else if (jsonResponse['message'] is Map) {
aiData = jsonResponse['message'];
}
if (aiData.containsKey('is_wrong_document') &&
aiData['is_wrong_document'] == true) {
String detectedEnglish = aiData['detected_type'] ?? '';
String detectedArabic = _translateDetectedType(detectedEnglish);
MyDialog().getDialog(
'وثيقة غير صحيحة'.tr,
'لقد قمت برفع: $detectedArabic\nالمطلوب هو: ${_getReadableName(type)}\n\nيرجى إعادة المحاولة.'
.tr,
() => Get.back(),
);
} else {
// النجاح: تخزين البيانات
_assignDataToVariable(type, aiData);
}
} else {
MyDialog().getDialog(
'فشل التحليل'.tr,
jsonResponse['message'] is String
? jsonResponse['message']
: 'لم يتم استخراج بيانات',
() => Get.back());
}
} else {
MyDialog().getDialog('خطأ في السيرفر'.tr, 'كود: ${response.statusCode}',
() => Get.back());
}
} catch (e, stacktrace) {
Log.print("Error inside allMethodForAI: $e");
Log.print("Stacktrace: $stacktrace");
MyDialog().getDialog(
'خطأ'.tr, 'حدث خطأ غير متوقع.\nالسبب: $e', () => Get.back());
} finally {
isLoading = false;
update();
}
}
// 2. معالجة الصورة (قص)
// ===========================================================================
// 2. دالة التسجيل الموحدة (السائق + السيارة) - Transaction
// ===========================================================================
Future<void> registerCompleteAccount() async {
isLoading = true;
update();
final loginDriverController = Get.put(LoginDriverController());
// 1. كلمة المرور
String passwordRaw = loginDriverController.passwordController.text.trim();
if (passwordRaw.isEmpty) {
passwordRaw =
'${box.read(BoxName.phoneDriver)}${box.read(BoxName.driverID)}';
}
// 2. التحقق من الهوية
String driverId = box.read(BoxName.driverID)?.toString() ?? '';
String email = box.read(BoxName.emailDriver)?.toString() ?? '';
String phone = box.read(BoxName.phoneDriver)?.toString() ?? '';
if (driverId.isEmpty || phone.isEmpty) {
isLoading = false;
update();
mySnackeBarError("Missing Registration Info".tr);
return;
}
// 3. استخراج الاسم الأول والأخير من (name_arabic)
// حسب ملف الـ AI الخاص بك، المفتاح هو 'name_arabic' للرخصة
String fullArabicName = (isEgypt
? responseIdEgyptDriverLicense['name_arabic']
: responseNonIdCardFront['full_name'])
?.toString() ??
'';
// تنظيف الاسم وتقسيمه
fullArabicName = fullArabicName.trim().replaceAll(RegExp(r'\s+'), ' ');
List<String> nameParts = fullArabicName.split(' ');
// إذا كان الاسم العربي فارغ، نحاول الإنجليزي أو أي اسم متاح
if (nameParts.isEmpty || nameParts[0].isEmpty) {
String altName = responseIdEgyptFront['full_name']?.toString() ?? '';
nameParts = altName.trim().split(' ');
}
String firstName = nameParts.isNotEmpty ? nameParts.first : '';
String lastName = nameParts.length > 1 ? nameParts.last : '';
// 4. تجهيز البيانات
Map<String, dynamic> driverData = {};
if (isEgypt) {
driverData = {
'is_egypt': '1',
'first_name': firstName, // تم التعديل
'last_name': lastName, // تم التعديل
'name_arabic': fullArabicName,
'national_number': responseIdEgyptDriverLicense['national_number'] ??
responseIdEgyptBack['nationalID'] ??
'',
'name_english': responseIdEgyptDriverLicense['name_english'] ?? '',
'gender': responseIdEgyptBack['gender'] ?? 'Male',
'license_type': responseIdEgyptDriverLicense['license_type'] ?? 'خاصة',
'license_categories': responseIdEgyptDriverLicense['license_type'] ??
'', // استخدام النوع كفئة إذا لم تتوفر الفئات
'birthdate': extractDOB(
responseIdEgyptDriverLicense['national_number'].toString()),
'address': responseIdEgyptFront['address'] ?? '',
'occupation': responseIdEgyptBack['occupation'] ?? 'Driver',
'religion': responseIdEgyptBack['religion'] ?? '',
'maritalStatus': responseIdEgyptBack['maritalStatus'] ?? '',
'card_id': responseIdEgyptBack['nationalID'] ?? '', // رقم البطاقة
'site': responseIdEgyptFront['address'] ?? '',
'education': responseIdEgyptBack['education'] ?? '',
'employmentType': '',
'bankCode': 'yet',
'accountBank': 'yet',
};
} else {
driverData = {
'is_egypt': '0',
'first_name': firstName,
'last_name': lastName,
'name_arabic': fullArabicName,
'national_number': responseNonIdCardFront['passport_no'] ?? '',
'name_english': '',
'gender': responseNonIdCardFront['gender'] ?? 'Male',
'license_type': 'Foreign',
'license_categories': 'Foreign',
'birthdate': responseNonIdCardFront['birthdate'] ?? '',
'address': responseNonIdCardFront['address'] ?? '',
'occupation': responseNonIdCardBack['workStatus'] ?? '',
'religion': responseNonIdCardFront['country'] ?? '',
'maritalStatus': '',
'card_id': responseNonIdCardFront['card_id'] ?? '',
'site': '',
'education': '',
'employmentType': '',
'bankCode': '',
'accountBank': '',
};
}
// إضافة البيانات الأساسية
driverData.addAll({
'id': driverId,
'email': email,
'phone': phone,
'password': passwordRaw,
});
// بيانات السيارة
Map<String, dynamic> carData = {
'vin': responseIdCardDriverEgyptBack['chassis'].toString(),
'car_plate': responseIdCardDriverEgyptFront['car_plate'].toString(),
'make': responseIdCardDriverEgyptBack['make'].toString(),
'model': responseIdCardDriverEgyptBack['model'].toString(),
'year': responseIdCardDriverEgyptBack['year'].toString(),
'expiration_date':
responseIdCardDriverEgyptFront['LicenseExpirationDate'].toString(),
'color': responseIdCardDriverEgyptBack['color'].toString(),
'color_hex': responseIdCardDriverEgyptBack['color_hex'].toString(),
'owner': responseIdCardDriverEgyptFront['owner'].toString(),
'displacement': responseIdCardDriverEgyptBack['displacement'].toString(),
'fuel': responseIdCardDriverEgyptBack['fuel'].toString(),
'registration_date':
responseIdCardDriverEgyptBack['inspection_date'].toString(),
};
// تحويل الكل إلى String
Map<String, dynamic> finalPayload = {};
driverData
.forEach((key, value) => finalPayload[key] = value?.toString() ?? '');
carData
.forEach((key, value) => finalPayload[key] = value?.toString() ?? '');
try {
Log.print("Sending Payload: $finalPayload");
var res = await CRUD().post(
link: '${AppLink.authCaptin}/register_complete.php',
payload: finalPayload);
Log.print("Raw Response: $res");
if (res == 'failure' || res == null) {
mySnackeBarError("Connection Failed".tr);
isLoading = false;
update();
return;
}
// 🟢 الإصلاح الرئيسي للكراش: التعامل مع الرد سواء كان Map أو String
Map<String, dynamic> statusResponse = {};
if (res is Map) {
statusResponse = Map<String, dynamic>.from(res);
} else if (res is String) {
try {
statusResponse = jsonDecode(res);
} catch (e) {
Log.print("JSON Parse Error: $e");
// في حال فشل الفك، نعتبره فشل
mySnackeBarError("Invalid Server Data".tr);
isLoading = false;
update();
return;
}
}
isLoading = false;
update();
if (statusResponse['status'] == 'success') {
box.write(BoxName.phoneVerified, '1');
mySnackbarSuccess("Registration Successful".tr);
await addToken();
NotificationService.sendNotification(
target: 'service',
title: 'New Driver',
body: 'New driver registered: $fullArabicName',
isTopic: true,
category: 'new_service_request',
);
Get.offAll(() => HomeCaptain());
} else {
String msg = statusResponse['message']?.toString() ?? "Error";
mySnackeBarError(msg.tr);
}
} catch (e) {
isLoading = false;
update();
Log.print("Critical Error: $e");
mySnackeBarError("Unexpected Error".tr);
}
}
// ===========================================================================
// 3. دوال مساعدة (Helpers)
// ===========================================================================
addToken() async {
String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
final payload = {
'token': (box.read(BoxName.tokenDriver)),
'captain_id': (box.read(BoxName.driverID)).toString(),
'fingerPrint': (fingerPrint).toString()
};
Log.print('payload for addToken: ${payload}');
CRUD().post(link: AppLink.addTokensDriver, payload: payload);
}
// ضغط الصورة
Future<File> testCompressAndGetFile(File file, String targetPath) async {
var newPath = targetPath
.replaceAll(".jpg", "_compressed.webp")
.replaceAll(".jpeg", "_compressed.webp");
var result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
newPath,
quality: 88,
format: CompressFormat.webp,
rotate: 0,
);
return File(result!.path);
}
// استخراج تاريخ الميلاد من الرقم القومي المصري
String extractDOB(String nationalId) {
if (nationalId.length != 14) return '';
String century = nationalId[0] == '2' ? '19' : '20';
String year = nationalId.substring(1, 3);
String month = nationalId.substring(3, 5);
String day = nationalId.substring(5, 7);
return "$century$year-$month-$day";
}
// توزيع البيانات القادمة من Gemini
void _assignDataToVariable(String type, Map<String, dynamic> data) {
switch (type) {
case 'driver_license':
responseIdEgyptDriverLicense = data;
break;
case 'car_front':
responseIdCardDriverEgyptFront = data;
break;
case 'car_back':
responseIdCardDriverEgyptBack = data;
break;
case 'id_front':
responseIdEgyptFront = data;
break;
case 'id_back':
responseIdEgyptBack = data;
break;
case 'non_id_front':
responseNonIdCardFront = data;
break;
case 'non_id_back':
responseNonIdCardBack = data;
break;
case 'criminal_record':
responseCriminalRecordEgypt = data;
break;
}
update(); // تحديث الواجهة لعرض البيانات الجديدة
}
// ترجمة أنواع الوثائق للعربية لرسائل الخطأ
String _getReadableName(String type) {
switch (type) {
case 'driver_license':
return 'رخصة القيادة (الوجه)';
case 'car_front':
return 'رخصة السيارة (الوجه - اللوحة)';
case 'car_back':
return 'رخصة السيارة (الخلف - البيانات)';
case 'id_front':
return 'البطاقة الشخصية (الوجه)';
case 'id_back':
return 'البطاقة الشخصية (الخلف)';
case 'non_id_front':
return 'إقامة أجانب (الوجه)';
case 'non_id_back':
return 'إقامة أجانب (الخلف)';
case 'criminal_record':
return 'صحيفة الحالة الجنائية (الفيش)';
default:
return 'الوثيقة المطلوبة';
}
}
// ترجمة ما اكتشفه الذكاء الاصطناعي (لرسالة الخطأ)
String _translateDetectedType(String englishText) {
String lower = englishText.toLowerCase();
if (lower.contains('back') && lower.contains('vehicle')) {
return 'ظهر رخصة السيارة';
}
if (lower.contains('front') && lower.contains('vehicle')) {
return 'وجه رخصة السيارة';
}
if (lower.contains('back') && lower.contains('id')) {
return 'ظهر البطاقة الشخصية';
}
if (lower.contains('front') && lower.contains('id')) {
return 'وجه البطاقة الشخصية';
}
if (lower.contains('driver')) return 'رخصة قيادة';
if (lower.contains('car')) return 'رخصة سيارة';
return 'وثيقة مختلفة';
}
// التحقق من صحة البيانات (هل الحقول فارغة؟)
bool _isValidDocumentData(String type, Map<String, dynamic> data) {
if (data.containsKey('is_wrong_document') &&
data['is_wrong_document'] == true) {
return false;
}
switch (type) {
case 'driver_license':
return (data['national_number'] != null &&
data['national_number'].toString().length > 5) ||
(data['name_arabic'] != null);
case 'car_front':
return data['car_plate'] != null;
case 'car_back':
return data['chassis'] != null ||
data['motor'] != null ||
data['color'] != null;
case 'id_front':
return data['full_name'] != null || data['card_id'] != null;
case 'id_back':
return data['nationalID'] != null;
case 'criminal_record':
return data['NationalID'] != null || data['FullName'] != null;
default:
return true;
}
}
bool isInviteDriverFound = false;
Future updateInvitationCodeFromRegister() async {
var res = await CRUD().post(
link: AppLink.updateDriverInvitationDirectly,
payload: {
"inviterDriverPhone": '+2${box.read(BoxName.phoneDriver).toString()}',
"driverId": box.read(BoxName.driverID).toString(),
},
);
if (res != 'failure') {
isInviteDriverFound = true;
update();
// mySnackbarSuccess("Code approved".tr); // Localized success message
box.write(BoxName.isInstall, '1');
NotificationController().showNotification(
"Code approved".tr, "Code approved".tr, 'tone2', '');
// Notification text with dynamic token
NotificationService.sendNotification(
target: jsonDecode(res)['message'][0]['token'].toString(),
title: 'You have received a gift token!'.tr,
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
category: 'gift_token',
tone: 'tone2',
isTopic: false,
);
} else {
// mySnackeBarError(
// "You dont have invitation code".tr); // Localized error message
}
}
// ===========================================================================
// حفظ الفيش الجنائي فقط (تحديث حالة السائق)
// ===========================================================================
Future<void> saveCriminalRecordOnly() async {
isLoading = true;
update();
try {
// تجهيز البيانات
var payload = {
'driver_id': box.read(BoxName.driverID) ?? box.read(BoxName.driverID),
'status_result':
responseCriminalRecordEgypt['InspectionResult'] ?? 'Unknown',
'issue_date': responseCriminalRecordEgypt['IssueDate'] ?? '',
'national_id': responseCriminalRecordEgypt['NationalID'] ?? '',
// يفضل إرسال الاسم أيضاً للتأكيد
'full_name': responseCriminalRecordEgypt['FullName'] ?? '',
};
// الاتصال بملف PHP الجديد
// تأكد من إضافة هذا الرابط في ملف links.dart
var res = await CRUD().post(
link: '${AppLink.server}${'/auth/captin/update_criminal_record.php'}',
payload: payload);
var json = jsonDecode(res);
if (json['status'] == 'success') {
// نجاح
Get.snackbar(
"تم بنجاح".tr,
"تم التحقق من صحيفة الحالة الجنائية وحفظها في ملفك".tr,
backgroundColor: AppColor.greenColor,
colorText: Colors.white,
duration: const Duration(seconds: 4),
);
// العودة للصفحة الرئيسية أو الإعدادات
Get.offAll(() => HomeCaptain());
} else {
// فشل
Get.snackbar(
"تنبيه".tr,
"فشل الحفظ: ${json['message']}".tr,
backgroundColor: AppColor.redColor,
colorText: Colors.white,
);
}
} catch (e) {
Log.print("Error saving criminal record: $e");
Get.snackbar("خطأ".tr, "حدث خطأ أثناء الاتصال بالسيرفر".tr,
backgroundColor: AppColor.redColor, colorText: Colors.white);
} finally {
isLoading = false;
update();
}
}
// Future updatePassengersInvitation() async {
// if (formKey.currentState!.validate()) {
// var res = await CRUD().post(
// link: AppLink.updatePassengersInvitation,
// payload: {"inviteCode": invitationCodeController.text});
// if (res != 'failure') {
// isInviteDriverFound = true;
// update();
// mySnackbarSuccess("Code approved".tr);
// } else {
// mySnackeBarError("Code not approved".tr);
// }
// }
// }
}
// إضافة بسيطة لتسهيل قراءة أول وآخر عنصر في المصفوفة (Extension)
extension ListExtension<E> on List<E> {
E? get firstOrNull => isEmpty ? null : first;
E? get lastOrNull => isEmpty ? null : last;
}
@@ -1,83 +0,0 @@
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/style.dart';
import '../../../../controller/functions/camer_controller.dart';
import '../../../../controller/functions/ocr_controller.dart';
import '../../../widgets/my_scafold.dart';
class CameraLisencePage extends StatelessWidget {
CameraLisencePage.CameraLicensePage({super.key});
final CameraClassController cameraClassController =
Get.put(CameraClassController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Scan Driver License'.tr,
body: [
Column(children: [
Text(
'Please put your licence in these border'.tr,
style: AppStyle.title.copyWith(color: AppColor.greenColor),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: GetBuilder<CameraClassController>(
builder: (cameraClassController) =>
cameraClassController.isCameraInitialized
? Stack(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: CameraPreview(
cameraClassController.cameraController,
),
),
Positioned(
top: Get.height * .1,
right: 5,
left: 5,
child: Container(
height: Get.width * 3 / 4,
width: Get.width * .9,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.yellowColor, width: 2),
),
),
),
],
)
: Container(
decoration: AppStyle.boxDecoration,
height: Get.width * 3 / 4,
width: Get.width,
child: Center(
child: Text(
'Camera not initialized yet',
style: AppStyle.title,
),
),
),
),
),
const SizedBox(
height: 20,
),
MyElevatedButton(
title: 'Take Image'.tr,
onPressed: () {
ScanDocumentsByApi().scanDocumentsByApi();
},
)
]),
],
isleading: true,
);
}
}
@@ -1,169 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/controller/functions/ocr_controller.dart';
import '../../../constant/style.dart';
import '../../../controller/auth/captin/ml_google_doc.dart';
import '../../../controller/auth/captin/register_captin_controller.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
// class CarLicensePage extends StatelessWidget {
// CarLicensePage({super.key});
// // CarRegistrationRecognizerController carRegistrationRecognizerController =
// // Get.put(CarRegistrationRecognizerController());
// RegisterCaptainController registerCaptainController =
// Get.put(RegisterCaptainController());
// @override
// Widget build(BuildContext context) {
// Get.find<ScanDocumentsByApi>().uploadImagePortrate();
// return MyScafolld(
// title: 'Car License Card'.tr,
// body: [
// Positioned(
// top: 3,
// left: Get.width * .2,
// right: Get.width * .2,
// child: MyElevatedButton(
// title: 'Take Picture Of ID Card'.tr,
// onPressed: () async {
// //0vQRyaYYDWpsv73A5CZOknseK7S2sgwE
// //3vQRyaYYSWpmv69A58ZOkxmeK6M1mgwEDlXrXlBl
// //0pALdqDDYHvzp73Q59SIgbzjG7Z2zkhJXr
// // String? visionApi = AK.serverPHP;
// await carRegistrationRecognizerController.scanText();
// },
// )),
// Positioned(
// top: 50,
// child: SizedBox(
// height: Get.height * .6,
// width: Get.width,
// child: buildImageWithBoundingBoxes(),
// ),
// ),
// Positioned(
// bottom: Get.height * .2,
// left: Get.width * .2,
// right: Get.width * .2,
// child: MyElevatedButton(
// title: 'Register'.tr,
// onPressed: () async {
// // registerCaptainController.addLisence();
// // registerCaptainController.register();
// registerCaptainController.addRegisrationCarForDriver(
// carRegistrationRecognizerController.extracted['vin'],
// carRegistrationRecognizerController.extracted['make'],
// carRegistrationRecognizerController.extracted['model'],
// carRegistrationRecognizerController.extracted['year'],
// carRegistrationRecognizerController.extracted['color'],
// carRegistrationRecognizerController.extracted['owner'],
// carRegistrationRecognizerController
// .extracted['expiration_date'],
// carRegistrationRecognizerController
// .extracted['registration_date'],
// );
// },
// )),
// ],
// isleading: true);
// }
// }
// Widget buildImageWithBoundingBoxes() {
// Get.put(CarRegistrationRecognizerController());
// return GetBuilder<CarRegistrationRecognizerController>(
// builder: (carRegistrationRecognizerController) =>
// carRegistrationRecognizerController.image == null ||
// carRegistrationRecognizerController.extracted.isEmpty
// ? Center(
// child: Text(
// 'No image selected yet'.tr,
// style: AppStyle.headTitle2,
// ))
// : Column(
// children: [
// SizedBox(
// width: Get.width * .8,
// height: Get.width * .5,
// child: Image.file(
// File(carRegistrationRecognizerController
// .croppedFile!.path),
// // fit: BoxFit.fill,
// )),
// const SizedBox(
// height: 20,
// ),
// Container(
// decoration: AppStyle.boxDecoration,
// height: Get.width * .5,
// width: Get.width * .9,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// Text(
// '${'Made :'.tr}${carRegistrationRecognizerController.extracted['make']}',
// style: AppStyle.title,
// ),
// Text(
// '${'model :'.tr}${carRegistrationRecognizerController.extracted['model']}',
// style: AppStyle.title,
// ),
// ],
// ),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// Text(
// '${'VIN :'.tr}${carRegistrationRecognizerController.extracted['vin']}',
// style: AppStyle.title,
// ),
// Text(
// '${'year :'.tr}${carRegistrationRecognizerController.extracted['year']}',
// style: AppStyle.title,
// ),
// ],
// ),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// Column(
// children: [
// Text(
// 'expiration date :${carRegistrationRecognizerController.extracted['expiration_date']}',
// style: AppStyle.title,
// ),
// Text(
// 'registration date :${carRegistrationRecognizerController.extracted['registration_date']}',
// style: AppStyle.title,
// ),
// ],
// ),
// Text(
// 'color :${carRegistrationRecognizerController.extracted['color']}',
// style: AppStyle.title,
// ),
// ],
// ),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// children: [
// Text(
// 'owner :${carRegistrationRecognizerController.extracted['owner']}',
// style: AppStyle.title,
// ),
// ],
// ),
// ],
// ),
// )
// ],
// ));
// }
@@ -1,80 +0,0 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
class GeminiEgypt extends GetxController {
Map<String, dynamic> responseIdCardDriverEgypt = {};
String? responseIdCardDriverEgypt1;
Future geminiAiExtraction(String prompt, payload) async {
var requestBody = jsonEncode({
'contents': [
{
'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
{
'text': """
$payload
$prompt ,and make dates format like year-month-day"""
},
],
},
],
'generationConfig': {
'temperature': 0.4,
'topK': 32,
'topP': 1,
'maxOutputTokens': 4096,
'stopSequences': [],
},
'safety_settings': [
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_NONE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_NONE"
},
]
});
final response = await http.post(
Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?key=AIzaSyCyoLcSkDzK5_SMe00nhut56SSXWPR074w'),
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(response.body);
// Process the responseData as needed
var result = responseData['candidates'][0]['content']['parts'][0]['text'];
RegExp regex = RegExp(r"```json([^`]*)```");
String? jsonString =
regex.firstMatch(responseData.toString())?.group(1)?.trim();
if (jsonString != null) {
// Convert the JSON object to a String
jsonString = jsonEncode(json.decode(jsonString));
responseIdCardDriverEgypt1 = jsonString;
responseIdCardDriverEgypt = jsonDecode(responseIdCardDriverEgypt1!);
update();
return responseIdCardDriverEgypt;
} else {}
// Rest of your code...
} else {}
}
}
@@ -1,221 +0,0 @@
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/controller/auth/captin/register_captin_controller.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
import 'package:sefer_driver/views/widgets/my_textField.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../Rate/rate_app_page.dart';
class SmsSignupEgypt extends StatelessWidget {
SmsSignupEgypt({super.key});
@override
Widget build(BuildContext context) {
Get.put(RegisterCaptainController());
return MyScafolld(
title: 'Phone Check'.tr,
body: [
GetBuilder<RegisterCaptainController>(
builder: (registerCaptainController) {
return ListView(
// mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo at the top
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Image.asset(
'assets/images/logo.gif', // Make sure you have a logo image in your assets folder
height: 100,
),
),
// Message to the driver
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Text(
'We need your phone number to contact you and to help you receive orders.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
),
// Enter phone number text
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Text(
'Enter your phone number'.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
),
// Phone number input field
Padding(
padding: const EdgeInsets.all(16.0),
child: !registerCaptainController.isSent
? Form(
key: registerCaptainController.formKey3,
child: MyTextForm(
controller:
registerCaptainController.phoneController,
label: 'Enter your phone number'.tr,
hint: 'Enter your phone number'.tr,
type: TextInputType.phone),
)
: Container(
decoration: AppStyle.boxDecoration1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
registerCaptainController.phoneController.text,
style: AppStyle.title,
),
),
)),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: registerCaptainController.isSent
? Form(
key: registerCaptainController.formKey3,
child: MyTextForm(
controller: registerCaptainController.verifyCode,
label: '5 digit'.tr,
hint: '5 digit'.tr,
type: TextInputType.number),
)
: const SizedBox()),
// Submit button
registerCaptainController.isLoading
? const MyCircularProgressIndicator()
: Padding(
padding: const EdgeInsets.all(16.0),
child: MyElevatedButton(
onPressed: () async {
!registerCaptainController.isSent
? await registerCaptainController.sendOtpMessage()
: await registerCaptainController.verifySMSCode();
},
title: 'Submit'.tr,
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: MyElevatedButton(
kolor: AppColor.yellowColor,
title: "Rate Our App".tr,
onPressed: () {
Get.to(RatingScreen());
}),
),
// IconButton(
// onPressed: () async {
// // final plainText =
// // 'https://server.sefer.live/sefer.click/sefer';
// // debugPrint('Plain Text: $plainText');
// // Encrypt the data
// // final encryptedData = encryptionHelper.encryptData(plainText);
// // debugPrint('Encrypted: $encryptedData');
// // Decrypt the data
// // final decryptedData = encryptionHelper.decryptData(
// // '2unGmj8jSMFBfxqH8+GN'); // Use the encryptedData variable
// // debugPrint('Decrypted: $decryptedData');
// // box.remove('DriversSecure');
// var drivers0 = await CRUD().get(
// link:
// 'https://server.sefer.live/sefer.click/sefer/auth/captin/getAllDriverSecure.php',
// payload: {});
// var decodedDriver;
// if (drivers0 != 'failure') {
// decodedDriver = jsonDecode(drivers0);
// // // // box.write('DriversSecure', decodedDriver['message']);
// }
// var drivers = decodedDriver['message'];
// Log.print('drivers.length: ${drivers.length}');
// for (var i = 0; i < drivers.length; i++) {
// Log.print('id: ${drivers[i]['id']}');
// var payload = {
// "phone": encryptionHelper
// .encryptData(drivers[i]['phone'].toString()),
// "email": encryptionHelper
// .encryptData(drivers[i]['email'].toString()),
// "gender": encryptionHelper
// .encryptData(drivers[i]['gender'] ?? 'unknown'),
// "birthdate": encryptionHelper
// .encryptData(drivers[i]['birthdate'].toString()),
// "first_name": encryptionHelper
// .encryptData(drivers[i]['first_name'].toString()),
// "last_name": encryptionHelper
// .encryptData(drivers[i]['last_name'].toString()),
// "sosPhone": encryptionHelper
// .encryptData(drivers[i]['sosPhone'].toString()),
// // "name_english": encryptionHelper
// // .encryptData(drivers[i]['name_english'].toString()),
// // "last_name": encryptionHelper
// // .encryptData(drivers[i]['last_name'].toString()),
// // "sosPhone": encryptionHelper
// // .encryptData(drivers[i]['sosPhone'].toString()),
// // "address": encryptionHelper
// // .encryptData(drivers[i]['address'].toString()),
// // "card_id": encryptionHelper
// // .encryptData(drivers[i]['card_id'].toString()),
// // "occupation": encryptionHelper
// // .encryptData(drivers[i]['occupation'].toString()),
// // "religion": encryptionHelper
// // .encryptData(drivers[i]['religion'].toString()),
// // "site": encryptionHelper
// // .encryptData(drivers[i]['site'].toString()),
// // "education": encryptionHelper
// // .encryptData(drivers[i]['education'].toString()),
// // "accountBank": encryptionHelper
// // .encryptData(drivers[i]['accountBank'].toString()),
// // "employmentType": encryptionHelper
// // .encryptData(drivers[i]['employmentType'].toString()),
// // "maritalStatus": (drivers[i]['maritalStatus'].toString()),
// // "fullNameMaritial": encryptionHelper.encryptData(
// // drivers[i]['fullNameMaritial'].toString()),
// 'id': drivers[i]['id'].toString()
// };
// // print(drivers[i]['idn']);
// // if (drivers[i]['id'].toString() !=
// // '01002165502a9sHC1tbrUrUw') {
// var result = await CRUD().post(
// link:
// 'https://server.sefer.live/sefer.click/sefer/auth/captin/updateDriverSecure.php',
// payload: payload);
// if (result != 'failure') {
// print(result);
// } else {
// print('failure');
// }
// // Future.delayed(Duration(microseconds: 200));
// // }
// }
// MyDialog().getDialog('title', 'midTitle', () {
// Get.back();
// });
// },
// icon: const Icon(
// FontAwesome5.grin_tears,
// size: 29,
// color: AppColor.blueColor,
// ),
// ),
],
);
}),
],
isleading: false,
);
}
}
@@ -1,987 +0,0 @@
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:sefer_driver/constant/box_name.dart';
// import 'package:sefer_driver/controller/functions/audio_controller.dart';
// import 'package:sefer_driver/main.dart';
// import 'package:url_launcher/url_launcher.dart';
// import '../../../../constant/colors.dart';
// import '../../../../constant/links.dart';
// import '../../../../constant/style.dart';
// import '../../../../controller/functions/gemeni.dart';
// import '../../../../controller/functions/package_info.dart';
// import '../../../../controller/functions/tts.dart';
// import '../../../../print.dart';
// import '../../../widgets/elevated_btn.dart';
// import '../../../widgets/my_circular_indicator_timer.dart';
// import '../../../widgets/my_scafold.dart';
// import '../../../widgets/mydialoug.dart';
// // --- اقتراحات الألوان الجديدة ---
// // يمكنك تعريف هذه الألوان في ملف AppColor.dart الخاص بك
// class NewAppColor {
// static const Color primaryColor = Color(0xFF0D47A1); // أزرق داكن
// static const Color accentColor = Color(0xFF1976D2); // أزرق أفتح
// static const Color backgroundColor = Color(0xFFF5F7FA); // رمادي فاتح للخلفية
// static const Color cardColor = Colors.white;
// static const Color textColor = Color(0xFF333333); // أسود ناعم للنصوص
// static const Color subTextColor = Color(0xFF757575); // رمادي للنصوص الفرعية
// static const Color successColor = Color(0xFF2E7D32); // أخضر للنجاح
// static const Color errorColor = Color(0xFFC62828); // أحمر للخطأ
// static const Color borderColor = Color(0xFFE0E0E0); // لون الحدود
// }
// // --- اقتراحات للخطوط ---
// // يمكنك استخدام حزمة google_fonts وتعيين الخط 'Cairo' أو 'Tajawal' للتطبيق
// class NewAppStyle {
// static TextStyle get headlineStyle {
// return const TextStyle(
// fontFamily: 'Cairo', // اسم الخط المقترح
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: NewAppColor.primaryColor,
// );
// }
// static TextStyle get titleStyle {
// return const TextStyle(
// fontFamily: 'Cairo',
// fontSize: 16,
// fontWeight: FontWeight.bold,
// color: NewAppColor.textColor,
// );
// }
// static TextStyle get bodyStyle {
// return const TextStyle(
// fontFamily: 'Cairo',
// fontSize: 14,
// color: NewAppColor.subTextColor,
// height: 1.5,
// );
// }
// static TextStyle get valueStyle {
// return const TextStyle(
// fontFamily: 'Cairo',
// fontSize: 15,
// fontWeight: FontWeight.w600,
// color: NewAppColor.textColor,
// );
// }
// }
// class SyrianCardAI extends StatelessWidget {
// SyrianCardAI({super.key});
// final TextToSpeechController textToSpeechController =
// Get.put(TextToSpeechController());
// @override
// Widget build(BuildContext context) {
// Get.put(AI());
// WidgetsBinding.instance.addPostFrameCallback((_) {
// checkForUpdate(context);
// });
// return MyScafolld(
// // تم تغيير لون الخلفية للتصميم الجديد
// // backgroundColor: NewAppColor.backgroundColor,
// title: "Approve Driver Documents".tr,
// action: GetBuilder<AI>(builder: (cont) {
// return IconButton(
// onPressed: () {
// cont.isLoading = false;
// cont.update();
// },
// icon: const Icon(Icons.refresh, color: NewAppColor.primaryColor),
// );
// }),
// body: [
// GetBuilder<AI>(builder: (controller) {
// return controller.isLoading
// ? MyCircularProgressIndicatorWithTimer(
// isLoading: controller.isLoading,
// )
// : Padding(
// padding: const EdgeInsets.symmetric(
// horizontal: 12.0, vertical: 8.0),
// child: Column(
// children: [
// // --- زر "التالي" بتصميم جديد ---
// if (controller.licenceFrontSy.isNotEmpty &&
// controller.licenceBackSy.isNotEmpty &&
// (controller.idFrontSy.isNotEmpty) &&
// (controller.idBackSy.isNotEmpty) &&
// controller.vehicleFrontSy.isNotEmpty &&
// controller.vehicleBackSy.isNotEmpty)
// Padding(
// padding: const EdgeInsets.only(bottom: 16.0),
// child: MyElevatedButton(
// title: 'التالي'.tr,
// // استخدام اللون الجديد للنجاح
// kolor: NewAppColor.successColor,
// onPressed: () {
// controller.addDriverAndCarEgypt();
// },
// ),
// ),
// Expanded(
// child: ListView(
// children: [
// // --- فيديو الشرح بتصميم جديد ---
// VideoButton(),
// const SizedBox(height: 16),
// egyptDriverLicense(),
// const SizedBox(height: 16),
// syriaDriverLicenseBack(),
// const SizedBox(height: 16),
// syriaVehicleCardFront(),
// const SizedBox(height: 16),
// syriaVehicleCardBack(),
// const SizedBox(height: 16),
// syriaIdCardFront(),
// const SizedBox(height: 16),
// syriaDriverIDBack(),
// const SizedBox(height: 16),
// // egyptCriminalRecord(),
// // const SizedBox(height: 24),
// ],
// ),
// ),
// ],
// ),
// );
// }),
// // --- شاشة الموافقة بتصميم جديد ---
// Positioned(
// top: 0,
// bottom: 0,
// right: 0,
// left: 0,
// child: GetBuilder<AI>(builder: (controller) {
// return controller.approved == false
// // --- إضافة خلفية معتمة ---
// ? Container(
// color: Colors.black.withOpacity(0.6),
// child: Center(
// child: Container(
// margin: const EdgeInsets.all(24),
// padding: const EdgeInsets.all(24),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(20),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.2),
// blurRadius: 15,
// spreadRadius: 5,
// )
// ],
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Text(
// "Approve Driver Documents".tr,
// style: NewAppStyle.headlineStyle,
// ),
// const SizedBox(height: 16),
// Text(
// "To become a ride-sharing driver on the Intaleq app, you need to upload your driver's license, ID document, and car registration document. Our AI system will instantly review and verify their authenticity in just 2-3 minutes. If your documents are approved, you can start working as a driver on the Intaleq app. Please note, submitting fraudulent documents is a serious offense and may result in immediate termination and legal consequences."
// .tr,
// style: NewAppStyle.bodyStyle,
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 24),
// // --- زر الاستماع بتصميم جديد ---
// TextButton.icon(
// style: TextButton.styleFrom(
// foregroundColor: NewAppColor.accentColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(12),
// ),
// padding: const EdgeInsets.symmetric(
// horizontal: 20, vertical: 10),
// ),
// onPressed: () async {
// controller.startTimer();
// if (box.read(BoxName.lang) == 'ar') {
// await Get.put(AudioController())
// .playAudio1('assets/aggrement.wav');
// } else {
// await textToSpeechController.speakText(
// 'To become a ride-sharing driver on the Intaleq app...'
// .tr);
// }
// },
// icon: const Icon(Icons.volume_up_outlined,
// size: 30),
// label: Text('اضغط للاستماع'.tr,
// style: AppStyle.title.copyWith(
// color: NewAppColor.accentColor)),
// ),
// const SizedBox(height: 24),
// // --- أزرار الموافقة والرفض بتصميم جديد ---
// controller.isTimerComplete
// ? Row(
// children: [
// Expanded(
// child: MyElevatedButton(
// title: 'إلغاء'.tr,
// kolor: NewAppColor.errorColor,
// onPressed: () {
// MyDialog().getDialog(
// 'سيتم إلغاء التسجيل'.tr, '',
// () async {
// Get.back();
// Get.back();
// });
// }),
// ),
// const SizedBox(width: 16),
// Expanded(
// child: MyElevatedButton(
// title: 'أوافق'.tr,
// kolor: NewAppColor.successColor,
// onPressed: () {
// controller.setApproved();
// }),
// ),
// ],
// )
// : Column(
// children: [
// CircularProgressIndicator(
// value: controller.progressValue,
// color: NewAppColor.primaryColor,
// ),
// const SizedBox(height: 8),
// Text(
// '${'الوقت المتبقي'.tr}: ${controller.remainingSeconds} ${"ثانية".tr}',
// style: NewAppStyle.bodyStyle,
// ),
// ],
// ),
// ],
// ),
// ),
// ),
// )
// : const SizedBox();
// }),
// )
// ],
// isleading: true,
// );
// }
// // --- واجهة عرض بيانات الوثيقة ---
// Widget _buildDocumentDataCard({
// required String title,
// required VoidCallback onRefresh,
// required List<Widget> children,
// }) {
// return Card(
// elevation: 4.0,
// color: NewAppColor.cardColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(16.0),
// side: BorderSide(color: NewAppColor.borderColor, width: 1),
// ),
// shadowColor: NewAppColor.primaryColor.withOpacity(0.1),
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(title, style: NewAppStyle.headlineStyle),
// IconButton(
// onPressed: onRefresh,
// icon:
// const Icon(Icons.refresh, color: NewAppColor.accentColor),
// ),
// ],
// ),
// const Divider(
// height: 24, thickness: 1, color: NewAppColor.borderColor),
// ...children,
// ],
// ),
// ),
// );
// }
// // --- واجهة التقاط صورة الوثيقة ---
// Widget _buildCaptureCard({
// required String title,
// required String imagePath,
// required VoidCallback onTap,
// }) {
// return GestureDetector(
// onTap: onTap,
// child: Card(
// clipBehavior: Clip.antiAlias,
// elevation: 2.0,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(16.0),
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.stretch,
// children: [
// Image.asset(
// imagePath,
// height: Get.height * .20,
// fit: BoxFit.cover,
// // --- في حال لم يتم العثور على الصورة ---
// errorBuilder: (context, error, stackTrace) {
// return Container(
// height: Get.height * .20,
// color: NewAppColor.borderColor,
// child: const Icon(
// Icons.camera_alt_outlined,
// size: 50,
// color: NewAppColor.subTextColor,
// ),
// );
// },
// ),
// Container(
// padding: const EdgeInsets.all(16.0),
// color: NewAppColor.cardColor,
// child: Text(
// title,
// style: AppStyle.title,
// textAlign: TextAlign.center,
// ),
// ),
// ],
// ),
// ),
// );
// }
// // --- ويدجت لعرض معلومة (سطر) ---
// Widget _infoRow(String label, String? value, {Color? valueColor}) {
// return Padding(
// padding: const EdgeInsets.symmetric(vertical: 6.0),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text('$label: ', style: NewAppStyle.bodyStyle),
// Expanded(
// child: Text(
// value ?? 'غير متوفر',
// style: NewAppStyle.valueStyle.copyWith(color: valueColor),
// ),
// ),
// ],
// ),
// );
// }
// GetBuilder<AI> egyptDriverLicense() {
// return GetBuilder<AI>(
// builder: (ai) {
// if (ai.licenceFrontSy.isNotEmpty) {
// final data = ai.licenceFrontSy;
// DateTime? expiryDateTime;
// bool isExpired = false;
// if (data['expiry_date'] != null) {
// expiryDateTime = DateTime.tryParse(data['expiry_date']);
// isExpired = expiryDateTime != null &&
// expiryDateTime.isBefore(DateTime.now());
// }
// // بطاقة «رخصة القيادة – الوجه الأمامي» بتنسيق مضغوط وأيقونات
// return Card(
// elevation: 2,
// color: NewAppColor.cardColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(14),
// side: BorderSide(color: NewAppColor.borderColor, width: .8),
// ),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // العنوان + زر التحديث
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('رخصة القيادة – الوجه الأمامي'.tr,
// style: NewAppStyle.headlineStyle),
// IconButton(
// icon: const Icon(Icons.refresh,
// size: 20, color: NewAppColor.accentColor),
// splashRadius: 18,
// onPressed: () async => await ai
// .pickAndSendImage('driving_license_sy_front'),
// ),
// ],
// ),
// const SizedBox(height: 8),
// // سطر الاسم الكامل
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['name_arabic'] != null)
// _iconInfo(Icons.person, data['name_arabic']!),
// if (data['birth_place'] != null)
// _iconInfo(Icons.location_city, data['birth_place']!),
// ],
// ),
// const SizedBox(height: 8),
// // بقية الحقول داخل Wrap لتقليل الطول
// Wrap(
// spacing: 12,
// runSpacing: 8,
// children: [
// if (data['national_number'] != null)
// _iconInfo(Icons.badge, data['national_number']!),
// if (data['civil_registry'] != null)
// _iconInfo(Icons.location_on, data['civil_registry']!),
// if (data['blood_type'] != null)
// _iconInfo(Icons.water_drop, data['blood_type']!),
// if (data['birth_year'] != null)
// _iconInfo(Icons.calendar_today, data['birth_year']!),
// ],
// ),
// ],
// ),
// ),
// );
// }
// return _buildCaptureCard(
// title: 'التقط صورة لرخصة القيادة'.tr,
// imagePath: 'assets/images/1.png',
// onTap: () async {
// await ai.pickAndSendImage('driving_license_sy_front');
// },
// );
// },
// );
// }
// GetBuilder<AI> syriaDriverLicenseBack() {
// return GetBuilder<AI>(
// builder: (ai) {
// if (ai.licenceBackSy.isNotEmpty) {
// final data = ai.licenceBackSy;
// // صلاحية الرخصة
// final DateTime? expDate =
// DateTime.tryParse(data['expiry_date'] ?? '');
// final bool expired =
// expDate != null && expDate.isBefore(DateTime.now());
// final Color expColor = expired
// ? NewAppColor.errorColor // أحمر إن انتهت
// : NewAppColor.successColor; // أخضر إن صالحة
// return Card(
// elevation: 2,
// color: NewAppColor.cardColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(14),
// side: BorderSide(color: NewAppColor.borderColor, width: .8),
// ),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // عنوان البطاقة + زر الإنعاش
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('رخصة القيادة – الوجه الخلفي'.tr,
// style: NewAppStyle.headlineStyle),
// IconButton(
// splashRadius: 18,
// icon: const Icon(Icons.refresh,
// size: 20, color: NewAppColor.accentColor),
// onPressed: () async => await ai
// .pickAndSendImage('driving_license_sy_back'),
// ),
// ],
// ),
// const SizedBox(height: 8),
// // صفّ أول (الفئة + الرقم)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['license_category'] != null)
// _iconInfo(
// Icons.star, data['license_category']!), // D1 / D2…
// if (data['license_number'] != null)
// _iconInfo(
// Icons.confirmation_number, data['license_number']!),
// ],
// ),
// const SizedBox(height: 8),
// // صفّ ثانٍ (التواريخ) مع تلوين تاريخ الصلاحية
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['issue_date'] != null)
// _iconInfo(Icons.event, data['issue_date']!),
// if (data['expiry_date'] != null)
// _iconInfo(Icons.event_busy, data['expiry_date']!,
// valueColor: expColor),
// ],
// ),
// ],
// ),
// ),
// );
// }
// // بطاقة الالتقاط الافتراضية
// return _buildCaptureCard(
// title: 'التقط صورة الوجه الخلفي للرخصة'.tr,
// imagePath: 'assets/images/5.png',
// onTap: () async =>
// await ai.pickAndSendImage('driving_license_sy_back'),
// );
// },
// );
// }
// GetBuilder<AI> syriaDriverIDBack() {
// return GetBuilder<AI>(
// builder: (ai) {
// // استلمنا الحقول الأربعة فقط (governorate-address-gender-issue_date)
// if (ai.idBackSy.isNotEmpty) {
// final data = ai.idBackSy;
// return Card(
// elevation: 2,
// color: NewAppColor.cardColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(14),
// side: BorderSide(color: NewAppColor.borderColor, width: .8),
// ),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // العنوان + زر التحديث
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('بطاقة الهوية – الوجه الخلفي'.tr,
// style: NewAppStyle.headlineStyle),
// IconButton(
// splashRadius: 18,
// icon: const Icon(Icons.refresh,
// size: 20, color: NewAppColor.accentColor),
// onPressed: () async =>
// await ai.pickAndSendImage('id_back_sy'),
// ),
// ],
// ),
// const SizedBox(height: 8),
// // المحافظة + العنوان
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['governorate'] != null)
// _iconInfo(Icons.location_city, data['governorate']!),
// if (data['address'] != null)
// _iconInfo(Icons.home, data['address']!),
// ],
// ),
// const SizedBox(height: 8),
// // الجنس + تاريخ الإصدار
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['gender'] != null)
// _iconInfo(Icons.person, data['gender']!),
// if (data['issue_date'] != null)
// _iconInfo(Icons.event, data['issue_date']!),
// ],
// ),
// ],
// ),
// ),
// );
// }
// // البطاقة الافتراضية لالتقاط الصورة
// return _buildCaptureCard(
// title: 'التقط صورة للوجه الخلفي للهوية'.tr,
// imagePath: 'assets/images/4.png',
// onTap: () async => await ai.pickAndSendImage('id_back_sy'),
// );
// },
// );
// }
// /// عنصر (أيقونة + قيمة) مع لون نص مخصّص عند الحاجة
// Widget _iconInfo(IconData icon, String value, {Color? valueColor}) {
// return Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(icon, size: 18, color: NewAppColor.accentColor),
// const SizedBox(width: 4),
// Flexible(
// child: Text(
// value.tr,
// style: NewAppStyle.bodyStyle.copyWith(color: valueColor),
// overflow: TextOverflow.ellipsis,
// ),
// ),
// ],
// );
// }
// GetBuilder<AI> syriaIdCardFront() {
// // أبقِ الاسم القديم إذا أردت
// return GetBuilder<AI>(
// builder: (ai) {
// if (ai.idFrontSy.isNotEmpty) {
// // غيّر المفتاح حسب متغيرك
// final data = ai.idFrontSy;
// return Card(
// elevation: 2,
// color: NewAppColor.cardColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(14),
// side: BorderSide(color: NewAppColor.borderColor, width: .8),
// ),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // العنوان + زر التحديث
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('بطاقة الهوية – الوجه الأمامي'.tr,
// style: NewAppStyle.headlineStyle),
// IconButton(
// splashRadius: 18,
// icon: const Icon(Icons.refresh,
// size: 20, color: NewAppColor.accentColor),
// onPressed: () async => await ai.pickAndSendImage(
// 'id_front_sy', // أو id_front حسب تسمية end-point
// ),
// ),
// ],
// ),
// const SizedBox(height: 8),
// // سطر الاسم الكامل
// if (data['full_name'] != null)
// _iconInfo(Icons.person, data['full_name']!),
// const SizedBox(height: 8),
// // صفّ (الرقم الوطني + تاريخ الميلاد)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['national_number'] != null)
// _iconInfo(Icons.badge, data['national_number']!),
// if (data['dob'] != null)
// _iconInfo(Icons.cake, data['dob']!),
// ],
// ),
// const SizedBox(height: 8),
// // العنوان كامل بمفرده
// if (data['address'] != null)
// _iconInfo(Icons.home, data['address']!),
// ],
// ),
// ),
// );
// }
// // كارد الالتقاط الافتراضية
// return _buildCaptureCard(
// title: 'التقط صورة للوجه الأمامي للهوية'.tr,
// imagePath: 'assets/images/2.png',
// onTap: () async => await ai.pickAndSendImage('id_front_sy'),
// );
// },
// );
// }
// GetBuilder<AI> syriaVehicleCardFront() {
// // يمكنك إبقاء الاسم القديم إن شئت
// return GetBuilder<AI>(
// builder: (ai) {
// if (ai.vehicleFrontSy.isNotEmpty) {
// final data = ai.vehicleFrontSy;
// // تاريخ الفحص القادم للفحص الدوري (inspection_date)
// final DateTime? nextCheck =
// DateTime.tryParse(data['inspection_date'] ?? '');
// final bool overdue =
// nextCheck != null && nextCheck.isBefore(DateTime.now());
// final Color checkColor =
// overdue ? NewAppColor.errorColor : NewAppColor.successColor;
// return Card(
// elevation: 2,
// color: NewAppColor.cardColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(14),
// side: BorderSide(color: NewAppColor.borderColor, width: .8),
// ),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // العنوان + تحديث
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('رخصة المركبة – الوجه الأمامي'.tr,
// style: NewAppStyle.headlineStyle),
// IconButton(
// splashRadius: 18,
// icon: const Icon(Icons.refresh,
// size: 20, color: NewAppColor.accentColor),
// onPressed: () async => await ai.pickAndSendImage(
// 'vehicle_license_sy_front',
// ),
// ),
// ],
// ),
// const SizedBox(height: 8),
// // الصف الأوّل (لوحة + مالك)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['car_plate'] != null)
// _iconInfo(Icons.directions_car, data['car_plate']!),
// if (data['owner'] != null)
// _iconInfo(Icons.person, data['owner']!),
// ],
// ),
// const SizedBox(height: 8),
// // الصف الثاني (VIN + اللون)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['vin'] != null)
// _iconInfo(Icons.confirmation_num, data['vin']!),
// if (data['color'] != null)
// _iconInfo(Icons.palette, data['color']!),
// ],
// ),
// const SizedBox(height: 8),
// // الصف الثالث (تاريخ المنح + الفحص القادم)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['issue_date'] != null)
// _iconInfo(Icons.event, data['issue_date']!),
// if (data['inspection_date'] != null)
// _iconInfo(
// Icons.event_available, data['inspection_date']!,
// valueColor: checkColor),
// ],
// ),
// ],
// ),
// ),
// );
// }
// // لو لم تُرفع صورة بعد
// return _buildCaptureCard(
// title: 'التقط صورة لوجه رخصة المركبة'.tr,
// imagePath: 'assets/images/6.png',
// onTap: () async =>
// await ai.pickAndSendImage('vehicle_license_sy_front'),
// );
// },
// );
// }
// GetBuilder<AI> syriaVehicleCardBack() {
// // أبقِ الاسم القديم إن أردت
// return GetBuilder<AI>(
// builder: (ai) {
// if (ai.vehicleBackSy.isNotEmpty) {
// final data = ai.vehicleBackSy;
// return Card(
// elevation: 2,
// color: NewAppColor.cardColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(14),
// side: BorderSide(color: NewAppColor.borderColor, width: .8),
// ),
// child: Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // العنوان + زر تحديث
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('رخصة المركبة – الوجه الخلفي'.tr,
// style: NewAppStyle.headlineStyle),
// IconButton(
// splashRadius: 18,
// icon: const Icon(Icons.refresh,
// size: 20, color: NewAppColor.accentColor),
// onPressed: () async => await ai.pickAndSendImage(
// 'vehicle_license_sy_back',
// ),
// ),
// ],
// ),
// const SizedBox(height: 8),
// // صفّ (الشركة + الطراز)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['make'] != null)
// _iconInfo(Icons.factory, data['make']!),
// if (data['model'] != null)
// _iconInfo(Icons.directions_car_filled, data['model']!),
// ],
// ),
// const SizedBox(height: 8),
// // صفّ (سنة الصنع + اللون)
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// if (data['year'] != null)
// _iconInfo(Icons.calendar_today, data['year']!),
// if (data['fuel'] != null)
// _iconInfo(Icons.local_gas_station, data['fuel']!),
// ],
// ),
// const SizedBox(height: 8),
// // رقم الهيكل بمفرده (قد يكون طويلًا)
// if (data['chassis'] != null)
// _iconInfo(Icons.confirmation_num, data['chassis']!),
// ],
// ),
// ),
// );
// }
// // بطاقة الالتقاط الافتراضية
// return _buildCaptureCard(
// title: 'التقط صورة لخلفية رخصة المركبة'.tr,
// imagePath: 'assets/images/3.png',
// onTap: () async =>
// await ai.pickAndSendImage('vehicle_license_sy_back'),
// );
// },
// );
// }
// GetBuilder<AI> egyptCriminalRecord() {
// return GetBuilder<AI>(
// builder: (ai) {
// if (ai.responseCriminalRecordEgypt.isNotEmpty) {
// return _buildDocumentDataCard(
// title: 'صحيفة الحالة الجنائية'.tr,
// onRefresh: () async {
// await ai.allMethodForAI(
// (ai.prompts[5]['prompt'].toString()),
// AppLink.uploadEgypt,
// 'criminalRecord',
// );
// },
// children: [
// _infoRow('نتيجة الفحص'.tr,
// ai.responseCriminalRecordEgypt['InspectionResult']),
// _infoRow(
// 'الاسم الكامل'.tr,
// ai.responseCriminalRecordEgypt['FullName'],
// valueColor: (ai.responseCriminalRecordEgypt['FullName']) ==
// (ai.responseIdEgyptDriverLicense['name_arabic'])
// ? NewAppColor.successColor
// : NewAppColor.errorColor,
// ),
// _infoRow('الرقم القومي'.tr,
// ai.responseCriminalRecordEgypt['NationalID']),
// ],
// );
// }
// return _buildCaptureCard(
// title: 'التقط صورة لصحيفة الحالة الجنائية'.tr,
// imagePath: 'assets/images/6.png',
// onTap: () async {
// await ai.allMethodForAI(
// (ai.prompts[5]['prompt'].toString()),
// AppLink.uploadEgypt,
// 'criminalRecord',
// );
// },
// );
// },
// );
// }
// }
// // --- واجهة عرض الأقسام غير المصرية (بدون تغيير) ---
// // --- زر الفيديو بتصميم جديد ---
// class VideoButton extends StatelessWidget {
// final String videoUrl =
// "https://youtube.com/shorts/fC0RmYH5B_0?feature=share";
// @override
// Widget build(BuildContext context) {
// return Card(
// elevation: 2,
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
// child: InkWell(
// onTap: () async {
// if (await canLaunchUrl(Uri.parse(videoUrl))) {
// await launchUrl(Uri.parse(videoUrl));
// } else {
// Get.snackbar('خطأ', 'لا يمكن فتح الفيديو');
// }
// },
// borderRadius: BorderRadius.circular(12),
// child: Padding(
// padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Icon(Icons.play_circle_outline,
// color: NewAppColor.accentColor, size: 28),
// const SizedBox(width: 12),
// Text(
// "شاهد فيديو الشرح".tr,
// style: AppStyle.title.copyWith(color: NewAppColor.accentColor),
// ),
// ],
// ),
// ),
// ),
// );
// }
// }
@@ -1,100 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/functions/tts.dart';
import '../../../controller/home/captin/contact_us_controller.dart';
import '../../widgets/my_scafold.dart';
class ContactUsPage extends StatelessWidget {
ContactUsPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(ContactUsController());
return GetBuilder<ContactUsController>(builder: (controller) {
return MyScafolld(
title: "Contact Us".tr,
body: [
Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
decoration: AppStyle.boxDecoration1,
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image.asset('assets/images/logo.gif')),
IconButton(
onPressed: () async {
Get.put(TextToSpeechController()).speakText(
'Intaleq is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.'
.tr);
},
icon: const Icon(Icons.headphones),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Intaleq is the safest ride-sharing app that introduces many features for both captains and passengers. We offer the lowest commission rate of just 8%, ensuring you get the best value for your rides. Our app includes insurance for the best captains, regular maintenance of cars with top engineers, and on-road services to ensure a respectful and high-quality experience for all users.'
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
],
),
),
const SizedBox(
height: 30,
),
Container(
decoration: AppStyle.boxDecoration1,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"You can contact us during working hours from 10:00 - 17:00."
.tr,
style: AppStyle.title,
textAlign: TextAlign.center,
),
),
),
InkWell(
onTap: () => controller.showContactDialog(context),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Icon(
Icons.phone,
color: AppColor.blueColor,
),
Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
Icon(
Icons.email,
color: AppColor.redColor,
),
],
),
),
const SizedBox(
height: 30,
)
],
),
)
],
isleading: true);
});
}
}
@@ -1,216 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../main.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/mydialoug.dart';
import 'ai_eg_controller.dart';
class CriminalDocumemtPage extends StatelessWidget {
const CriminalDocumemtPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(AI()); // التأكد من وجود الكنترولر
return MyScafolld(
title: "صحيفة الحالة الجنائية".tr, // تعريب العنوان
isleading: true,
body: [
GetBuilder<AI>(builder: (controller) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// رسالة توضيحية للسائق
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.blue.withOpacity(0.3)),
),
child: Row(
children: [
const Icon(Icons.info_outline, color: Colors.blue),
const SizedBox(width: 10),
Expanded(
child: Text(
"هذه الوثيقة تساعد في توثيق حسابك وزيادة فرص قبول الرحلات."
.tr,
style: const TextStyle(fontSize: 12),
),
),
],
),
),
const SizedBox(height: 20),
// بطاقة رفع الفيش
_buildCriminalRecordCard(controller),
const SizedBox(height: 40),
// زر الحفظ (يظهر فقط إذا تم الرفع والتحقق)
if (controller.responseCriminalRecordEgypt.isNotEmpty)
MyElevatedButton(
title: 'تأكيد وحفظ'.tr,
// width: double.infinity,
onPressed: () => _handleSave(controller),
),
],
),
);
})
],
);
}
// --- Widget بطاقة الرفع والعرض ---
Widget _buildCriminalRecordCard(AI ai) {
bool hasData = ai.responseCriminalRecordEgypt.isNotEmpty;
if (hasData) {
var data = ai.responseCriminalRecordEgypt;
// جلب اسم السائق المسجل حالياً في التطبيق (من الـ Box أو السيرفر)
// ملاحظة: تأكد أنك تحفظ الاسم العربي في BoxName.nameArabic عند تسجيل الدخول
String currentDriverName = box.read(BoxName.nameArabic) ?? '';
// مقارنة تقريبية (إزالة المسافات)
bool isNameMatch = _compareNames(data['FullName'], currentDriverName);
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("البيانات المستخرجة".tr, style: AppStyle.headTitle2),
IconButton(
icon: const Icon(Icons.edit, color: AppColor.primaryColor),
onPressed: () => ai.allMethodForAI('criminal_record'),
)
],
),
const Divider(),
_infoRow("الاسم في الفيش".tr, data['FullName'],
isError: !isNameMatch),
if (!isNameMatch)
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"الاسم لا يطابق اسمك المسجل: $currentDriverName".tr,
style: const TextStyle(color: Colors.red, fontSize: 10),
),
),
_infoRow("الرقم القومي".tr, data['NationalID']),
_infoRow("النتيجة".tr, data['InspectionResult'],
isSuccess:
data['InspectionResult'].toString().contains("لا يوجد")),
_infoRow("تاريخ الإصدار".tr, data['IssueDate']),
],
),
),
);
}
// حالة الانتظار (زر الرفع)
return GestureDetector(
onTap: () => ai.allMethodForAI('criminal_record'),
child: Container(
height: 200,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(15),
border: Border.all(
color: Colors.grey[300]!, width: 2, style: BorderStyle.solid),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/6.png',
height: 80, color: Colors.grey), // أيقونة بصمة أو مستند
const SizedBox(height: 15),
Text(
"اضغط لرفع صورة الفيش".tr,
style: AppStyle.title.copyWith(color: Colors.grey[600]),
),
],
),
),
);
}
Widget _infoRow(String label, String? value,
{bool isError = false, bool isSuccess = false}) {
Color textColor = Colors.black87;
if (isError) textColor = Colors.red;
if (isSuccess) textColor = Colors.green;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(color: Colors.grey)),
Expanded(
child: Text(
value ?? "---",
textAlign: TextAlign.end,
style: TextStyle(fontWeight: FontWeight.bold, color: textColor),
),
),
],
),
);
}
// --- منطق المقارنة والحفظ ---
bool _compareNames(String? extractedName, String storedName) {
if (extractedName == null) return false;
// تنظيف الأسماء (إزالة مسافات، توحيد الهمزات) للمقارنة الدقيقة
String clean1 = extractedName
.replaceAll(' ', '')
.replaceAll('أ', 'ا')
.replaceAll('إ', 'ا');
String clean2 = storedName
.replaceAll(' ', '')
.replaceAll('أ', 'ا')
.replaceAll('إ', 'ا');
// نكتفي بمطابقة جزئية (مثلاً 50% من الأحرف) أو مطابقة كاملة حسب صرامة النظام
return clean1.contains(clean2) || clean2.contains(clean1);
}
void _handleSave(AI controller) async {
String currentDriverName = box.read(BoxName.nameArabic) ?? '';
String extractedName =
controller.responseCriminalRecordEgypt['FullName'] ?? '';
if (!_compareNames(extractedName, currentDriverName)) {
Get.defaultDialog(
title: "خطأ في التطابق".tr,
middleText:
"اسمك في الفيش لا يطابق الاسم المسجل في حسابك.\nيرجى التأكد من الوثيقة."
.tr,
confirmTextColor: Colors.white,
onConfirm: () => Get.back(),
);
return;
}
// إذا كان التطابق صحيحاً، نقوم برفع البيانات للسيرفر
// هنا نفترض وجود دالة في الكنترولر لحفظ الفيش فقط
await controller.saveCriminalRecordOnly();
}
}
@@ -1,176 +0,0 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart';
import '../../widgets/error_snakbar.dart';
class DriverCarController extends GetxController {
bool isLoading = false;
List cars = [];
// int? carId;
fetchCatrsForDrivers() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getNewCarsDrivers, payload: {
"driverID": box.read(BoxName.driverID).toString(),
});
if (res != 'failure') {
var d = jsonDecode(res)['message'];
cars = d;
// carId = cars.isEmpty ? 1 : cars.length + 1;
}
isLoading = false;
update();
}
Future<void> addNewCarToAccount({
required String vin,
required String plate,
required String make,
required String model,
required String year,
required String expiryDate,
required String color,
required String colorHex,
required String owner,
required String address,
required String inspectionDate,
required String displacement,
required String fuel,
}) async {
// إظهار Loading
Get.dialog(const Center(child: CircularProgressIndicator()),
barrierDismissible: false);
try {
var payload = {
'driver_id': box.read(BoxName.driverID), // معرف السائق الحالي
'vin': vin,
'car_plate': plate,
'make': make,
'model': model,
'year': year,
'expiration_date': expiryDate,
'color': color,
'color_hex': colorHex,
'owner': owner,
'address': address,
'displacement': displacement,
'fuel': fuel,
'registration_date': inspectionDate,
};
// الاتصال بسكريبت PHP الجديد
var res = await CRUD().post(
link: AppLink.addNewCarsDrivers,
payload: payload); // تأكد من إضافة الرابط في links.dart
Get.back(); // إغلاق الـ Loading
var json = jsonDecode(res);
if (json['status'] == 'success') {
Get.snackbar("Success".tr, "New car added successfully".tr,
backgroundColor: AppColor.greenColor, colorText: Colors.white);
// تحديث قائمة السيارات (إذا كان لديك دالة تجلب السيارات)
// getDriverCars();
Get.back(); // العودة للصفحة السابقة
} else {
Get.snackbar("Error".tr, json['message'].toString().tr,
backgroundColor: AppColor.redColor, colorText: Colors.white);
}
} catch (e) {
Get.back();
print("Add Car Error: $e");
Get.snackbar("Error".tr, "Connection failed".tr);
}
}
addCarsForDrivers(
String vin,
String carPlate,
String make,
String model,
String year,
String expirationDate,
String color,
String colorHex,
String address,
String owner,
String registrationDate,
String displacement,
String fuel) async {
var res = await CRUD().post(
link: AppLink.addRegisrationCar,
payload: {
"driverID": box.read(BoxName.driverID).toString(),
"vin": vin ?? 'unknown',
"car_plate": carPlate.toString(),
"make": make ?? 'unknown',
"model": model ?? 'unknown',
"year": year ?? 'unknown',
"expiration_date": expirationDate ?? 'unknown',
"color": color ?? 'unknown',
"owner": owner ?? 'unknown',
"color_hex": colorHex ?? '#000000',
"address": address ?? 'unknown',
"displacement": displacement ?? 'unknown',
"fuel": fuel ?? 'unknown',
"registration_date": registrationDate ?? 'unknown',
},
);
if (res != 'failure') {
mySnackbarSuccess('');
fetchCatrsForDrivers();
} else {
mySnackeBarError('');
}
}
// update carRegistration only and insert on it without tow column
Future<void> updateCarRegistration(String id, String driverID) async {
final body = {
'id': id,
'driverID': driverID,
};
// remove default before update
var response = await CRUD().post(
link: AppLink.makeDefaultCar,
payload: body,
);
if (response != 'failure') {
mySnackbarSuccess('Updated'.tr);
} else {
mySnackeBarError('Not updated'.tr);
}
}
//todo need review
removeCar(String carId) async {
isLoading = true;
update();
var res = await CRUD().post(link: AppLink.deleteNewCarsDrivers, payload: {
"id": carId.toString(),
});
if (res != 'failure') {
mySnackbarSuccess('deleted'.tr);
}
isLoading = false;
update();
}
@override
void onInit() {
fetchCatrsForDrivers();
super.onInit();
}
}
@@ -1,582 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../constant/box_name.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/style.dart';
import '../../../../controller/functions/package_info.dart';
import '../../../../controller/functions/tts.dart';
import '../../../../main.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_circular_indicator_timer.dart';
import '../../widgets/my_scafold.dart';
import 'ai_eg_controller.dart';
class EgyptCardAI extends StatelessWidget {
EgyptCardAI({super.key});
final TextToSpeechController textToSpeechController =
Get.put(TextToSpeechController());
@override
Widget build(BuildContext context) {
Get.put(AI());
WidgetsBinding.instance.addPostFrameCallback((_) {
checkForUpdate(context);
});
return MyScafolld(
title: 'Documents Verification'.tr,
isleading: true,
action: GetBuilder<AI>(builder: (cont) {
return IconButton(
onPressed: () {
cont.isLoading = false;
cont.update();
},
icon: const Icon(Icons.refresh_rounded),
);
}),
body: [
GetBuilder<AI>(builder: (controller) {
if (box.read(BoxName.isInstall) != null &&
box.read(BoxName.isInstall).toString() != '') {
controller.updateInvitationCodeFromRegister();
}
return controller.isLoading
? MyCircularProgressIndicatorWithTimer(
isLoading: controller.isLoading)
: SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10),
child: Column(
children: [
_buildHeaderSection(controller),
const SizedBox(height: 15),
// 1. Driver License
DocumentStatusCard(
title: "Driver License".tr,
icon: Icons.badge_outlined,
assetImage: 'assets/images/driver_license.png',
isUploaded:
controller.responseIdEgyptDriverLicense.isNotEmpty,
data: controller.responseIdEgyptDriverLicense,
onTap: () =>
controller.allMethodForAI('driver_license'),
isValid: _checkDriverLicenseValidity(controller),
children: _buildDriverLicenseDetails(controller),
),
// 2. Car License Front
DocumentStatusCard(
title: "Vehicle License (Front)".tr,
icon: Icons.directions_car_outlined,
assetImage: 'assets/images/car_front.png',
isUploaded: controller
.responseIdCardDriverEgyptFront.isNotEmpty,
data: controller.responseIdCardDriverEgyptFront,
onTap: () => controller.allMethodForAI('car_front'),
isValid: _checkCarFrontValidity(controller),
children: _buildCarFrontDetails(controller),
),
// 3. Car License Back
DocumentStatusCard(
title: "Vehicle License (Back)".tr,
icon: Icons.local_shipping_outlined,
assetImage: 'assets/images/car_back.png',
isUploaded:
controller.responseIdCardDriverEgyptBack.isNotEmpty,
data: controller.responseIdCardDriverEgyptBack,
onTap: () => controller.allMethodForAI('car_back'),
isValid: _checkCarBackValidity(controller),
children: _buildCarBackDetails(controller),
),
// 4. ID Front (Egypt / Non-Egypt)
DocumentStatusCard(
title: controller.isEgypt
? "National ID (Front)".tr
: "Residence Card (Front)".tr,
icon: Icons.credit_card,
assetImage: 'assets/images/id_front.png',
isUploaded: controller.isEgypt
? controller.responseIdEgyptFront.isNotEmpty
: controller.responseNonIdCardFront.isNotEmpty,
data: controller.isEgypt
? controller.responseIdEgyptFront
: controller.responseNonIdCardFront,
onTap: () => controller.allMethodForAI(
controller.isEgypt ? 'id_front' : 'non_id_front'),
isValid: true,
children: controller.isEgypt
? _buildIdFrontDetails(controller)
: _buildNonIdFrontDetails(controller),
),
// 5. ID Back (Egypt / Non-Egypt)
DocumentStatusCard(
title: controller.isEgypt
? "National ID (Back)".tr
: "Residence Card (Back)".tr,
icon: Icons.credit_card_outlined,
assetImage: 'assets/images/id_back.png',
isUploaded: controller.isEgypt
? controller.responseIdEgyptBack.isNotEmpty
: controller.responseNonIdCardBack.isNotEmpty,
data: controller.isEgypt
? controller.responseIdEgyptBack
: controller.responseNonIdCardBack,
onTap: () => controller.allMethodForAI(
controller.isEgypt ? 'id_back' : 'non_id_back'),
isValid: _checkIdBackValidity(controller),
children: controller.isEgypt
? _buildIdBackDetails(controller)
: _buildNonIdBackDetails(controller),
),
const SizedBox(height: 20),
_buildActionButtons(controller),
const SizedBox(height: 50),
],
),
);
}),
],
);
}
// --- Helper Widgets Methods ---
Widget _buildHeaderSection(AI controller) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.primaryColor.withOpacity(0.3)),
),
child: Row(
children: [
const Icon(Icons.info_outline, color: AppColor.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Text(
"Please upload clear photos. Ensure all corners are visible and no glare."
.tr,
style: AppStyle.title.copyWith(fontSize: 12),
),
),
VideoButton(),
],
),
);
}
Widget _buildActionButtons(AI controller) {
bool isAllUploaded = (controller.responseIdCardDriverEgyptBack.isNotEmpty &&
controller.responseIdCardDriverEgyptFront.isNotEmpty &&
(controller.responseIdEgyptFront.isNotEmpty ||
controller.responseNonIdCardFront.isNotEmpty) &&
(controller.responseIdEgyptBack.isNotEmpty ||
controller.responseNonIdCardBack.isNotEmpty) &&
controller.responseIdEgyptDriverLicense.isNotEmpty);
return Column(
children: [
if (isAllUploaded)
MyElevatedButton(
title: 'Verify & Register'.tr,
kolor: AppColor.greenColor,
onPressed: () {
addDriverAndCarEgypt(controller);
},
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Nationality: ".tr, style: AppStyle.title),
TextButton(
onPressed: () => controller.changeNationality(),
child: Text(
controller.isEgypt ? "Egyptian 🇪🇬" : "Foreigner 🌍",
style: AppStyle.title.copyWith(color: AppColor.primaryColor),
),
),
],
)
],
);
}
// --- Details Builders ---
List<Widget> _buildDriverLicenseDetails(AI ai) {
var data = ai.responseIdEgyptDriverLicense;
String licenseNID = data['national_number'] ?? '';
String idNID = ai.responseIdEgyptBack['nationalID'] ?? '';
bool isMatch =
licenseNID.isNotEmpty && idNID.isNotEmpty && licenseNID == idNID;
return [
_infoRow("License Type".tr, data['license_type']),
_infoRow("National No.".tr, licenseNID,
statusColor: isMatch ? AppColor.greenColor : AppColor.redColor,
isBold: true),
if (!isMatch && idNID.isNotEmpty)
Text("Does not match ID!".tr,
style: const TextStyle(color: Colors.red, fontSize: 10)),
_infoRow("Name".tr, data['name_arabic']),
_infoRow("Expiry".tr, data['expiry_date'],
statusColor:
_isDateExpired(data['expiry_date']) ? Colors.red : Colors.green),
];
}
List<Widget> _buildCarFrontDetails(AI ai) {
var data = ai.responseIdCardDriverEgyptFront;
return [
_infoRow("Plate Number".tr, data['car_plate'],
isBold: true, valueSize: 18),
_infoRow("Owner".tr, data['owner']),
_infoRow("License End".tr, data['LicenseExpirationDate'],
statusColor: _isDateExpired(data['LicenseExpirationDate'])
? Colors.red
: Colors.green),
];
}
List<Widget> _buildCarBackDetails(AI ai) {
var data = ai.responseIdCardDriverEgyptBack;
String hexColor = data['color_hex'] ?? '';
return [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: _infoRow("Car".tr,
"${data['make']} ${data['model']} ${data['year']}")),
if (hexColor.isNotEmpty)
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: _parseColor(hexColor),
shape: BoxShape.circle,
border: Border.all(color: Colors.grey)))
],
),
_infoRow("Chassis".tr, data['chassis']),
_infoRow("Color".tr, data['color']),
_infoRow("Inspection".tr, data['inspection_date'],
statusColor: _isDateExpired(data['inspection_date'])
? Colors.red
: Colors.green),
];
}
List<Widget> _buildIdFrontDetails(AI ai) {
var data = ai.responseIdEgyptFront;
return [
_infoRow("Name".tr, data['full_name']),
_infoRow("Address".tr, data['address']),
];
}
List<Widget> _buildNonIdFrontDetails(AI ai) {
var data = ai.responseNonIdCardFront;
return [
_infoRow("Name".tr, data['full_name']),
_infoRow("Passport".tr, data['passport_no']),
_infoRow("Nationality".tr, data['country']),
];
}
List<Widget> _buildIdBackDetails(AI ai) {
var data = ai.responseIdEgyptBack;
return [
_infoRow("National ID".tr, data['nationalID'], isBold: true),
_infoRow("Job".tr, data['occupation']),
_infoRow("Gender".tr, data['gender']),
_infoRow("Expiry".tr, data['expirationDate'],
statusColor: _isDateExpired(data['expirationDate'])
? Colors.red
: Colors.green),
];
}
List<Widget> _buildNonIdBackDetails(AI ai) {
var data = ai.responseNonIdCardBack;
return [
_infoRow("Type".tr, data['residencyType']),
_infoRow("Work".tr, data['workStatus']),
_infoRow("Expiry".tr, data['residencyExpirationDate'],
statusColor: _isDateExpired(data['residencyExpirationDate'])
? Colors.red
: Colors.green),
];
}
Widget _infoRow(String label, String? value,
{Color? statusColor, bool isBold = false, double valueSize = 14}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label,
style: AppStyle.title
.copyWith(color: Colors.grey[600], fontSize: 12)),
Expanded(
child: Text(
value ?? "---",
textAlign: TextAlign.end,
style: AppStyle.title.copyWith(
color: statusColor ?? Colors.black87,
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
fontSize: valueSize),
),
),
],
),
);
}
bool _isDateExpired(String? dateStr) {
if (dateStr == null || dateStr.isEmpty) return false;
final date = DateTime.tryParse(dateStr);
if (date == null) return false;
return date.isBefore(DateTime.now());
}
Color _parseColor(String hex) {
try {
return Color(int.parse(hex.replaceFirst('#', '0xff')));
} catch (e) {
return Colors.transparent;
}
}
// --- Validity Checkers ---
bool _checkDriverLicenseValidity(AI ai) =>
!_isDateExpired(ai.responseIdEgyptDriverLicense['expiry_date']);
bool _checkCarFrontValidity(AI ai) => !_isDateExpired(
ai.responseIdCardDriverEgyptFront['LicenseExpirationDate']);
bool _checkCarBackValidity(AI ai) =>
!_isDateExpired(ai.responseIdCardDriverEgyptBack['inspection_date']);
bool _checkIdBackValidity(AI ai) => !_isDateExpired(ai.isEgypt
? ai.responseIdEgyptBack['expirationDate']
: ai.responseNonIdCardBack['residencyExpirationDate']);
// --- Main Logic: Add Driver & Car ---
Future<void> addDriverAndCarEgypt(AI controller) async {
// تم إبقاء المنطق كما هو حسب طلبك
if (controller.isEgypt) {
String licenseNID =
controller.responseIdEgyptDriverLicense['national_number'] ?? '1';
String idNID = controller.responseIdEgyptBack['nationalID'] ?? '2';
licenseNID = licenseNID.replaceAll(' ', '');
idNID = idNID.replaceAll(' ', '');
if (licenseNID != idNID) {
_showErrorDialog(
'ID Mismatch'.tr,
"The National ID on your driver's license doesn't match your ID card."
.tr);
return;
}
}
await controller.registerCompleteAccount();
}
void _showErrorDialog(String title, String msg) {
Get.defaultDialog(
title: title,
titleStyle: const TextStyle(color: Colors.red),
content: Column(
children: [
const Icon(Icons.warning_amber_rounded,
size: 50, color: Colors.red),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(msg, textAlign: TextAlign.center),
),
],
),
confirm: MyElevatedButton(
title: "OK",
kolor: AppColor.primaryColor,
onPressed: () => Get.back(),
));
}
}
// -----------------------------------------------------------------------------
// NEW REDESIGNED CARD: Shows Clear Example Image
// -----------------------------------------------------------------------------
class DocumentStatusCard extends StatelessWidget {
final String title;
final IconData icon;
final String assetImage;
final bool isUploaded;
final Map<String, dynamic> data;
final VoidCallback onTap;
final List<Widget> children;
final bool isValid;
const DocumentStatusCard({
super.key,
required this.title,
required this.icon,
required this.assetImage,
required this.isUploaded,
required this.data,
required this.onTap,
required this.children,
required this.isValid,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
margin: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: isUploaded
? BorderSide(
color: isValid ? Colors.green.withOpacity(0.5) : Colors.red,
width: 2)
: BorderSide(color: Colors.grey.shade300),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. Header Row
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12),
child: Row(
children: [
Icon(icon, color: AppColor.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Text(
title,
style: AppStyle.headTitle2.copyWith(fontSize: 16),
),
),
if (isUploaded)
Icon(
isValid ? Icons.check_circle : Icons.error_outline,
color: isValid ? Colors.green : Colors.red,
)
],
),
),
// 2. Main Content Area (Visual)
if (!isUploaded) ...[
GestureDetector(
onTap: onTap,
child: Container(
height: 180, // ارتفاع كبير لجعل الصورة واضحة
width: double.infinity,
margin: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade300, width: 2),
image: DecorationImage(
image: AssetImage(assetImage),
// نستخدم cover لتعبئة المكان، أو fill لشد الصورة
// cover غالباً هو الأجمل والأوضح
fit: BoxFit.cover,
),
),
child: Stack(
children: [
// زر "التقط الصورة" في المنتصف فوق الصورة
Center(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6),
borderRadius: BorderRadius.circular(30),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.camera_alt_outlined,
color: Colors.white),
const SizedBox(width: 8),
Text(
"Tap to Scan".tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
),
),
],
),
),
),
const SizedBox(height: 16),
] else ...[
// 3. Uploaded Details Area
InkWell(
onTap: onTap, // للسماح بإعادة الرفع عند الضغط على البيانات
child: Container(
color: isValid
? Colors.green.withOpacity(0.05)
: Colors.red.withOpacity(0.05),
padding: const EdgeInsets.all(16),
child: Column(
children: [
...children,
const Divider(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.edit, size: 16, color: Colors.grey[600]),
const SizedBox(width: 5),
Text(
"Tap to re-upload".tr,
style:
TextStyle(color: Colors.grey[600], fontSize: 12),
),
],
)
],
),
),
),
],
],
),
);
}
}
class VideoButton extends StatelessWidget {
final String videoUrl =
"https://youtube.com/shorts/fC0RmYH5B_0?feature=share";
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () async {
if (await canLaunchUrl(Uri.parse(videoUrl))) {
await launchUrl(Uri.parse(videoUrl));
}
},
icon: const Icon(Icons.play_circle_fill, color: Colors.red),
tooltip: "Watch Tutorial",
);
}
}
@@ -1,80 +0,0 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
class GeminiEgypt extends GetxController {
Map<String, dynamic> responseIdCardDriverEgypt = {};
String? responseIdCardDriverEgypt1;
Future geminiAiExtraction(String prompt, payload) async {
var requestBody = jsonEncode({
'contents': [
{
'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
{
'text': """
$payload
$prompt ,and make dates format like year-month-day"""
},
],
},
],
'generationConfig': {
'temperature': 0.4,
'topK': 32,
'topP': 1,
'maxOutputTokens': 4096,
'stopSequences': [],
},
'safety_settings': [
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_NONE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_NONE"
},
]
});
final response = await http.post(
Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?key=AIzaSyCyoLcSkDzK5_SMe00nhut56SSXWPR074w'),
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(response.body);
// Process the responseData as needed
var result = responseData['candidates'][0]['content']['parts'][0]['text'];
RegExp regex = RegExp(r"```json([^`]*)```");
String? jsonString =
regex.firstMatch(responseData.toString())?.group(1)?.trim();
if (jsonString != null) {
// Convert the JSON object to a String
jsonString = jsonEncode(json.decode(jsonString));
responseIdCardDriverEgypt1 = jsonString;
responseIdCardDriverEgypt = jsonDecode(responseIdCardDriverEgypt1!);
update();
return responseIdCardDriverEgypt;
} else {}
// Rest of your code...
} else {}
}
}
@@ -1,639 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../controller/auth/captin/invit_controller.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
import '../../../main.dart';
class InviteScreen extends StatelessWidget {
final InviteController controller = Get.put(InviteController());
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: CupertinoColors.systemBackground,
appBar: AppBar(
backgroundColor: CupertinoColors.systemBackground,
elevation: 0,
title: Text(
'Invite'.tr,
style: const TextStyle(color: CupertinoColors.label),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: AppColor.blueColor),
onPressed: () => Get.back(),
),
),
body: SafeArea(
child: GetBuilder<InviteController>(
builder: (controller) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(8),
),
child: SegmentedButton<int>(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith<Color>(
(states) => states.contains(WidgetState.selected)
? CupertinoColors.white
: Colors.transparent,
),
foregroundColor: WidgetStateProperty.resolveWith<Color>(
(states) => states.contains(WidgetState.selected)
? AppColor.blueColor
: CupertinoColors.label,
),
),
segments: [
ButtonSegment(
value: 0,
label: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Drivers'.tr),
),
),
ButtonSegment(
value: 1,
label: Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Passengers'.tr),
),
),
],
selected: {controller.selectedTab},
onSelectionChanged: (Set<int> newSelection) {
controller.updateSelectedTab(newSelection.first);
},
),
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: controller.selectedTab == 0
? _buildDriverTab(context)
: _buildPassengerTab(context),
),
),
],
);
},
),
),
);
}
Widget _buildDriverTab(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
Text(
"Invite another driver and both get a gift after he completes 100 trips!"
.tr,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: CupertinoColors.label,
),
),
const SizedBox(height: 20),
_buildPhoneInput(),
const SizedBox(height: 20),
_buildActionButtons(),
const SizedBox(height: 20),
_buildInvitationsList(context),
],
);
}
Widget _buildPassengerTab(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
"Share this code with passengers and earn rewards when they use it!"
.tr,
textAlign: TextAlign.center,
style: const TextStyle(
color: CupertinoColors.secondaryLabel,
fontSize: 13,
),
),
],
),
),
const SizedBox(height: 20),
_buildPhoneInput(),
const SizedBox(height: 20),
_buildActionButtonsPassengers(),
const SizedBox(height: 20),
const SizedBox(height: 20),
_buildInvitationsListPassengers(context),
],
);
}
Widget _buildPhoneInput() {
return Container(
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(
child: CupertinoTextField.borderless(
controller: controller.invitePhoneController,
placeholder: 'Enter phone'.tr,
padding: const EdgeInsets.all(12),
keyboardType: TextInputType.phone,
),
),
CupertinoButton(
child: const Icon(CupertinoIcons.person_badge_plus,
color: AppColor.blueColor),
onPressed: () async {
await controller.pickContactFromNativeApp();
if (controller.contacts.isNotEmpty) {
if (box.read(BoxName.isSavedPhones) == null) {
// controller.savePhoneToServer();
box.write(BoxName.isSavedPhones, true);
}
_showContactsDialog(Get.context!);
}
},
),
],
),
);
}
Widget _buildActionButtons() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: CupertinoButton(
color: AppColor.blueColor,
borderRadius: BorderRadius.circular(10),
padding: const EdgeInsets.symmetric(vertical: 14),
onPressed: controller.sendInvite,
child: Text(
'Send Invite'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: CupertinoColors.white,
),
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: CupertinoButton(
color: AppColor.blueColor,
borderRadius: BorderRadius.circular(10),
padding: const EdgeInsets.symmetric(vertical: 14),
child: Text(
'Show Invitations'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: CupertinoColors.white,
),
),
onPressed: () async {
controller.fetchDriverStats();
},
),
),
),
],
),
);
}
Widget _buildActionButtonsPassengers() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0),
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: CupertinoButton(
color: AppColor.blueColor,
borderRadius: BorderRadius.circular(10),
padding: const EdgeInsets.symmetric(vertical: 14),
onPressed: controller.sendInviteToPassenger,
child: Text(
'Send Invite'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: CupertinoColors.white,
),
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: CupertinoButton(
color: AppColor.blueColor,
borderRadius: BorderRadius.circular(10),
padding: const EdgeInsets.symmetric(vertical: 14),
child: Text(
'Show Invitations'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: CupertinoColors.white,
),
),
onPressed: () async {
controller.fetchDriverStatsPassengers();
},
),
),
),
],
),
);
}
Widget _buildInvitationsList(BuildContext context) {
return SizedBox(
height: Get.height * .4,
child: controller.driverInvitationData.isEmpty
? Center(
child: Text(
"No invitation found yet!".tr,
style: const TextStyle(
color: CupertinoColors.secondaryLabel,
fontSize: 17,
),
),
)
: ListView.builder(
itemCount: controller.driverInvitationData.length,
itemBuilder: (context, index) {
return _buildInvitationItem(context, index);
},
),
);
}
Widget _buildInvitationsListPassengers(BuildContext context) {
return SizedBox(
height: Get.height * .4,
child: controller.driverInvitationDataToPassengers.isEmpty
? Center(
child: Text(
"No invitation found yet!".tr,
style: const TextStyle(
color: CupertinoColors.secondaryLabel,
fontSize: 17,
),
),
)
: ListView.builder(
itemCount: controller.driverInvitationDataToPassengers.length,
itemBuilder: (context, index) {
return _buildInvitationItemPassengers(context, index);
},
),
);
}
Widget _buildInvitationItem(BuildContext context, int index) {
int countOfInvitDriver = int.tryParse(
(controller.driverInvitationData[index]['countOfInvitDriver'])
?.toString() ??
'0') ??
0;
double progressValue = (countOfInvitDriver / 100.0).clamp(0.0, 1.0);
return GestureDetector(
onTap: () {
controller.onSelectDriverInvitation(index);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
(controller.driverInvitationData[index]['invitorName']),
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: CupertinoColors.label,
),
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: progressValue,
backgroundColor: CupertinoColors.systemGrey4,
valueColor:
const AlwaysStoppedAnimation<Color>(AppColor.blueColor),
minHeight: 6,
),
),
const SizedBox(height: 4),
Text(
'$countOfInvitDriver / 100 ${'Trip'.tr}',
style: const TextStyle(
fontSize: 13,
color: CupertinoColors.secondaryLabel,
),
),
],
),
),
);
}
Widget _buildInvitationItemPassengers(BuildContext context, int index) {
// Extracting the data from the sample JSON-like structure
var invitation = controller.driverInvitationDataToPassengers[index];
int countOfInvitDriver =
int.tryParse((invitation['countOfInvitDriver'])?.toString() ?? '0') ??
0;
double progressValue = (countOfInvitDriver / 10.0).clamp(0.0, 1.0);
return GestureDetector(
onTap: () {
controller.onSelectPassengerInvitation(index);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
(invitation['passengerName'])
.toString(), // Handle null or missing data
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: CupertinoColors.label,
),
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: progressValue,
backgroundColor: CupertinoColors.systemGrey4,
valueColor:
const AlwaysStoppedAnimation<Color>(AppColor.blueColor),
minHeight: 6,
),
),
const SizedBox(height: 4),
Text(
'$countOfInvitDriver / 3 ${'Trip'.tr}', // Show trips completed
style: const TextStyle(
fontSize: 13,
color: CupertinoColors.secondaryLabel,
),
),
],
),
),
);
}
Widget _buildPassengerStats(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Your Rewards".tr,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
color: CupertinoColors.label,
),
),
const SizedBox(height: 16),
_buildStatItem(
context,
"Total Invites".tr,
(controller.driverInvitationDataToPassengers[0]
['countOfInvitDriver']
.toString()),
),
_buildStatItem(
context,
"Active Users".tr,
(controller.driverInvitationDataToPassengers[0]['passengerName'])
.toString(),
),
],
),
);
}
Widget _buildStatItem(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: const TextStyle(
color: CupertinoColors.label,
fontSize: 15,
),
),
Text(
value,
style: const TextStyle(
fontWeight: FontWeight.w600,
color: AppColor.blueColor,
fontSize: 15,
),
),
],
),
);
}
void _showContactsDialog(BuildContext context) {
showCupertinoModalPopup(
context: context,
builder: (BuildContext context) => Container(
height: 400,
color: CupertinoColors.systemBackground,
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: CupertinoColors.separator.withOpacity(0.5)),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CupertinoButton(
padding: EdgeInsets.zero,
child: Text('Cancel'.tr),
onPressed: () => Navigator.pop(context),
),
Text(
'Choose from contact'.tr,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 60), // For balance
],
),
),
Expanded(
child: ListView.builder(
itemCount: controller.contactMaps.length,
itemBuilder: (context, index) {
final contact = controller.contactMaps[index];
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: CupertinoColors.separator.withOpacity(0.5),
),
),
),
child: CupertinoButton(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
contact['name'],
style: const TextStyle(
color: CupertinoColors.label,
fontSize: 17,
),
),
Text(
(contact['phones'][0].toString()),
style: const TextStyle(
color: CupertinoColors.secondaryLabel,
fontSize: 15,
),
),
],
),
),
const Icon(
CupertinoIcons.chevron_right,
color: CupertinoColors.secondaryLabel,
),
],
),
onPressed: () {
controller.selectPhone(contact['phones'].toString());
// Navigator.pop(context);
},
),
);
},
),
),
],
),
),
);
}
}
@@ -1,559 +0,0 @@
import 'dart:io';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
// تأكد من صحة المسارات في مشروعك
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/auth/apple_sigin.dart';
import '../../../controller/auth/captin/login_captin_controller.dart';
import '../../../controller/auth/google_sign.dart';
import '../../../main.dart';
import 'contact_us_page.dart';
// import 'otp_page.dart';
class LoginCaptin extends StatefulWidget {
const LoginCaptin({super.key});
@override
State<LoginCaptin> createState() => _LoginCaptinState();
}
class _LoginCaptinState extends State<LoginCaptin> with WidgetsBindingObserver {
final AuthController authController = Get.put(AuthController());
final LoginDriverController controller = Get.put(LoginDriverController());
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
controller.getLocationPermission();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
controller.getLocationPermission();
}
}
@override
Widget build(BuildContext context) {
return GetBuilder<LoginDriverController>(
builder: (controller) {
return Scaffold(
backgroundColor: Colors.white, // خلفية نظيفة
body: SafeArea(
child: SizedBox(
width: double.infinity,
height: double.infinity,
child: _buildBodyContent(context, controller),
),
),
);
},
);
}
Widget _buildBodyContent(
BuildContext context, LoginDriverController controller) {
// 1. صفحة الشروط
if (box.read(BoxName.agreeTerms) != 'agreed') {
return _buildAgreementPage(context, controller);
}
// 2. صفحة إذن الموقع
if (box.read(BoxName.locationPermission) != 'true') {
return _buildLocationPermissionPage(context, controller);
}
// 3. صفحة تسجيل الدخول (التصميم الجديد)
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: _buildSocialLoginOptions(context, controller),
),
);
}
// --- 1. Agreement Page ---
Widget _buildAgreementPage(
BuildContext context, LoginDriverController controller) {
return Column(
children: [
const SizedBox(height: 20),
// Header ثابت
const Icon(Icons.policy_outlined,
size: 50, color: AppColor.primaryColor),
const SizedBox(height: 10),
Text('Driver Agreement'.tr,
textAlign: TextAlign.center, style: AppStyle.headTitle2),
const SizedBox(height: 10),
// منطقة المحتوى القابلة للتمرير (Expanded لتأخذ المساحة المتبقية)
Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: Colors.grey[50],
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
),
child: Scrollbar(
thumbVisibility: true,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: HtmlWidget(
box.read(BoxName.lang).toString() == 'ar'
? AppInformation.privacyPolicyArabic
: AppInformation.privacyPolicy,
textStyle: const TextStyle(
fontSize: 14, height: 1.6), // تنسيق نص مريح
),
),
),
),
),
// منطقة الإجراءات السفلية
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -5),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: Text(
'I have read and agree to the Terms of Use and Privacy Policy'
.tr,
style: const TextStyle(
fontSize: 14, fontWeight: FontWeight.w600),
),
value: controller.isAgreeTerms,
onChanged: (value) => controller.changeAgreeTerm(),
activeColor: AppColor.primaryColor,
controlAffinity: ListTileControlAffinity.leading,
),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 0,
),
onPressed: controller.isAgreeTerms
? () => controller.saveAgreementTerms()
: null,
child: Text(
'Accept & Continue'.tr,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
],
);
}
// --- 2. Location Permission Page ---
Widget _buildLocationPermissionPage(
BuildContext context, LoginDriverController controller) {
return Padding(
padding: const EdgeInsets.all(30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/map.png',
height: 150,
errorBuilder: (c, o, s) => const Icon(Icons.location_on,
size: 100, color: AppColor.primaryColor)),
const SizedBox(height: 30),
Text(
'Enable Location'.tr,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
Text(
'We need your location to receive nearby ride requests and navigation.'
.tr,
textAlign: TextAlign.center,
style:
TextStyle(color: Colors.grey[600], fontSize: 16, height: 1.5),
),
const SizedBox(height: 40),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.greenColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
onPressed: () async {
box.write(BoxName.locationPermission, 'true');
controller.update();
var status = await Permission.location.request();
if (status.isGranted) {
var bgStatus = await Permission.locationAlways.request();
if (bgStatus.isGranted) {
// Granted
}
} else if (status.isPermanentlyDenied) {
openAppSettings();
}
controller.update();
},
child: Text("Allow Access".tr,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold)),
),
),
TextButton(
onPressed: () => openAppSettings(),
child: Text("Open Settings".tr,
style: const TextStyle(color: Colors.grey)),
)
],
),
);
}
// --- 3. Social Login Options (RE-DESIGNED) ---
Widget _buildSocialLoginOptions(
BuildContext context, LoginDriverController controller) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Header Logo
InkWell(
onLongPress: () {
_showTesterLoginDialog(context);
},
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 5))
]),
child: Image.asset('assets/images/logo.gif',
height: 80, width: 80, fit: BoxFit.contain),
),
),
const SizedBox(height: 24),
Text(
'Welcome Captain!'.tr,
style: const TextStyle(
fontSize: 26, fontWeight: FontWeight.bold, color: Colors.black87),
),
const SizedBox(height: 8),
Text(
'Login to manage your rides and earnings'.tr,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
),
const SizedBox(height: 40),
// Google Button
_buildSocialCard(
label: 'Continue with Google'.tr,
icon: FontAwesome.google,
iconColor: Colors.red,
onTap: () {
GoogleSignInHelper().signInFromLogin();
},
),
// Apple Button (IOS Only)
if (Platform.isIOS) ...[
const SizedBox(height: 16),
_buildSocialCard(
label: 'Continue with Apple'.tr,
icon: FontAwesome.apple,
iconColor: Colors.black,
onTap: () async {
User? user = await authController.signInWithApple();
if (user != null) {
box.write(BoxName.emailDriver, user.email.toString());
box.write(BoxName.driverID, user.uid);
controller.loginWithGoogleCredential(
user.uid,
user.email.toString(),
);
}
},
),
],
const SizedBox(height: 30),
// Divider
Row(
children: [
Expanded(child: Divider(color: Colors.grey[300])),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text("OR".tr,
style: TextStyle(
color: Colors.grey[400],
fontSize: 12,
fontWeight: FontWeight.bold)),
),
Expanded(child: Divider(color: Colors.grey[300])),
],
),
const SizedBox(height: 30),
// Manual Email Button
SizedBox(
width: double.infinity,
height: 55,
child: OutlinedButton(
onPressed: () => controller.isPhoneVerified(),
style: OutlinedButton.styleFrom(
side: BorderSide(color: AppColor.primaryColor.withOpacity(0.5)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text(
'Use Email & Password'.tr,
style: const TextStyle(
color: AppColor.primaryColor,
fontSize: 16,
fontWeight: FontWeight.bold),
),
),
),
const SizedBox(height: 40),
// Support Link
GestureDetector(
onTap: () => Get.to(() => ContactUsPage()),
child: Text(
'Need help? Contact Support'.tr,
style: TextStyle(
color: Colors.grey[500],
fontSize: 13,
decoration: TextDecoration.underline),
),
),
],
);
}
// Helper Widget for Social Cards
Widget _buildSocialCard({
required String label,
required IconData icon,
required Color iconColor,
required VoidCallback onTap,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Icon(icon, color: iconColor, size: 24),
const SizedBox(width: 24),
Expanded(
child: Text(
label,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
),
const Icon(Icons.arrow_forward_ios_rounded,
size: 16, color: Colors.grey),
],
),
),
),
);
}
void _showTesterLoginDialog(BuildContext context) {
final controller = Get.put(LoginDriverController());
final emailController = TextEditingController();
final passwordController = TextEditingController();
final formKey = GlobalKey<FormState>();
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: '',
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (ctx, anim1, anim2) => Container(),
transitionBuilder: (ctx, anim1, anim2, child) {
return ScaleTransition(
scale: CurvedAnimation(parent: anim1, curve: Curves.easeOutBack),
child: AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
title: Column(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.admin_panel_settings_rounded,
color: AppColor.primaryColor, size: 30),
),
const SizedBox(height: 10),
const Text(
'Tester Access',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColor.writeColor,
),
),
],
),
content: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: emailController,
style: const TextStyle(color: AppColor.writeColor),
decoration: InputDecoration(
labelText: 'Tester Email',
prefixIcon:
const Icon(Icons.email_outlined, color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
const BorderSide(color: AppColor.primaryColor),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
validator: (v) => v!.contains('@') ? null : 'Invalid Email',
),
const SizedBox(height: 16),
TextFormField(
controller: passwordController,
style: const TextStyle(color: AppColor.writeColor),
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon:
const Icon(Icons.lock_outline, color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
const BorderSide(color: AppColor.primaryColor),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
validator: (v) => v!.isEmpty ? 'Required' : null,
),
],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('Cancel',
style: TextStyle(color: Colors.grey.shade600)),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
elevation: 0,
),
onPressed: () {
if (formKey.currentState!.validate()) {
controller.logintest(
passwordController.text.trim(),
emailController.text.trim(),
);
Get.back();
}
},
child:
const Text('Login', style: TextStyle(color: Colors.white)),
),
],
),
);
},
);
}
}
@@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/controller/functions/log_out.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
class LogoutCaptain extends StatelessWidget {
const LogoutCaptain({super.key});
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Log Out Page'.tr,
body: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MyElevatedButton(
title: 'Log Off'.tr,
onPressed: () {
LogOutController().logOutCaptain();
}),
const SizedBox(
height: 30,
),
MyElevatedButton(
title: 'Delete My Account'.tr,
onPressed: () {
LogOutController().deletecaptainAccount();
},
kolor: AppColor.redColor,
),
],
),
)
],
isleading: true,
);
}
}
@@ -1,703 +0,0 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// --- افترض أن هذه المسارات موجودة في مشروعك ---
import 'package:sefer_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:sefer_driver/controller/auth/captin/phone_helper_controller.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../controller/local/phone_intel/intl_phone_field.dart';
import '../../../controller/local/phone_intel/phone_number.dart';
// import '../../../main.dart';
/// ----------------------------------------------------------------------------
/// 1. AUTH SCREEN WRAPPER
/// (يحتوي على التصميم العام + اللوجيك الخفي للـ App Tester)
/// ----------------------------------------------------------------------------
class AuthScreen extends StatelessWidget {
final String title;
final String subtitle;
final Widget form;
const AuthScreen({
super.key,
required this.title,
required this.subtitle,
required this.form,
});
// --- دالة إظهار نافذة دخول الفاحصين (Hidden Tester Dialog) ---
void _showTesterLoginDialog(BuildContext context) {
final controller = Get.put(LoginDriverController());
final emailController = TextEditingController();
final passwordController = TextEditingController();
final formKey = GlobalKey<FormState>();
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: '',
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (ctx, anim1, anim2) => Container(),
transitionBuilder: (ctx, anim1, anim2, child) {
return ScaleTransition(
scale: CurvedAnimation(parent: anim1, curve: Curves.easeOutBack),
child: AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
title: Column(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.admin_panel_settings_rounded,
color: AppColor.primaryColor, size: 30),
),
const SizedBox(height: 10),
const Text(
'Tester Access',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColor.writeColor,
),
),
],
),
content: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: emailController,
style: const TextStyle(color: AppColor.writeColor),
decoration: InputDecoration(
labelText: 'Tester Email',
prefixIcon:
const Icon(Icons.email_outlined, color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
const BorderSide(color: AppColor.primaryColor),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
validator: (v) => v!.contains('@') ? null : 'Invalid Email',
),
const SizedBox(height: 16),
TextFormField(
controller: passwordController,
style: const TextStyle(color: AppColor.writeColor),
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon:
const Icon(Icons.lock_outline, color: Colors.grey),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
const BorderSide(color: AppColor.primaryColor),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12),
),
validator: (v) => v!.isEmpty ? 'Required' : null,
),
],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('Cancel',
style: TextStyle(color: Colors.grey.shade600)),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
elevation: 0,
),
onPressed: () {
if (formKey.currentState!.validate()) {
controller.logintest(
passwordController.text.trim(),
emailController.text.trim(),
);
Get.back();
}
},
child:
const Text('Login', style: TextStyle(color: Colors.white)),
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: SizedBox(
height: size.height,
child: Stack(
children: [
// 1. الخلفية العلوية (Blue Gradient Header)
Container(
height: size.height * 0.45,
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: AppColor.gradientStartEnd,
),
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(60),
bottomLeft: Radius.circular(0),
),
),
child: Stack(
children: [
// زخارف خلفية بسيطة
Positioned(
top: -40,
left: -40,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
),
),
Positioned(
bottom: 40,
right: -20,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
),
),
// المحتوى (اللوجو والعناوين)
SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// --- اللوجو مع الضغط المطول (Backdoor) ---
GestureDetector(
onLongPress: () {
_showTesterLoginDialog(context);
},
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 10,
spreadRadius: 2,
)
],
),
child: ClipOval(
child: Image.asset(
'assets/images/logo.gif',
height: 80,
width: 80,
fit: BoxFit.cover,
),
),
),
),
const SizedBox(height: 20),
Text(
title,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1,
),
),
const SizedBox(height: 8),
Text(
subtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.9),
),
),
const SizedBox(
height: 40), // مساحة لرفع المحتوى عن الكارت
],
),
),
),
],
),
),
// 2. الفورم (White Floating Card)
Positioned(
top: size.height * 0.38,
left: 20,
right: 20,
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 30),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: AppColor.primaryColor.withOpacity(0.15),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: form,
),
),
// 3. التذييل
Positioned(
bottom: 20,
left: 0,
right: 0,
child: Center(
child: Text(
"Tripz Driver Egypt © 2026",
style: TextStyle(
color: Colors.grey.shade400,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
),
),
);
}
}
/// ----------------------------------------------------------------------------
/// 2. PHONE NUMBER SCREEN
/// (تحقق صارم جداً للأرقام المصرية + تنظيف الأصفار)
/// ----------------------------------------------------------------------------
class PhoneNumberScreen extends StatefulWidget {
const PhoneNumberScreen({super.key});
@override
State<PhoneNumberScreen> createState() => _PhoneNumberScreenState();
}
class _PhoneNumberScreenState extends State<PhoneNumberScreen> {
final _formKey = GlobalKey<FormState>();
// ignore: unused_field
String _completePhoneNumber = "";
String _pureNumber = ""; // الرقم بدون كود الدولة
bool _isLoading = false;
void _submit() async {
if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true);
// --- منطق المعالجة (Strict Processing) ---
// 1. تنظيف الرقم من أي صفر في البداية (Standardization)
String cleanNumber =
_pureNumber.startsWith('0') ? _pureNumber.substring(1) : _pureNumber;
// 2. بناء الرقم الكامل ليكون +2010xxxxxxxxx
String finalPhone = "+20$cleanNumber";
// 3. الإرسال
final success = await PhoneAuthHelper.sendOtp(finalPhone);
if (success && mounted) {
setState(() => _isLoading = false);
// ✅ [تصحيح]: الانتقال لصفحة الـ OTP بدلاً من استدعاء دالة verifyOtp
Get.to(() => OtpVerificationScreen(phoneNumber: finalPhone));
} else {
if (mounted) setState(() => _isLoading = false);
}
if (mounted) setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return AuthScreen(
title: 'أهلاً بك كابتن'.tr,
subtitle: 'سجل دخولك وابدأ رحلاتك في مصر'.tr,
form: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'رقم الهاتف'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: AppColor.writeColor,
),
),
const SizedBox(height: 10),
Directionality(
textDirection: TextDirection.ltr,
child: IntlPhoneField(
disableLengthCheck: true, // تفعيل التحقق اليدوي للصرامة
showCountryFlag: true,
flagsButtonPadding: const EdgeInsets.only(left: 10),
dropdownIconPosition: IconPosition.trailing,
dropdownIcon: const Icon(Icons.keyboard_arrow_down,
color: AppColor.primaryColor),
initialCountryCode: 'EG', // مصر افتراضياً
languageCode: 'ar', // الدول بالعربية
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColor.writeColor),
decoration: InputDecoration(
hintText: '010xxxxxxxxx',
hintStyle: TextStyle(color: Colors.grey.shade300),
filled: true,
fillColor: Colors.grey.shade50,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: AppColor.primaryColor, width: 2),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.redAccent),
),
),
onChanged: (phone) {
_completePhoneNumber = phone.completeNumber;
_pureNumber = phone.number;
},
// --- التحقق الصارم (Strict Validation) ---
validator: (PhoneNumber? phone) {
if (phone == null || phone.number.isEmpty) {
return 'رقم الهاتف مطلوب'.tr;
}
// 1. التأكد أن الدولة مصر
if (phone.countryISOCode != 'EG') {
return 'التطبيق متاح حالياً في مصر فقط'.tr;
}
// 2. تنظيف الرقم من الرموز والمسافات
String number = phone.number.replaceAll(RegExp(r'\D'), '');
// 3. حذف الصفر من البداية لتوحيد التحقق
String cleanNumber =
number.startsWith('0') ? number.substring(1) : number;
// 4. التحقق من الطول (يجب أن يكون 10 أرقام صافية)
if (cleanNumber.length != 10) {
return 'الرقم يجب أن يكون 11 خانة (01xxxxxxxxx)'.tr;
}
// 5. التحقق من مفاتيح الشبكات (010, 011, 012, 015)
List<String> validPrefixes = ['10', '11', '12', '15'];
bool isValidPrefix = validPrefixes
.any((prefix) => cleanNumber.startsWith(prefix));
if (!isValidPrefix) {
return 'يجب أن يبدأ الرقم بـ 010, 011, 012, أو 015'.tr;
}
return null;
},
),
),
const SizedBox(height: 30),
_isLoading
? const Center(
child:
CircularProgressIndicator(color: AppColor.primaryColor))
: SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'إرسال رمز التحقق'.tr,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
),
),
),
],
),
),
);
}
}
/// ----------------------------------------------------------------------------
/// 3. OTP VERIFICATION SCREEN
/// ----------------------------------------------------------------------------
class OtpVerificationScreen extends StatefulWidget {
final String phoneNumber;
const OtpVerificationScreen({super.key, required this.phoneNumber});
@override
State<OtpVerificationScreen> createState() => _OtpVerificationScreenState();
}
class _OtpVerificationScreenState extends State<OtpVerificationScreen> {
final _formKey = GlobalKey<FormState>();
final _otpController = TextEditingController();
bool _isLoading = false;
void _submit() async {
if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true);
// ✅ [تصحيح]: تمرير الكود المدخل للدالة
await PhoneAuthHelper.verifyOtp(widget.phoneNumber, _otpController.text);
if (mounted) setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return AuthScreen(
title: 'تأكيد الرقم'.tr,
subtitle: '${'تم إرسال الرمز إلى'.tr} ${widget.phoneNumber}',
form: Column(
children: [
Text(
'أدخل الرمز المكون من 5 أرقام'.tr,
style: const TextStyle(
fontSize: 14, color: Colors.grey, fontWeight: FontWeight.w600),
),
const SizedBox(height: 20),
Form(
key: _formKey,
child: Directionality(
textDirection: TextDirection.ltr,
child: TextFormField(
controller: _otpController,
keyboardType: TextInputType.number,
maxLength: 5,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
letterSpacing: 10,
color: AppColor.primaryColor,
),
decoration: InputDecoration(
counterText: '',
hintText: '•••••',
hintStyle: TextStyle(
color: Colors.grey.shade300,
letterSpacing: 10,
),
enabledBorder: UnderlineInputBorder(
borderSide:
BorderSide(color: Colors.grey.shade300, width: 2),
),
focusedBorder: const UnderlineInputBorder(
borderSide:
BorderSide(color: AppColor.primaryColor, width: 3),
),
),
validator: (v) => (v == null || v.length < 5) ? '' : null,
onChanged: (value) {
if (value.length == 5) _submit();
},
),
),
),
const SizedBox(height: 40),
_isLoading
? const Center(
child:
CircularProgressIndicator(color: AppColor.primaryColor))
: SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text(
'تحقق الآن'.tr,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
),
],
),
);
}
}
/// ----------------------------------------------------------------------------
/// 4. REGISTRATION SCREEN
/// ----------------------------------------------------------------------------
class RegistrationScreen extends StatefulWidget {
final String phoneNumber;
const RegistrationScreen({super.key, required this.phoneNumber});
@override
State<RegistrationScreen> createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
final _formKey = GlobalKey<FormState>();
final _firstNameController = TextEditingController();
final _lastNameController = TextEditingController();
final _emailController = TextEditingController();
bool _isLoading = false;
void _submit() async {
if (_formKey.currentState!.validate()) {
setState(() => _isLoading = true);
// await PhoneAuthHelper.registerUser(
// phoneNumber: widget.phoneNumber,
// firstName: _firstNameController.text.trim(),
// lastName: _lastNameController.text.trim(),
// email: _emailController.text.trim(),
// );
if (mounted) setState(() => _isLoading = false);
}
}
Widget _buildField(
TextEditingController controller, String label, IconData icon,
{bool required = true}) {
return TextFormField(
controller: controller,
style: const TextStyle(color: AppColor.writeColor),
decoration: InputDecoration(
labelText: label,
labelStyle: TextStyle(color: Colors.grey.shade600),
prefixIcon: Icon(icon, color: AppColor.primaryColor),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColor.primaryColor, width: 2),
),
filled: true,
fillColor: Colors.grey.shade50,
),
validator: required ? (v) => v!.isEmpty ? '$label مطلوب'.tr : null : null,
);
}
@override
Widget build(BuildContext context) {
return AuthScreen(
title: 'بيانات الحساب'.tr,
subtitle: 'أكمل بياناتك لإنشاء حساب السائق'.tr,
form: Form(
key: _formKey,
child: Column(
children: [
_buildField(
_firstNameController, 'الاسم الأول'.tr, Icons.person_outline),
const SizedBox(height: 16),
_buildField(
_lastNameController, 'اسم العائلة'.tr, Icons.person_outline),
const SizedBox(height: 16),
_buildField(_emailController, 'البريد الإلكتروني (اختياري)'.tr,
Icons.email_outlined,
required: false),
const SizedBox(height: 30),
_isLoading
? const Center(
child:
CircularProgressIndicator(color: AppColor.primaryColor))
: SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
onPressed: _submit,
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text(
'إتمام التسجيل'.tr,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
);
}
}
@@ -1,176 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../controller/auth/captin/opt_token_controller.dart';
class OtpVerificationPage extends StatefulWidget {
final String phone;
final String deviceToken;
final String token;
final String ptoken;
const OtpVerificationPage({
super.key,
required this.phone,
required this.deviceToken,
required this.token,
required this.ptoken,
});
@override
State<OtpVerificationPage> createState() => _OtpVerificationPageState();
}
class _OtpVerificationPageState extends State<OtpVerificationPage> {
late final OtpVerificationController controller;
final List<FocusNode> _focusNodes = List.generate(6, (index) => FocusNode());
final List<TextEditingController> _textControllers =
List.generate(6, (index) => TextEditingController());
@override
void initState() {
super.initState();
controller = Get.put(OtpVerificationController(
phone: widget.phone,
deviceToken: widget.deviceToken,
token: widget.token,
));
}
@override
void dispose() {
for (var node in _focusNodes) {
node.dispose();
}
for (var controller in _textControllers) {
controller.dispose();
}
super.dispose();
}
void _onOtpChanged(String value, int index) {
if (value.isNotEmpty) {
if (index < 5) {
_focusNodes[index + 1].requestFocus();
} else {
_focusNodes[index].unfocus(); // إلغاء التركيز بعد آخر حقل
}
} else if (index > 0) {
_focusNodes[index - 1].requestFocus();
}
// تجميع نصوص كل الحقول لتكوين الرمز النهائي
controller.otpCode.value = _textControllers.map((c) => c.text).join();
}
Widget _buildOtpInputFields() {
return Directionality(
textDirection: TextDirection.ltr, // لضمان ترتيب الحقول من اليسار لليمين
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(5, (index) {
return SizedBox(
width: 45,
height: 55,
child: TextFormField(
controller: _textControllers[index],
focusNode: _focusNodes[index],
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
maxLength: 1,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
decoration: InputDecoration(
counterText: "",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade300),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Theme.of(context).primaryColor, width: 2),
),
),
onChanged: (value) => _onOtpChanged(value, index),
),
);
}),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Verify OTP'.tr),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
),
backgroundColor: Colors.grey[50],
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 20),
Icon(Icons.phonelink_lock_rounded,
size: 80, color: Theme.of(context).primaryColor),
const SizedBox(height: 24),
Text(
'Verification Code'.tr,
style:
const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Text(
'${'We have sent a verification code to your mobile number:'.tr} ${widget.phone}',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey.shade600, fontSize: 16, height: 1.5),
),
),
const SizedBox(height: 40),
_buildOtpInputFields(),
const SizedBox(height: 40),
Obx(() => SizedBox(
width: double.infinity,
height: 50,
child: controller.isVerifying.value
? const Center(child: CircularProgressIndicator())
: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12))),
onPressed: () =>
controller.verifyOtp(widget.ptoken),
child: Text('Verify'.tr,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w600)),
),
)),
const SizedBox(height: 24),
Obx(
() => controller.canResend.value
? TextButton(
onPressed: controller.sendOtp,
child: Text('Resend Code'.tr),
)
: Text(
'${'You can resend in'.tr} ${controller.countdown.value} ${'seconds'.tr}',
style: const TextStyle(color: Colors.grey),
),
)
],
),
),
),
);
}
}
@@ -1,202 +0,0 @@
import 'package:sefer_driver/constant/style.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/controller/auth/captin/register_captin_controller.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
import '../../../controller/auth/google_sign.dart';
class RegisterCaptin extends StatelessWidget {
const RegisterCaptin({super.key});
@override
Widget build(BuildContext context) {
Get.put(RegisterCaptainController());
return MyScafolld(
title: 'Register Driver'.tr,
body: [
// GetBuilder<RegisterCaptainController>(
// builder: (controller) => Form(
// key: controller.formKey,
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: SingleChildScrollView(
// child: Container(
// decoration: const BoxDecoration(
// boxShadow: [
// BoxShadow(
// offset: Offset(3, 3),
// color: AppColor.accentColor,
// blurRadius: 3)
// ],
// color: AppColor.secondaryColor,
// ),
// child: Padding(
// padding: const EdgeInsets.all(16),
// child: Column(
// children: [
// SizedBox(
// width: Get.width * .8,
// child: TextFormField(
// keyboardType: TextInputType.emailAddress,
// controller: controller.emailController,
// decoration: InputDecoration(
// focusedBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: AppColor.primaryColor,
// width: 2.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// fillColor: AppColor.accentColor,
// hoverColor: AppColor.accentColor,
// focusColor: AppColor.accentColor,
// border: const OutlineInputBorder(
// borderRadius:
// BorderRadius.all(Radius.circular(12))),
// labelText: 'Email'.tr,
// hintText: 'Enter your email address'.tr,
// ),
// validator: (value) {
// if (value!.isEmpty ||
// (!value.contains('@') ||
// !value.contains('.'))) {
// return 'Please enter Your Email.'.tr;
// }
// return null;
// },
// ),
// ),
// const SizedBox(
// height: 15,
// ),
// SizedBox(
// width: Get.width * .8,
// child: TextFormField(
// obscureText: true,
// keyboardType: TextInputType.emailAddress,
// controller: controller.passwordController,
// decoration: InputDecoration(
// focusedBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: AppColor.primaryColor,
// width: 2.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// fillColor: AppColor.accentColor,
// hoverColor: AppColor.accentColor,
// focusColor: AppColor.accentColor,
// border: const OutlineInputBorder(
// borderRadius:
// BorderRadius.all(Radius.circular(12))),
// labelText: 'Password'.tr,
// hintText: 'Enter your Password'.tr,
// ),
// validator: (value) {
// if (value!.isEmpty) {
// return 'Please enter Your Password.'.tr;
// }
// if (value.length < 6) {
// return 'Password must br at least 6 character.'
// .tr;
// }
// return null;
// },
// ),
// ),
// const SizedBox(
// height: 15,
// ),
// SizedBox(
// width: Get.width * .8,
// child: TextFormField(
// keyboardType: TextInputType.phone,
// cursorColor: AppColor.accentColor,
// controller: controller.phoneController,
// decoration: InputDecoration(
// focusedBorder: OutlineInputBorder(
// borderSide: const BorderSide(
// color: AppColor.primaryColor,
// width: 2.0,
// ),
// borderRadius: BorderRadius.circular(10),
// ),
// focusColor: AppColor.accentColor,
// fillColor: AppColor.accentColor,
// border: const OutlineInputBorder(
// borderRadius:
// BorderRadius.all(Radius.circular(12))),
// labelText: 'Phone'.tr,
// hintText: 'Enter your phone number'.tr,
// ),
// validator: (value) {
// if (value!.isEmpty || value.length != 10) {
// return 'Please enter your phone number.'.tr;
// }
// return null;
// },
// ),
// ),
// const SizedBox(
// height: 15,
// ),
// MyElevatedButton(
// title: 'Next'.tr,
// onPressed: () => controller.nextToAIDetection()),
// ],
// ),
// ),
// ),
// ),
// ),
// ),
// )
Image.asset(
'assets/images/on1.png',
fit: BoxFit.cover,
height: double.maxFinite,
width: double.maxFinite,
),
Center(
child: Container(
decoration: AppStyle.boxDecoration1,
height: Get.height * .7,
width: Get.width * .9,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Image.asset(
'assets/images/logo.gif',
height: Get.width * .3,
width: Get.width * .3,
fit: BoxFit.fill,
),
Container(
decoration: AppStyle.boxDecoration1,
height: Get.height * .3,
width: Get.width * .8,
child: Center(
child: Text(
'Sign in with Google for easier email and name entry'.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
),
),
MyElevatedButton(
title: 'Sign In by Google'.tr,
onPressed: () async {
// await GoogleSignInHelper.signIn();
},
kolor: AppColor.blueColor,
),
],
),
))
],
isleading: true);
}
}
@@ -1,91 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/controller/auth/captin/register_captin_controller.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
class VerifyEmailCaptainPage extends StatelessWidget {
VerifyEmailCaptainPage({super.key});
RegisterCaptainController registerCaptinController =
Get.put(RegisterCaptainController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Verify Email For Driver'.tr,
body: [
Positioned(
top: 10,
left: 20,
child: Text(
'We sent 5 digit to your Email provided'.tr,
style: AppStyle.title.copyWith(fontSize: 20),
)),
GetBuilder<RegisterCaptainController>(
builder: (controller) => Positioned(
top: 100,
left: 80,
right: 80,
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
children: [
SizedBox(
width: 100,
child: TextField(
controller: registerCaptinController.verifyCode,
decoration: InputDecoration(
labelStyle: AppStyle.title,
border: const OutlineInputBorder(),
hintText: '5 digit'.tr,
counterStyle: AppStyle.number,
hintStyle: AppStyle.subtitle
.copyWith(color: AppColor.accentColor),
),
maxLength: 5,
keyboardType: TextInputType.number,
),
),
const SizedBox(
height: 30,
),
MyElevatedButton(
title: 'Send Verfication Code'.tr,
onPressed: () {
registerCaptinController.sendVerifications();
})
],
),
),
)),
],
isleading: true,
);
}
Padding verifyEmail() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: AppColor.accentColor,
width: 2,
),
borderRadius: BorderRadius.circular(8),
),
child: const Padding(
padding: EdgeInsets.all(10),
child: SizedBox(
width: 20,
child: TextField(
maxLength: 1,
keyboardType: TextInputType.number,
),
),
),
),
);
}
}