Files
intaleq_driver/lib/views/auth/syria/registration_view.dart
Hamza-Ayed 3c0ae4cf2f 26-1-20/1
2026-01-20 10:11:10 +03:00

501 lines
19 KiB
Dart
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 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../controller/auth/syria/registration_controller.dart';
class RegistrationView extends StatelessWidget {
const RegistrationView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final RegistrationController controller = Get.put(RegistrationController());
return Scaffold(
appBar: AppBar(
title: Text('Driver Registration'.tr),
centerTitle: true,
),
body: Column(
children: [
SizedBox(
height: 90,
child: Obx(
() => Stepper(
currentStep: controller.currentPage.value,
type: StepperType.horizontal,
controlsBuilder: (_, __) => const SizedBox.shrink(),
steps: [
Step(
title: Text('Driver'.tr),
content: const SizedBox.shrink()),
Step(
title: Text('Vehicle'.tr),
content: const SizedBox.shrink()),
Step(
title: Text('Docs'.tr), content: const SizedBox.shrink()),
],
),
),
),
Expanded(
child: PageView(
controller: controller.pageController,
physics: const NeverScrollableScrollPhysics(),
onPageChanged: (i) => controller.currentPage.value = i,
children: [
_buildDriverInfoStep(context, controller),
_buildCarInfoStep(context, controller),
_buildDocumentUploadStep(context, controller),
],
),
),
],
),
bottomNavigationBar: _buildBottomNavBar(controller),
);
}
// STEP 1
Widget _buildDriverInfoStep(BuildContext ctx, RegistrationController c) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: c.driverInfoFormKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Driver's Personal Information".tr,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
TextFormField(
controller: c.firstNameController,
decoration: InputDecoration(
labelText: 'First Name'.tr,
border: const OutlineInputBorder(),
),
validator: (v) {
if (v == null || v.isEmpty) {
return 'Required field'.tr;
}
if (v.length < 2) {
return 'Name must be at least 2 characters'.tr;
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: c.lastNameController,
decoration: InputDecoration(
labelText: 'Last Name'.tr,
border: const OutlineInputBorder(),
),
validator: (v) {
if (v == null || v.isEmpty) {
return 'Required field'.tr;
}
if (v.length < 2) {
return 'Name must be at least 2 characters'.tr;
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: c.nationalIdController,
decoration: InputDecoration(
labelText: 'National ID Number'.tr,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (v) {
if (v == null || v.isEmpty) {
return 'Required field'.tr;
}
if (v.length != 11) {
return 'National ID must be 11 digits'.tr;
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: c.bithdateController,
decoration: InputDecoration(
labelText: 'سنة الميلاد'.tr,
hintText: '1999'.tr,
border: const OutlineInputBorder(),
),
keyboardType: TextInputType.number,
validator: (v) {
if (v == null || v.isEmpty) {
return 'Required field'.tr;
}
if (v.length != 4) {
return 'Birth year must be 4 digits'.tr;
}
// Optional: check if its a valid number
if (int.tryParse(v) == null) {
return 'Enter a valid year'.tr;
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: c.driverLicenseExpiryController,
decoration: InputDecoration(
labelText: 'License Expiry Date'.tr,
hintText: 'YYYY-MM-DD'.tr,
border: const OutlineInputBorder()),
readOnly: true,
onTap: () async {
DateTime? d = await showDatePicker(
context: ctx,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (d != null) {
c.driverLicenseExpiryDate = d;
c.driverLicenseExpiryController.text =
d.toLocal().toString().split(' ')[0];
}
},
validator: (v) =>
(v?.isEmpty ?? true) ? 'Please select a date'.tr : null,
),
],
),
),
);
}
// STEP 2
Widget _buildCarInfoStep(BuildContext ctx, RegistrationController c) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: c.carInfoFormKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Vehicle Information'.tr,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
// ============================================================
// 1. القائمة المنسدلة لتصنيف المركبة (سيارة، دراجة، فان)
// ============================================================
DropdownButtonFormField<int>(
value: c.selectedVehicleCategoryId,
decoration: InputDecoration(
labelText: 'Vehicle Category'.tr, // ترجمة: تصنيف المركبة
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.directions_car),
),
items: c.vehicleCategoryOptions.map((item) {
return DropdownMenuItem<int>(
value: item['id'],
child: Text(item['name']),
);
}).toList(),
onChanged: (val) {
c.selectedVehicleCategoryId = val;
c.update(); // تحديث الواجهة إذا لزم الأمر
},
validator: (v) => (v == null) ? 'Required field'.tr : null,
),
const SizedBox(height: 16),
// ============================================================
// 2. القائمة المنسدلة لنوع الوقود (بنزين، كهرباء...)
// ============================================================
DropdownButtonFormField<int>(
value: c.selectedFuelTypeId,
decoration: InputDecoration(
labelText: 'Fuel Type'.tr, // ترجمة: نوع الوقود
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.local_gas_station),
),
items: c.fuelTypeOptions.map((item) {
return DropdownMenuItem<int>(
value: item['id'],
child: Text(item['name']),
);
}).toList(),
onChanged: (val) {
c.selectedFuelTypeId = val;
c.update();
},
validator: (v) => (v == null) ? 'Required field'.tr : null,
),
const SizedBox(height: 16),
TextFormField(
controller: c.carPlateController,
decoration: InputDecoration(
labelText: 'Car Plate Number'.tr,
border: const OutlineInputBorder()),
validator: (v) =>
(v?.isEmpty ?? true) ? 'Required field'.tr : null,
),
const SizedBox(height: 16),
TextFormField(
controller: c.carMakeController,
decoration: InputDecoration(
labelText: 'Car Make (e.g., Toyota)'.tr,
border: const OutlineInputBorder()),
validator: (v) =>
(v?.isEmpty ?? true) ? 'Required field'.tr : null,
),
const SizedBox(height: 16),
TextFormField(
controller: c.carModelController,
decoration: InputDecoration(
labelText: 'Car Model (e.g., Corolla)'.tr,
border: const OutlineInputBorder()),
validator: (v) =>
(v?.isEmpty ?? true) ? 'Required field'.tr : null,
),
const SizedBox(height: 16),
TextFormField(
controller: c.carYearController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Year of Manufacture'.tr,
border: const OutlineInputBorder()),
validator: (v) =>
(v?.isEmpty ?? true) ? 'Required field'.tr : null,
),
const SizedBox(height: 16),
// حقل اسم اللون (يبقى اختياري أو نملؤه تلقائيًا عند اختيار الهكس)
// TextFormField(
// controller: c.carColorController,
// decoration: InputDecoration(
// labelText: 'Car Color (Name)'.tr,
// border: const OutlineInputBorder(),
// ),
// validator: (v) =>
// (v?.isEmpty ?? true) ? 'Required field'.tr : null,
// ),
// const SizedBox(height: 16),
// الدروب داون للهكس + دائرة اللون
GetBuilder<RegistrationController>(
id: 'carColor', // اختياري لتحديث انتقائي
builder: (c) {
return DropdownButtonFormField<String>(
value: (c.colorHex != null && c.colorHex!.isNotEmpty)
? c.colorHex
: null,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Car Color (Hex)'.tr,
border: const OutlineInputBorder(),
// prefixIcon: Padding(
// padding:
// const EdgeInsetsDirectional.only(start: 12, end: 8),
// child: Container(
// width: 18,
// height: 18,
// decoration: BoxDecoration(
// color: (c.colorHex?.isNotEmpty ?? false)
// ? c.hexToColor(c.colorHex!)
// : Colors.transparent,
// shape: BoxShape.circle,
// border: Border.all(color: Colors.black26),
// ),
// ),
// ),
),
items: RegistrationController.kCarColorOptions.map((opt) {
final hex = opt['hex']!;
final key = opt['key']!;
return DropdownMenuItem<String>(
value: hex,
child: Row(
children: [
Container(
width: 18,
height: 18,
decoration: BoxDecoration(
color: c.hexToColor(hex),
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
),
const SizedBox(width: 8),
Expanded(child: Text(key.tr)),
],
),
);
}).toList(),
onChanged: (hex) {
c.colorHex = hex; // خزّن الهكس
final key = RegistrationController.kCarColorOptions
.firstWhere((o) => o['hex'] == hex)['key']!;
c.carColorController.text = key.tr;
c.update([
'carColor'
]); // <-- مهم: يعيد بناء الودجت ويحدّث الدائرة
},
validator: (v) =>
(v == null || v.isEmpty) ? 'Required field'.tr : null,
);
},
)
],
),
),
);
}
// STEP 3
Widget _buildDocumentUploadStep(BuildContext ctx, RegistrationController c) {
final String linkUpload =
'https://syria.intaleq.xyz/intaleq/auth/syria/uploadImage.php';
return GetBuilder<RegistrationController>(
builder: (ctrl) => SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Upload Documents'.tr,
style:
const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
_buildImagePickerBox(
'Driver License (Front)'.tr,
ctrl.docUrls['driver_license_front'],
// () => ctrl.pickImage(ImageType.driverLicenseFront),
() async =>
await ctrl.choosImage(linkUpload, 'driver_license_front'),
),
_buildImagePickerBox(
'Driver License (Back)'.tr,
ctrl.docUrls['driver_license_back'],
() async =>
await ctrl.choosImage(linkUpload, 'driver_license_back'),
// () => ctrl.pickImage(ImageType.driverLicenseBack),
),
_buildImagePickerBox(
'Car Registration (Front)'.tr,
ctrl.docUrls['car_license_front'],
() async =>
await ctrl.choosImage(linkUpload, 'car_license_front'),
// () => ctrl.pickImage(ImageType.carLicenseFront),
),
_buildImagePickerBox(
'Car Registration (Back)'.tr,
ctrl.docUrls['car_license_back'],
() async => await ctrl.choosImage(linkUpload, 'car_license_back'),
// () => ctrl.pickImage(ImageType.carLicenseBack),
),
],
),
),
);
}
Widget signedImageWithAuth(String fileUrl, String bearerToken) {
return Image.network(
fileUrl,
headers: {'Authorization': 'Bearer $bearerToken'},
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => const Text('Image expired or unauthorized'),
);
}
Widget _buildImagePickerBox(String title, String? img, VoidCallback onTap) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: onTap,
child: SizedBox(
height: 150,
width: double.infinity,
child: (img != null && img.isNotEmpty)
? Image.network(
img,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.broken_image, size: 40, color: Colors.red),
const SizedBox(height: 8),
Text('Image not available',
style: TextStyle(color: Colors.red[700])),
],
),
);
},
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.camera_alt_outlined,
size: 40, color: Colors.grey[600]),
const SizedBox(height: 8),
Text(title, style: TextStyle(color: Colors.grey[700])),
Text('Tap to upload'.tr,
style:
const TextStyle(color: Colors.grey, fontSize: 12)),
],
),
),
),
);
}
Widget _buildBottomNavBar(RegistrationController c) {
return Obx(() => Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (c.currentPage.value > 0)
TextButton(
onPressed: c.goToPreviousStep,
child: Text('<< BACK'.tr),
),
const Spacer(),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: 40, vertical: 12),
backgroundColor: c.currentPage.value == 2
? Colors.green
: Theme.of(Get.context!).primaryColor,
),
onPressed: c.isLoading.value
? null
: () {
if (c.currentPage.value == 2) {
c.submitRegistration();
} else {
c.goToNextStep();
}
},
child: c.isLoading.value
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2))
: Text(
c.currentPage.value == 2 ? 'SUBMIT'.tr : 'NEXT >>'.tr,
style:
const TextStyle(fontSize: 16, color: Colors.white),
),
),
],
),
));
}
}