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,308 +0,0 @@
|
||||
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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user