Files
tripz-llc/apps/driver/lib/views/auth/captin/criminal_documents_page.dart
T
HamzaandClaude Opus 4.8 6234e66471 feat: Gemini AI (doc OCR + face match) + role-assignment endpoints
- GeminiService (integrations/gemini, @Global): extractDocument (OCR fields) + faceMatch; graceful if no GEMINI_API_KEY
- documents: face-match now real via Gemini; POST /admin/documents/:id/ocr extracts fields for CS auto-fill
- roles: PATCH /admin/users/:id/role (admin) + POST /platform/users/role (bootstrap via x-platform-secret)
- config: gemini + platform.secret; .env.example entries

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 20:01:26 +03:00

217 lines
7.9 KiB
Dart
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}