- 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>
309 lines
11 KiB
Dart
Executable File
309 lines
11 KiB
Dart
Executable File
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import '../../../constant/colors.dart';
|
|
import '../../../constant/style.dart';
|
|
import '../../auth/captin/ai_eg_controller.dart';
|
|
import '../../auth/captin/driver_car_controller.dart';
|
|
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
import 'package:sefer_driver/views/widgets/my_scafold.dart';
|
|
|
|
class CarsInsertingPage extends StatelessWidget {
|
|
CarsInsertingPage({super.key});
|
|
|
|
// حقن الكنترولرز
|
|
final driverCarController = Get.put(DriverCarController());
|
|
final aiController = Get.put(AI());
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MyScafolld(
|
|
title: 'Insert New Car'.tr,
|
|
isleading: true,
|
|
body: [
|
|
Container(
|
|
color: AppColor.accentColor.withOpacity(.2),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: ListView(
|
|
children: [
|
|
// 1. الوجه الأمامي (الذكاء الاصطناعي)
|
|
egyptCarLicenceFront(),
|
|
|
|
// 2. الوجه الخلفي (الذكاء الاصطناعي)
|
|
egyptCarLicenceBack(),
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// تعليمات
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8)),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Please make sure to read the license carefully.'.tr,
|
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 5),
|
|
Text(
|
|
'If your car license has the new design, upload the front side with two images.'
|
|
.tr,
|
|
style: const TextStyle(fontSize: 12)),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// 3. زر الحفظ النهائي
|
|
GetBuilder<AI>(builder: (controller) {
|
|
// التحقق من أن الصور تم رفعها
|
|
bool isFrontReady =
|
|
controller.responseIdCardDriverEgyptFront.isNotEmpty;
|
|
bool isBackReady =
|
|
controller.responseIdCardDriverEgyptBack.isNotEmpty;
|
|
|
|
return MyElevatedButton(
|
|
title: 'Save New Car'.tr,
|
|
// تعطيل الزر إذا لم تكتمل البيانات
|
|
onPressed: _handleSaveCar,
|
|
kolor: (isFrontReady && isBackReady)
|
|
? AppColor.primaryColor
|
|
: Colors.grey,
|
|
);
|
|
})
|
|
],
|
|
),
|
|
),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
|
|
// منطق الحفظ (يربط الـ AI بالـ DriverCarController)
|
|
void _handleSaveCar() {
|
|
final aiFront = Get.find<AI>().responseIdCardDriverEgyptFront;
|
|
final aiBack = Get.find<AI>().responseIdCardDriverEgyptBack;
|
|
|
|
// استدعاء دالة الإضافة في كنترولر السيارات
|
|
Get.find<DriverCarController>().addNewCarToAccount(
|
|
vin: aiBack['chassis'].toString(),
|
|
plate: aiFront['car_plate'].toString(), // اللوحة من الأمام
|
|
make: aiBack['make'].toString(),
|
|
model: aiBack['model'].toString(),
|
|
year: aiBack['year'].toString(),
|
|
expiryDate: aiFront['LicenseExpirationDate'].toString(),
|
|
color: aiBack['color'].toString(),
|
|
colorHex: aiBack['color_hex'].toString(),
|
|
owner: aiFront['owner'].toString(),
|
|
address: aiFront['address'].toString(),
|
|
inspectionDate: aiBack['inspection_date'].toString(), // تاريخ الفحص
|
|
displacement: aiBack['displacement'].toString(), // السعة اللترية
|
|
fuel: aiBack['fuel'].toString(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// --- Widget الوجه الأمامي ---
|
|
GetBuilder<AI> egyptCarLicenceFront() {
|
|
return GetBuilder<AI>(
|
|
builder: (ai) {
|
|
if (ai.responseIdCardDriverEgyptFront.isNotEmpty) {
|
|
final licenseExpiryDate = DateTime.tryParse(
|
|
ai.responseIdCardDriverEgyptFront['LicenseExpirationDate'] ??
|
|
'') ??
|
|
DateTime.now();
|
|
final isLicenseExpired = licenseExpiryDate.isBefore(DateTime.now());
|
|
|
|
return Card(
|
|
elevation: 4.0,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text('Vehicle Details Front'.tr,
|
|
style: AppStyle.headTitle2),
|
|
IconButton(
|
|
// إعادة الرفع
|
|
onPressed: () => ai.allMethodForAI('car_front'),
|
|
icon: const Icon(Icons.refresh,
|
|
color: AppColor.primaryColor),
|
|
),
|
|
],
|
|
),
|
|
const Divider(color: AppColor.accentColor),
|
|
_infoRow('Plate Number'.tr,
|
|
ai.responseIdCardDriverEgyptFront['car_plate']),
|
|
_infoRow('Owner Name'.tr,
|
|
ai.responseIdCardDriverEgyptFront['owner']),
|
|
_infoRow(
|
|
'Address'.tr, ai.responseIdCardDriverEgyptFront['address']),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text('License Expiry Date'.tr),
|
|
Text(
|
|
licenseExpiryDate.toString().split(' ')[0],
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: isLicenseExpired ? Colors.red : Colors.green,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
// حالة الانتظار (زر الرفع)
|
|
return Card(
|
|
child: InkWell(
|
|
onTap: () => ai.allMethodForAI('car_front'), // ✅ هنا التفعيل
|
|
child: Column(
|
|
children: [
|
|
Image.asset(
|
|
'assets/images/3.png',
|
|
height: Get.height * .20,
|
|
fit: BoxFit.contain,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text('Capture Car License (Front)'.tr,
|
|
style: AppStyle.title),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// --- Widget الوجه الخلفي ---
|
|
GetBuilder<AI> egyptCarLicenceBack() {
|
|
return GetBuilder<AI>(
|
|
builder: (ai) {
|
|
if (ai.responseIdCardDriverEgyptBack.isNotEmpty) {
|
|
// منطق تواريخ الفحص والضريبة
|
|
final inspectionDateStr =
|
|
ai.responseIdCardDriverEgyptBack['inspection_date'] ?? '';
|
|
|
|
return Card(
|
|
elevation: 4.0,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text('Vehicle Details Back'.tr, style: AppStyle.headTitle2),
|
|
IconButton(
|
|
// إعادة الرفع
|
|
onPressed: () => ai.allMethodForAI('car_back'),
|
|
icon: const Icon(Icons.refresh,
|
|
color: AppColor.primaryColor),
|
|
),
|
|
],
|
|
),
|
|
const Divider(color: AppColor.accentColor),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: _infoRow('Make'.tr,
|
|
ai.responseIdCardDriverEgyptBack['make'])),
|
|
Expanded(
|
|
child: _infoRow('Model'.tr,
|
|
ai.responseIdCardDriverEgyptBack['model'])),
|
|
],
|
|
),
|
|
_infoRow('Year'.tr, ai.responseIdCardDriverEgyptBack['year']),
|
|
_infoRow(
|
|
'Chassis'.tr, ai.responseIdCardDriverEgyptBack['chassis']),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _infoRow('Color'.tr,
|
|
ai.responseIdCardDriverEgyptBack['color'])),
|
|
// عرض دائرة اللون
|
|
if (ai.responseIdCardDriverEgyptBack['color_hex'] != null)
|
|
Container(
|
|
width: 20,
|
|
height: 20,
|
|
margin: const EdgeInsets.symmetric(horizontal: 10),
|
|
decoration: BoxDecoration(
|
|
color: Color(int.parse(ai
|
|
.responseIdCardDriverEgyptBack['color_hex']
|
|
.toString()
|
|
.replaceFirst('#', '0xff'))),
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: Colors.grey)),
|
|
)
|
|
],
|
|
),
|
|
_infoRow('Displacement'.tr,
|
|
"${ai.responseIdCardDriverEgyptBack['displacement']} cc"),
|
|
_infoRow('Fuel'.tr, ai.responseIdCardDriverEgyptBack['fuel']),
|
|
_infoRow('Inspection Date'.tr, inspectionDateStr),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
// حالة الانتظار
|
|
return Card(
|
|
child: InkWell(
|
|
onTap: () => ai.allMethodForAI('car_back'), // ✅ هنا التفعيل
|
|
child: Column(
|
|
children: [
|
|
Image.asset(
|
|
'assets/images/4.png',
|
|
height: Get.height * .20,
|
|
fit: BoxFit.contain,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text('Capture Car License (Back)'.tr,
|
|
style: AppStyle.title),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// Helper Widget
|
|
Widget _infoRow(String label, String? value) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text("$label:", style: const TextStyle(color: Colors.grey)),
|
|
Expanded(
|
|
child: Text(value ?? "---",
|
|
textAlign: TextAlign.end,
|
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|