feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق
الباك إند: - تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً، وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً. - OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع. - N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر. N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)، ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird. فلاتر: - docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام). - السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب. - طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط. - الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3ab74a13a8
commit
de4bbd00ac
@@ -1,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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user