first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/links.dart';
import 'package:siro_service/controller/functions/crud.dart';
import 'package:siro_service/controller/functions/device_helper.dart';
import 'package:siro_service/print.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class RegisterController extends GetxController {
final firstName = TextEditingController();
final lastName = TextEditingController();
final email = TextEditingController();
final phone = TextEditingController();
final password = TextEditingController();
final FlutterSecureStorage storage = const FlutterSecureStorage();
final formKey = GlobalKey<FormState>();
var isLoading = false.obs;
void register() async {
if (formKey.currentState!.validate()) {
isLoading.value = true;
// الحصول على البصمة
String fingerprint = await DeviceHelper.getDeviceFingerprint();
var payload = {
"first_name": firstName.text,
"last_name": lastName.text,
"email": email.text,
"phone": phone.text,
"password": password.text,
"fingerprint": fingerprint,
};
Log.print('🚀 Register Payload: $payload');
var res = await CRUD().post(link: AppLink.register, payload: payload);
Log.print('📥 Register Response: $res');
isLoading.value = false;
if (res != 'failure' && res is Map && res['status'] == 'success') {
// حفظ كلمة المرور للدخول التلقائي لاحقاً
await storage.write(key: 'password', value: password.text);
Get.defaultDialog(
title: "نجاح",
middleText: res['message']['message'] ?? "تم تقديم طلبك بنجاح. يرجى انتظار موافقة الإدارة.",
onConfirm: () {
Get.back(); // close dialog
Get.back(); // return to login
},
textConfirm: "موافق",
);
} else {
Get.snackbar(
"خطأ",
res is Map ? res['message'].toString() : "فشل تقديم طلب التسجيل",
backgroundColor: Colors.red.withOpacity(0.7),
colorText: Colors.white,
);
}
}
}
@override
void onClose() {
firstName.dispose();
lastName.dispose();
email.dispose();
phone.dispose();
password.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,71 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../print.dart';
import 'functions/crud.dart';
class Driverthebest extends GetxController {
bool isLoading = false;
List driver = [];
getBestDriver() async {
var res = await CRUD().get(link: AppLink.getBestDriver, payload: {});
if (res != 'failure') {
driver = res['message'];
// Log.print('driver: ${driver}');
update();
} else {
Get.snackbar('error', '', backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}
class DriverTheBestGizaController extends GetxController {
bool isLoading = false;
List driver = [];
getBestDriver() async {
var res = await CRUD().get(link: AppLink.getBestDriverGiza, payload: {});
if (res != 'failure') {
driver = res['message'];
update();
} else {
Get.snackbar('error', '', backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}
class DriverTheBestAlexandriaController extends GetxController {
bool isLoading = false;
List driver = [];
getBestDriver() async {
var res =
await CRUD().get(link: AppLink.getBestDriverAlexandria, payload: {});
if (res != 'failure') {
driver = res['message'];
update();
} else {
Get.snackbar('error', '', backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}

View File

@@ -0,0 +1,110 @@
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:get/get.dart';
import 'package:siro_service/controller/local_notification.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
import '../../print.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
Log.print("Handling a background message: ${message.messageId}");
if (message.data.isNotEmpty && message.notification != null) {
// في وضع الخلفية، يفضل إرسال إشعار محلي أو تحديث البيانات الصامتة
}
}
class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance;
List<String> tokens = [];
List dataTokens = [];
late String driverID;
late String driverToken;
NotificationSettings? notificationSettings;
Future<void> getNotificationSettings() async {
// Get the current notification settings
NotificationSettings? notificationSettings =
await FirebaseMessaging.instance.getNotificationSettings();
'Notification authorization status: ${notificationSettings.authorizationStatus}';
// Call the update function if needed
update();
}
Future<void> requestFirebaseMessagingPermission() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Check if the platform is Android
if (Platform.isAndroid) {
// Request permission for Android
await messaging.requestPermission();
} else if (Platform.isIOS) {
// Request permission for iOS
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: true,
criticalAlert: true,
provisional: false,
sound: true,
);
messaging.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
}
}
// NotificationController notificationController =
// Get.isRegistered<NotificationController>()
// ? Get.find<NotificationController>()
// : Get.put(NotificationController());
Future getToken() async {
fcmToken.getToken().then((token) {
// Log.print('fcmToken: ${token}');
box.write(BoxName.tokenFCM, (token.toString()));
});
// 🔹 الاشتراك في topic
await fcmToken.subscribeToTopic("service"); // أو "users" حسب نوع المستخدم
Log.print("Subscribed to 'service' topic ✅");
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// If the app is in the background or terminated, show a system tray message
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
// استخدام الدالة العامة للهاندلر في الخلفية
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
}
Future<void> fireBaseTitles(RemoteMessage message) async {
// [!! تعديل !!]
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان
String category = message.data['category'] ?? '';
// اقرأ العنوان (للعرض)
String title = message.notification?.title ?? '';
String body = message.notification?.body ?? '';
if (category == 'new_service_request') {
// <-- مثال: كان 'Order'.tr
Log.print('message: ${message}');
if (Platform.isAndroid) {
NotificationController().showNotification(title, body, 'Order');
}
}
}
}

View File

@@ -0,0 +1,109 @@
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../main.dart';
import 'functions/crud.dart';
class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance;
List<String> tokens = [];
List dataTokens = [];
late String driverID;
late String driverToken;
NotificationSettings? notificationSettings;
Future<void> getNotificationSettings() async {
// Get the current notification settings
NotificationSettings? notificationSettings =
await FirebaseMessaging.instance.getNotificationSettings();
'Notification authorization status: ${notificationSettings.authorizationStatus}';
// Call the update function if needed
update();
}
Future<void> requestFirebaseMessagingPermission() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Check if the platform is Android
if (Platform.isAndroid) {
// Request permission for Android
await messaging.requestPermission();
} else if (Platform.isIOS) {
// Request permission for iOS
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: true,
criticalAlert: true,
provisional: false,
sound: true,
);
messaging.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
}
}
Future getTokens() async {
var res = await CRUD().post(link: AppLink.getTokens, payload: {});
if (res != 'failure' && res['status'] == 'success') {
dataTokens = res['data'];
for (var i = 0; i < dataTokens.length; i++) {
tokens.add(res['data'][i]['token']);
}
box.write(BoxName.tokens, tokens);
} else {
Get.snackbar("Warning".tr, "Server Error".tr,
backgroundColor: AppColor.redColor);
}
}
Future getToken() async {
fcmToken.getToken().then((token) {
box.write(BoxName.tokenFCM, token);
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// If the app is in the background or terminated, show a system tray message
RemoteNotification? notification = message.notification;
AndroidNotification? android = notification?.android;
// if (notification != null && android != null) {
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {
// Handle background message
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
}
void fireBaseTitles(RemoteMessage message) {
if (message.notification!.title! == 'Order'.tr) {
} else if (message.notification!.title! == 'Apply Ride'.tr) {
var passengerList = message.data['passengerList'];
var myList = jsonDecode(passengerList) as List<dynamic>;
driverID = myList[0].toString();
}
}
}

View File

@@ -0,0 +1,386 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:siro_service/constant/box_name.dart';
import 'package:siro_service/constant/links.dart';
import 'package:siro_service/controller/functions/encrypt_decrypt.dart';
import 'package:siro_service/env/env.dart';
import 'package:siro_service/controller/functions/security_helper.dart';
import 'package:siro_service/main.dart';
import 'package:siro_service/print.dart';
import '../../constant/api_key.dart';
class CRUD {
static bool _isRefreshingJWT = false;
static String? _appSignature;
static String _lastErrorSignature = '';
static DateTime _lastErrorTimestamp = DateTime(2000);
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
// ── JWT Validity Check (No external libs) ──────────────────────
static bool _isJwtValid(String? token) {
if (token == null || token.isEmpty) return false;
try {
final parts = token.split('.');
if (parts.length != 3) return false;
String payload = parts[1];
switch (payload.length % 4) {
case 2:
payload += '==';
break;
case 3:
payload += '=';
break;
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
final exp = decoded['exp'];
if (exp == null) return false;
// 30 seconds buffer
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
} catch (_) {
return false;
}
}
static Future<void> addError(
String error, String details, String where) async {
try {
final currentErrorSignature = '$where-$error';
final now = DateTime.now();
if (currentErrorSignature == _lastErrorSignature &&
now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) {
return;
}
_lastErrorSignature = currentErrorSignature;
_lastErrorTimestamp = now;
final userId =
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
final userType = 'Service';
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
CRUD().post(
link: AppLink.addError,
payload: {
'error': error.toString(),
'userId': userId.toString(),
'userType': userType,
'phone': phone.toString(),
'device': where,
'details': details,
},
);
} catch (e) {}
}
String _getFpHeader() {
return box.read(BoxName.fingerPrint)?.toString() ?? '';
}
String _generateHmac(String body, String timestamp, String nonce) {
// نستخدم المفتاح الخاص بالمستخدم (المخزن في البوكس) كـ HMAC Secret
final hmacSecret = box.read(BoxName.hmac) ?? '';
final payload = body + timestamp + nonce;
final key = utf8.encode(hmacSecret);
final bytes = utf8.encode(payload);
final hmacSha256 = Hmac(sha256, key);
final result = hmacSha256.convert(bytes).toString();
Log.print('🔐 [HMAC-DEBUG] Secret: $hmacSecret');
Log.print('🔐 [HMAC-DEBUG] Body(${body.length}): "$body"');
Log.print('🔐 [HMAC-DEBUG] TS: $timestamp | Nonce: $nonce');
Log.print('🔐 [HMAC-DEBUG] Result: $result');
return result;
}
// ═══════════════════════════════════════════════════════════════
// _makeRequest — Central Request Handler
// ───────────────────────────────────────────────────────────────
Future<dynamic> _makeRequest({
required String link,
Map<String, dynamic>? payload,
required Map<String, String> headers,
}) async {
const totalTimeout = Duration(seconds: 60);
// توليد بيانات الـ HMAC للطلب الحالي
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
final nonce =
DateTime.now().microsecondsSinceEpoch.toString(); // Nonce فريد
// تحويل الـ payload إلى string لمحاكة ما سيصل للسيرفر (php://input)
String bodyString = '';
if (payload != null && payload.isNotEmpty) {
// الـ http.post يرسل البيانات كـ x-www-form-urlencoded
bodyString = payload.keys
.map((key) =>
"$key=${Uri.encodeQueryComponent(payload[key].toString())}")
.join("&");
}
final hmacSignature = _generateHmac(bodyString, timestamp, nonce);
// إضافة هيدرات الـ HMAC
headers['X-HMAC-Auth'] = hmacSignature;
headers['X-Timestamp'] = timestamp;
headers['X-Nonce'] = nonce;
Future<http.Response> doPost() {
final url = Uri.parse(link);
return http
.post(url, body: payload, headers: headers)
.timeout(totalTimeout);
}
http.Response? response;
int attempts = 0;
final requestId =
DateTime.now().millisecondsSinceEpoch.toString().substring(7);
Log.print('🚀 [REQ-$requestId] $link');
Log.print('🔑 [FP-$requestId] ${headers['X-Device-FP']}');
Log.print('🔏 [SIGN-$requestId] ${headers['X-App-Signature']}');
if (payload != null) Log.print('📦 [PAYLOAD-$requestId] $payload');
while (attempts < 3) {
try {
attempts++;
response = await doPost();
break;
} on SocketException catch (_) {
Log.print('⚠️ SocketException attempt $attempts$link');
if (attempts >= 3) return 'no_internet';
await Future.delayed(const Duration(seconds: 1));
} on TimeoutException catch (_) {
Log.print('⚠️ TimeoutException attempt $attempts$link');
if (attempts >= 3) return 'failure';
} catch (e) {
if (e.toString().contains('errno = 9') && attempts < 3) {
await Future.delayed(const Duration(milliseconds: 500));
continue;
}
addError(
'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
return 'failure';
}
}
if (response == null) return 'failure';
final sc = response.statusCode;
final body = response.body;
Log.print('📥 [RES-$requestId] [$sc] $link');
Log.print('📄 [BODY-$requestId] $body');
if (sc >= 200 && sc < 300) {
try {
return jsonDecode(body);
} catch (e, st) {
addError(
'JSON Decode Error', 'Body: $body\n$st', 'CRUD._makeRequest $link');
return 'failure';
}
}
if (sc == 401) {
if (!_isRefreshingJWT && !link.contains('errorApp.php')) {
_isRefreshingJWT = true;
try {
await getJWT();
} finally {
_isRefreshingJWT = false;
}
}
return 'token_expired';
}
if (sc >= 500) {
addError('Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
return 'failure';
}
return 'failure';
}
// ═══════════════════════════════════════════════════════════════
// post — standard POST
// ═══════════════════════════════════════════════════════════════
Future<dynamic> post({
required String link,
Map<String, dynamic>? payload,
}) async {
String token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0];
if (!_isJwtValid(token) &&
!_isRefreshingJWT &&
!link.contains('login.php')) {
_isRefreshingJWT = true;
try {
await getJWT();
token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0];
} finally {
_isRefreshingJWT = false;
}
}
// Initialize app signature if null
_appSignature ??= await SecurityHelper.getAppSignature();
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(),
'X-App-Signature': _appSignature ?? '',
};
return await _makeRequest(link: link, payload: payload, headers: headers);
}
// ═══════════════════════════════════════════════════════════════
// get — standard GET (uses POST method in this architecture)
// ═══════════════════════════════════════════════════════════════
Future<dynamic> get({
required String link,
Map<String, dynamic>? payload,
}) async {
return await post(link: link, payload: payload);
}
// ═══════════════════════════════════════════════════════════════
// getJWT — V1 Login Flow
// ═══════════════════════════════════════════════════════════════
Future<void> getJWT() async {
var payload = {
'fingerprint': _getFpHeader(),
'password': box.read(BoxName.password) ?? '',
'aud': 'service',
};
// Initialize app signature if null
_appSignature ??= await SecurityHelper.getAppSignature();
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'X-App-Signature': _appSignature ?? '',
};
final response = await _makeRequest(
link: AppLink.login, payload: payload, headers: headers);
if (response != 'failure' &&
response is Map &&
response['status'] == 'success') {
final jwt = response['message']['jwt'];
final hmac = response['message']['hmac'];
Log.print('jwt: $jwt');
Log.print('hmac_key: $hmac');
await box.write(BoxName.jwt, c(jwt));
if (hmac != null) {
await box.write(BoxName.hmac, hmac);
final verify = box.read(BoxName.hmac);
Log.print('✅ Verified stored HMAC: $verify');
}
}
}
// ─────────────────────────────────────────────────────────────
// Service Specific Methods (Preserved)
// ─────────────────────────────────────────────────────────────
Future<dynamic> arabicTextExtractByVisionAndAI({
required String imagePath,
required String driverID,
}) async {
var headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': AK.ocpApimSubscriptionKey
};
String imagePathFull =
'${AppLink.server}/card_image/$imagePath-$driverID.jpg';
var request = http.Request(
'POST',
Uri.parse(
'https://eastus.api.cognitive.microsoft.com/computervision/imageanalysis:analyze?features=caption,read&model-version=latest&language=en&api-version=2024-02-01'));
request.body = json.encode({"url": imagePathFull});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
return await response.stream.bytesToString();
}
return 'failure';
}
Future<dynamic> getAgoraToken({
required String channelName,
required String uid,
}) async {
var res = await http.get(
Uri.parse(
'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'),
headers: {'Authorization': 'Bearer '});
if (res.statusCode == 200) {
var response = jsonDecode(res.body);
return response['token'];
}
return 'failure';
}
Future<dynamic> getLlama({
required String link,
required String payload,
required String prompt,
}) async {
var url = Uri.parse(link);
var headers = {
'Content-Type': 'application/json',
'Authorization':
'Bearer LL-X5lJ0Px9CzKK0HTuVZ3u2u4v3tGWkImLTG7okGRk4t25zrsLqJ0qNoUzZ2x4ciPy'
};
var data = json.encode({
"model": "Llama-3-70b-Inst-FW",
"messages": [
{
"role": "user",
"content":
"Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload"
}
],
"temperature": 0.9
});
var response = await http.post(url, body: data, headers: headers);
if (response.statusCode == 200) {
return response.body;
}
return response.statusCode;
}
sendEmail(String link, Map<String, String>? payload) async {
var headers = {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
};
var request = http.Request('POST', Uri.parse(link));
request.bodyFields = payload!;
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {}
}
}

View File

@@ -0,0 +1,49 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:siro_service/constant/box_name.dart';
import 'package:siro_service/controller/functions/encrypt_decrypt.dart';
import '../../main.dart';
import '../../print.dart';
class DeviceHelper {
static Future<String> getDeviceFingerprint() async {
await EncryptionHelper.initialize();
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
var deviceData;
try {
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
deviceData = androidInfo.toMap();
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
deviceData = iosInfo.toMap();
} else {
throw UnsupportedError('Unsupported platform');
}
final String deviceId = Platform.isAndroid
? deviceData['id'] ??
deviceData['androidId'] ??
deviceData['fingerprint'] ??
'unknown'
: deviceData['identifierForVendor'] ?? 'unknown';
final String deviceModel = deviceData['model'] ?? 'unknown';
Log.print('DeviceId: $deviceId');
Log.print('DeviceModel: $deviceModel');
final String fingerprint =
EncryptionHelper.instance.encryptData('${deviceId}_$deviceModel');
Log.print('Generated Fingerprint: $fingerprint');
box.write(BoxName.fingerPrint, fingerprint);
return (fingerprint);
} catch (e) {
Log.print('Error generating device fingerprint: $e');
return '';
}
}
}

View File

@@ -0,0 +1,87 @@
import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:flutter/foundation.dart';
import 'package:siro_service/env/env.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:siro_service/constant/char_map.dart';
import 'package:siro_service/print.dart';
class EncryptionHelper {
static EncryptionHelper? _instance;
late final encrypt.Key key;
late final encrypt.IV iv;
EncryptionHelper._(this.key, this.iv);
static EncryptionHelper get instance {
if (_instance == null) {
throw Exception(
"EncryptionHelper is not initialized. Call `await EncryptionHelper.initialize()` in main.");
}
return _instance!;
}
/// Initializes and stores the instance globally
static Future<void> initialize() async {
if (_instance != null) {
debugPrint("EncryptionHelper is already initialized.");
return; // Prevent re-initialization
}
debugPrint("Initializing EncryptionHelper...");
// Read stored keys
String keyOfApp = r(Env.keyOfApp).toString().split(Env.addd)[0];
String initializationVector = r(Env.initializationVector).toString().split(Env.addd)[0];
Log.print('Key Length: ${keyOfApp.length}');
Log.print('IV Length: ${initializationVector.length}');
// Set the global instance
_instance = EncryptionHelper._(
encrypt.Key.fromUtf8(keyOfApp),
encrypt.IV.fromUtf8(initializationVector),
);
debugPrint("EncryptionHelper initialized successfully.");
}
/// Encrypts a string
String encryptData(String plainText) {
Log.print('Encrypting: $plainText');
try {
final encrypter =
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
final encrypted = encrypter.encrypt(plainText, iv: iv);
return encrypted.base64;
} catch (e) {
Log.print('Encryption Error: $e');
return '';
}
}
/// Decrypts a string
String decryptData(String encryptedText) {
try {
final encrypter =
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
final encrypted = encrypt.Encrypted.fromBase64(encryptedText);
return encrypter.decrypt(encrypted, iv: iv);
} catch (e) {
debugPrint('Decryption Error: $e');
return '';
}
}
}
r(String string) {
var res = X.r(X.r(X.r(string, cn), cC), cs).toString();
// Log.print('r($string) => $res');
return res;
}
c(String string) {
return X.c(X.c(X.c(string, cn), cC), cs).toString();
}

View File

@@ -0,0 +1,340 @@
import 'dart:convert';
import 'dart:io';
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:image/image.dart' as img;
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart' as path_provider;
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../main.dart';
import 'package:siro_service/controller/functions/encrypt_decrypt.dart';
import 'package:siro_service/env/env.dart';
class ImageController extends GetxController {
File? myImage;
bool isloading = false;
CroppedFile? croppedFile;
final picker = ImagePicker();
var image;
Future<img.Image> detectAndCropDocument(File imageFile) async {
img.Image? image = img.decodeImage(await imageFile.readAsBytes());
if (image == null) throw Exception('Unable to decode image');
int left = image.width, top = image.height, right = 0, bottom = 0;
// Threshold for considering a pixel as part of the document (adjust as needed)
const int threshold = 240;
for (int y = 0; y < image.height; y++) {
for (int x = 0; x < image.width; x++) {
final pixel = image.getPixel(x, y);
final luminance = img.getLuminance(pixel);
if (luminance < threshold) {
left = x < left ? x : left;
top = y < top ? y : top;
right = x > right ? x : right;
bottom = y > bottom ? y : bottom;
}
}
}
// Add a small padding
left = (left - 5).clamp(0, image.width);
top = (top - 5).clamp(0, image.height);
right = (right + 5).clamp(0, image.width);
bottom = (bottom + 5).clamp(0, image.height);
return img.copyCrop(image,
x: left, y: top, width: right - left, height: bottom - top);
}
Future<File> rotateImageIfNeeded(File imageFile) async {
img.Image croppedDoc = await detectAndCropDocument(imageFile);
// Check if the document is in portrait orientation
bool isPortrait = croppedDoc.height > croppedDoc.width;
img.Image processedImage;
if (isPortrait) {
// Rotate the image by 90 degrees clockwise
processedImage = img.copyRotate(croppedDoc, angle: 90);
} else {
processedImage = croppedDoc;
}
// Get temporary directory
final tempDir = await path_provider.getTemporaryDirectory();
final tempPath = tempDir.path;
// Create the processed image file
File processedFile = File('$tempPath/processed_image.jpg');
await processedFile.writeAsBytes(img.encodeJpg(processedImage));
return processedFile;
}
Future<File> rotateImage(File imageFile) async {
// Read the image file
img.Image? image = img.decodeImage(await imageFile.readAsBytes());
if (image == null) return imageFile;
// Rotate the image by 90 degrees clockwise
img.Image rotatedImage = img.copyRotate(image, angle: 90);
// Get temporary directory
final tempDir = await path_provider.getTemporaryDirectory();
final tempPath = tempDir.path;
// Create the rotated image file
File rotatedFile = File('$tempPath/rotated_image.jpg');
await rotatedFile.writeAsBytes(img.encodeJpg(rotatedImage));
return rotatedFile;
}
choosImage(String link, String driverId, String imageType) async {
final pickedImage = await picker.pickImage(
source: ImageSource.gallery,
);
if (pickedImage == null) return;
image = File(pickedImage.path);
croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
IOSUiSettings(
title: 'Cropper'.tr,
),
],
);
if (croppedFile == null) return;
myImage = File(croppedFile!.path);
isloading = true;
update();
// Rotate the compressed image
// File rotatedImage = await rotateImage(compressedImage);
File processedImage = await rotateImageIfNeeded(File(croppedFile!.path));
File compressedImage = await compressImage(processedImage);
print('link =$link');
try {
await uploadImage(
compressedImage,
{'driverID': driverId, 'imageType': imageType},
link,
);
} catch (e) {
Get.snackbar('Image Upload Failed'.tr, e.toString(),
backgroundColor: AppColor.redColor);
} finally {
isloading = false;
update();
}
}
choosFace(String link, String imageType) async {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.front,
);
if (pickedImage != null) {
image = File(pickedImage.path);
isloading = true;
update();
// Compress the image
File compressedImage = await compressImage(File(pickedImage.path));
// Save the picked image directly
// File savedImage = File(pickedImage.path);
print('link =$link');
try {
await uploadImage(
compressedImage,
{
'driverID':
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
'imageType': imageType
},
link,
);
} catch (e) {
Get.snackbar('Image Upload Failed'.tr, e.toString(),
backgroundColor: AppColor.redColor);
} finally {
isloading = false;
update();
}
}
}
uploadImage(File file, Map data, String link) async {
var request = http.MultipartRequest(
'POST',
Uri.parse(link), //'https://ride.mobile-app.store/uploadImage1.php'
);
var length = await file.length();
var stream = http.ByteStream(file.openRead());
var multipartFile = http.MultipartFile(
'image',
stream,
length,
filename: basename(file.path),
);
String token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0];
request.headers.addAll({
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'Authorization': 'Bearer $token',
'X-Device-FP': box.read(BoxName.fingerPrint)?.toString() ?? '',
});
// Set the file name to the driverID
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
if (res.statusCode == 200) {
return jsonDecode(res.body);
} else {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
choosImagePicture(String link, String imageType) async {
final pickedImage = await picker.pickImage(
source: ImageSource.gallery,
// preferredCameraDevice: CameraDevice.rear,
// maxHeight: Get.height * .3,
// maxWidth: Get.width * .9,
// imageQuality: 100,
);
image = File(pickedImage!.path);
croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
IOSUiSettings(
title: 'Cropper'.tr,
),
],
);
myImage = File(pickedImage.path);
isloading = true;
update();
// Save the cropped image
// File savedCroppedImage = File(croppedFile!.path);
File compressedImage = await compressImage(File(croppedFile!.path));
print('link =$link');
try {
await uploadImage(
compressedImage,
{
'driverID':
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
'imageType': imageType
},
link,
);
} catch (e) {
Get.snackbar('Image Upload Failed'.tr, e.toString(),
backgroundColor: AppColor.redColor);
} finally {
isloading = false;
update();
}
}
uploadImagePicture(File file, Map data, String link) async {
var request = http.MultipartRequest(
'POST',
Uri.parse(link), //'https://ride.mobile-app.store/uploadImage1.php'
);
var length = await file.length();
var stream = http.ByteStream(file.openRead());
var multipartFile = http.MultipartFile(
'image',
stream,
length,
filename: basename(file.path),
);
String token = r(box.read(BoxName.jwt) ?? '').toString().split(Env.addd)[0];
request.headers.addAll({
'Authorization': 'Bearer $token',
'X-Device-FP': box.read(BoxName.fingerPrint)?.toString() ?? '',
});
// Set the file name to the driverID
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
if (res.statusCode == 200) {
return jsonDecode(res.body);
} else {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
}
Future<File> compressImage(File file) async {
final dir = await path_provider.getTemporaryDirectory();
final targetPath = "${dir.absolute.path}/temp.jpg";
var result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
targetPath,
quality: 70,
minWidth: 1024,
minHeight: 1024,
);
return File(result!.path);
}

View File

@@ -0,0 +1,43 @@
import 'dart:convert';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:siro_service/controller/functions/crud.dart';
import '../../constant/box_name.dart';
import '../../constant/char_map.dart';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../env/env.dart';
import '../../main.dart';
import '../../print.dart';
class AppInitializer {
List<Map<String, dynamic>> links = [];
// Future<void> initializeApp() async {
// if (box.read(BoxName.jwt) == null) {
// await CRUD().getJWT();
// } else {
// bool isTokenExpired = JwtDecoder.isExpired(X
// .r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs)
// .toString()
// .split(AppInformation.addd)[0]);
// if (isTokenExpired) {
// await CRUD().getJWT();
// }
// }
// }
getAIKey(String key1) async {
if (box.read(BoxName.firstTimeLoadKey) == null) {
var res =
await CRUD().get(link: Env.getapiKey, payload: {"keyName": key1});
if (res != 'failure') {
var d = res['message'];
await storage.write(key: key1, value: d[key1].toString());
await Future.delayed(Duration.zero);
} else {}
}
}
}

View File

@@ -0,0 +1,76 @@
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
void showInBrowser(String url) async {
if (await canLaunchUrl(Uri.parse(url))) {
launchUrl(Uri.parse(url));
} else {}
}
Future<void> makePhoneCall(String phoneNumber) async {
final Uri launchUri = Uri(
scheme: 'tel',
path: phoneNumber,
);
await launchUrl(launchUri);
}
void launchCommunication(
String method, String contactInfo, String message) async {
String url;
if (Platform.isIOS) {
switch (method) {
case 'phone':
url = 'tel:$contactInfo';
break;
case 'sms':
url = 'sms:$contactInfo?body=${Uri.encodeComponent(message)}';
break;
case 'whatsapp':
url =
'https://api.whatsapp.com/send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
break;
case 'email':
url =
'mailto:$contactInfo?subject=Subject&body=${Uri.encodeComponent(message)}';
break;
default:
return;
}
} else if (Platform.isAndroid) {
switch (method) {
case 'phone':
url = 'tel:$contactInfo';
break;
case 'sms':
url = 'sms:$contactInfo?body=${Uri.encodeComponent(message)}';
break;
case 'whatsapp':
// Check if WhatsApp is installed
final bool whatsappInstalled =
await canLaunchUrl(Uri.parse('whatsapp://'));
if (whatsappInstalled) {
url =
'whatsapp://send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
} else {
// Provide an alternative action, such as opening the WhatsApp Web API
url =
'https://api.whatsapp.com/send?phone=$contactInfo&text=${Uri.encodeComponent(message)}';
}
break;
case 'email':
url =
'mailto:$contactInfo?subject=Subject&body=${Uri.encodeComponent(message)}';
break;
default:
return;
}
} else {
return;
}
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {}
}

View File

@@ -0,0 +1,32 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:siro_service/print.dart';
class SecurityHelper {
static const platform = MethodChannel('com.service_intaleq/security');
static Future<String?> getAppSignature() async {
try {
final String? signature = await platform.invokeMethod('getAppSignature');
final mode = kDebugMode ? 'DEBUG' : 'RELEASE';
Log.print('----------------------------------------------------');
Log.print('🚀 APP SIGNATURE HASH ($mode): $signature');
Log.print('----------------------------------------------------');
return signature;
} on PlatformException catch (e) {
Log.print('❌ Failed to get app signature: ${e.message}');
return null;
}
}
static Future<bool> isDeviceRooted() async {
try {
final bool isRooted = await platform.invokeMethod('isNativeRooted');
return isRooted;
} on PlatformException catch (e) {
Log.print('❌ Failed to check root: ${e.message}');
return false;
}
}
}

View File

@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
import '../themes/themes.dart';
class LocaleController extends GetxController {
Locale? language;
String countryCode = '';
ThemeData appTheme = lightThemeEnglish;
void changeLang(String langcode) {
Locale locale;
switch (langcode) {
case "ar":
locale = const Locale("ar");
appTheme = lightThemeArabic;
box.write(BoxName.lang, 'ar');
break;
case "en":
locale = const Locale("en");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'en');
break;
case "tr":
locale = const Locale("tr");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'tr');
break;
case "fr":
locale = const Locale("fr");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'fr');
break;
case "it":
locale = const Locale("it");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'it');
break;
case "de":
locale = const Locale("de");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'de');
break;
case "el":
locale = const Locale("el");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'el');
break;
case "es":
locale = const Locale("es");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'es');
break;
case "fa":
locale = const Locale("fa");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'fa');
break;
case "zh":
locale = const Locale("zh");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'zh');
break;
case "ru":
locale = const Locale("ru");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'ru');
break;
case "hi":
locale = const Locale("hi");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'hi');
break;
default:
locale = Locale(Get.deviceLocale!.languageCode);
box.write(BoxName.lang, Get.deviceLocale!.languageCode);
appTheme = lightThemeEnglish;
break;
}
box.write(BoxName.lang, langcode);
Get.changeTheme(appTheme);
Get.updateLocale(locale);
update();
}
@override
void onInit() {
String? storedLang = box.read(BoxName.lang);
if (storedLang == null) {
// Use device language if no language is stored
storedLang = Get.deviceLocale!.languageCode;
box.write(BoxName.lang, storedLang);
}
changeLang(storedLang);
super.onInit();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,330 @@
import 'dart:async';
import 'dart:io';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../../main.dart';
class NotificationController extends GetxController {
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
@override
void onInit() {
super.onInit();
initNotifications();
}
// Initializes the local notifications plugin
Future<void> initNotifications() async {
const AndroidInitializationSettings android =
AndroidInitializationSettings('@mipmap/launcher_icon');
DarwinInitializationSettings ios = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
// onDidReceiveLocalNotification:
// (int id, String? title, String? body, String? payload) async {},
);
InitializationSettings initializationSettings =
InitializationSettings(android: android, iOS: ios);
await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
tz.initializeTimeZones();
print('Notifications initialized');
}
// Displays a notification with the given title and message
void showNotification(String title, String message, String tone) async {
final AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
showWhen: false,
sound: RawResourceAndroidNotificationSound(tone),
);
const DarwinNotificationDetails ios = DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
await _flutterLocalNotificationsPlugin.show(0, title, message, details);
print('Notification shown: $title - $message');
}
// /Users/hamzaaleghwairyeen/development/App/ride 2/lib/controller/firebase/local_notification.dart
// Assume _flutterLocalNotificationsPlugin is initialized somewhere in your code
// void scheduleNotificationsForSevenDays(
// String title, String message, String tone) async {
// final AndroidNotificationDetails android = AndroidNotificationDetails(
// 'high_importance_channel',
// 'High Importance Notifications',
// importance: Importance.max,
// priority: Priority.high,
// sound: RawResourceAndroidNotificationSound(tone),
// );
// const DarwinNotificationDetails ios = DarwinNotificationDetails(
// sound: 'default',
// presentAlert: true,
// presentBadge: true,
// presentSound: true,
// );
// final NotificationDetails details =
// NotificationDetails(android: android, iOS: ios);
// // Check for the exact alarm permission on Android 12 and above
// if (Platform.isAndroid) {
// if (await Permission.scheduleExactAlarm.isDenied) {
// if (await Permission.scheduleExactAlarm.request().isGranted) {
// print('SCHEDULE_EXACT_ALARM permission granted');
// } else {
// print('SCHEDULE_EXACT_ALARM permission denied');
// return;
// }
// }
// }
// // Schedule notifications for the next 7 days
// for (int day = 0; day < 7; day++) {
// // Schedule for 8:00 AM
// await _scheduleNotificationForTime(
// day, 8, 0, title, message, details, day * 1000 + 1);
// // Schedule for 3:00 PM
// await _scheduleNotificationForTime(
// day, 15, 0, title, message, details, day * 1000 + 2); // Unique ID
// // Schedule for 8:00 PM
// await _scheduleNotificationForTime(
// day, 20, 0, title, message, details, day * 1000 + 3); // Unique ID
// }
// print('Notifications scheduled successfully for the next 7 days');
// }
void scheduleNotificationsForSevenDays(
String title, String message, String tone) async {
final AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound(tone),
);
const DarwinNotificationDetails ios = DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
// Check for the exact alarm permission on Android 12 and above
if (Platform.isAndroid) {
if (await Permission.scheduleExactAlarm.isDenied) {
if (await Permission.scheduleExactAlarm.request().isGranted) {
print('SCHEDULE_EXACT_ALARM permission granted');
} else {
print('SCHEDULE_EXACT_ALARM permission denied');
return;
}
}
}
// Schedule notifications for the next 7 days
for (int day = 0; day < 7; day++) {
// List of notification times
final notificationTimes = [
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1}, // 8:00 AM
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2}, // 3:00 PM
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3}, // 8:00 PM
];
for (var time in notificationTimes) {
final notificationId = time['id'] as int;
// Check if this notification ID is already stored
bool isScheduled = box.read('notification_$notificationId') ?? false;
if (!isScheduled) {
// Schedule the notification if not already scheduled
await _scheduleNotificationForTime(
day,
time['hour'] as int,
time['minute'] as int,
title,
message,
details,
notificationId,
);
// Mark this notification ID as scheduled in GetStorage
box.write('notification_$notificationId', true);
} else {
print('Notification with ID $notificationId is already scheduled.');
}
}
}
print('Notifications scheduled successfully for the next 7 days');
}
void scheduleNotificationsForTimeSelected(
String title, String message, String tone, DateTime timeSelected) async {
final AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound(tone),
);
const DarwinNotificationDetails ios = DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
// Check for the exact alarm permission on Android 12 and above
if (Platform.isAndroid) {
if (await Permission.scheduleExactAlarm.isDenied) {
if (await Permission.scheduleExactAlarm.request().isGranted) {
print('SCHEDULE_EXACT_ALARM permission granted');
} else {
print('SCHEDULE_EXACT_ALARM permission denied');
return;
}
}
}
// Schedule notifications for 10 and 30 minutes before the timeSelected
await _scheduleNotificationForTimeVIP(
timeSelected.subtract(const Duration(minutes: 10)), // 10 minutes before
title,
message,
details,
1, // Unique ID for 10-minute before notification
);
await _scheduleNotificationForTimeVIP(
timeSelected.subtract(const Duration(minutes: 30)), // 30 minutes before
title,
message,
details,
2, // Unique ID for 30-minute before notification
);
print('Notifications scheduled successfully for the time selected');
}
Future<void> _scheduleNotificationForTimeVIP(
DateTime scheduledDate,
String title,
String message,
NotificationDetails details,
int notificationId,
) async {
// Initialize and set Cairo timezone
tz.initializeTimeZones();
var cairoLocation = tz.getLocation('Africa/Cairo');
final now = tz.TZDateTime.now(cairoLocation);
// Convert to Cairo time
tz.TZDateTime scheduledTZDateTime =
tz.TZDateTime.from(scheduledDate, cairoLocation);
// Check if 10 minutes before the scheduled time is in the past
if (scheduledTZDateTime
.subtract(const Duration(minutes: 10))
.isBefore(now)) {
// If the 10 minutes before the scheduled time is in the past, don't schedule
print(
'Scheduled time minus 10 minutes is in the past. Skipping notification.');
return; // Skip this notification
}
print('Current time (Cairo): $now');
print('Scheduling notification for: $scheduledTZDateTime');
await _flutterLocalNotificationsPlugin.zonedSchedule(
notificationId, // Unique ID for each notification
title,
message,
scheduledTZDateTime,
details,
androidScheduleMode: AndroidScheduleMode.exact,
// uiLocalNotificationDateInterpretation:
// UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents:
null, // Don't repeat automatically; we handle manually
);
print('Notification scheduled successfully for: $scheduledTZDateTime');
}
Future<void> _scheduleNotificationForTime(
int dayOffset,
int hour,
int minute,
String title,
String message,
NotificationDetails details,
int notificationId,
) async {
// Initialize and set Cairo timezone
tz.initializeTimeZones();
var cairoLocation = tz.getLocation('Africa/Cairo');
final now = tz.TZDateTime.now(cairoLocation);
tz.TZDateTime scheduledDate = tz.TZDateTime(
cairoLocation,
now.year,
now.month,
now.day + dayOffset, // Add offset to schedule for the next days
hour,
minute,
);
// If the scheduled time is in the past, move it to the next day
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
print('Current time (Cairo): $now');
print('Scheduling notification for: $scheduledDate');
await _flutterLocalNotificationsPlugin.zonedSchedule(
notificationId, // Unique ID for each notification
title,
message,
scheduledDate,
details,
androidScheduleMode: AndroidScheduleMode.exact,
// uiLocalNotificationDateInterpretation:
// UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents:
null, // Don't repeat automatically; we handle 7 days manually
);
print('Notification scheduled successfully for: $scheduledDate');
}
}

View File

@@ -0,0 +1,89 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/links.dart';
import 'package:siro_service/controller/functions/crud.dart';
import 'package:siro_service/controller/functions/device_helper.dart';
import 'package:siro_service/controller/functions/encrypt_decrypt.dart';
import '../constant/box_name.dart';
import '../main.dart';
import '../print.dart';
import '../views/home/main.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class LoginController extends GetxController {
var email = TextEditingController(); // Kept for UI compatibility
var password = TextEditingController();
final formKey = GlobalKey<FormState>();
final FlutterSecureStorage storage = const FlutterSecureStorage();
void login() async {
// Ensure fingerprint is ready
String fingerprint = box.read(BoxName.fingerPrint) ?? '';
if (fingerprint.isEmpty) {
fingerprint = await DeviceHelper.getDeviceFingerprint();
}
String? storedPassword = await storage.read(key: 'password');
String pass = storedPassword ?? password.text;
if (pass.isEmpty && !formKey.currentState!.validate()) return;
var payload = {
"fingerprint": fingerprint,
"password": pass,
"aud": "service",
};
Log.print('🚀 Login Payload: $payload');
var res = await CRUD().post(link: AppLink.login, payload: payload);
Log.print('📥 Login Response: $res');
if (res != 'failure' && res is Map && res['status'] == 'success') {
var d = res[
'message']; // V1 returns {status, message: {jwt, data: {user...}}}
// Store JWT & HMAC
final jwt = d['jwt'];
final hmac = d['hmac'];
await box.write(BoxName.jwt, c(jwt));
if (hmac != null) {
await box.write(BoxName.hmac, hmac);
}
// Store User Data
var userData = d['data'];
await storage.write(key: 'name', value: userData['first_name']);
await storage.write(key: 'driverID', value: userData['id'].toString());
await storage.write(key: 'password', value: pass);
await box.write(BoxName.employeename, userData['first_name']);
await box.write(BoxName.password, pass);
Get.offAll(() => Main());
} else {
Get.snackbar(
'خطأ'.tr,
res is Map ? res['message'].toString().tr : 'فشل تسجيل الدخول'.tr,
backgroundColor: Colors.red.withOpacity(0.7),
colorText: Colors.white,
);
}
}
@override
void onInit() async {
await EncryptionHelper.initialize();
await DeviceHelper.getDeviceFingerprint();
// Auto login if credentials exist
String? storedPassword = await storage.read(key: 'password');
if (storedPassword != null) {
login();
}
super.onInit();
}
}

View File

@@ -0,0 +1,567 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/colors.dart';
import 'package:siro_service/constant/links.dart';
import 'package:siro_service/controller/functions/crud.dart';
import 'package:siro_service/controller/mainController/pages/driver_page.dart';
import 'package:siro_service/main.dart';
import 'package:siro_service/views/widgets/my_dialog.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../print.dart';
import 'pages/passengers_page.dart';
class MainController extends GetxController {
final formKey = GlobalKey<FormState>();
bool isLoading = false;
final passengerPhoneController = TextEditingController();
final driverPhoneController = TextEditingController();
final notesController = TextEditingController();
final carplateController = TextEditingController();
TextEditingController colorController = TextEditingController();
TextEditingController makeController = TextEditingController();
TextEditingController modelController = TextEditingController();
TextEditingController expirationDateController = TextEditingController();
TextEditingController yearController = TextEditingController();
TextEditingController ownerController = TextEditingController();
TextEditingController carOwnerWorkController = TextEditingController();
TextEditingController driverNameController = TextEditingController();
TextEditingController nationalIdController = TextEditingController();
TextEditingController birthDateController = TextEditingController();
TextEditingController licenseTypeController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController phoneCarController = TextEditingController();
TextEditingController carNumberController = TextEditingController();
TextEditingController manufactureYearController = TextEditingController();
TextEditingController carModelController = TextEditingController();
TextEditingController carTypeController = TextEditingController();
TextEditingController siteCarController = TextEditingController();
TextEditingController siteDriverController = TextEditingController();
TextEditingController registrationDateController = TextEditingController();
Map passengerData = {};
Map driverData = {};
List filteredDrivers = [];
var color = ''.obs;
var colorHex = ''.obs;
@override
void onInit() {
super.onInit();
// refreshDashboardStats(); // Removed to save data consumption at start
}
Future<void> refreshDashboardStats() async {
isLoading = true;
update();
try {
await Future.wait<void>([
getDriverWantCompleteRegistration(),
getDriverNotCompleteRegistration(),
getNewDriverRegister(),
]);
} catch (e) {
Log.print('Error refreshing stats: $e');
}
isLoading = false;
update();
}
Future<void> searchPassengerByPhone() async {
if (formKey.currentState == null || formKey.currentState!.validate()) {
isLoading = true;
update();
await getPassengersByPhone();
isLoading = false;
update();
Get.back();
if (passengerData.isEmpty) {
Get.snackbar('Error'.tr, 'Passenger not found'.tr,
backgroundColor: Colors.red, colorText: Colors.white);
return;
}
Get.to(() => PassengersPage());
}
}
void searchDrivers(String query) {
if (query.isEmpty) {
filteredDrivers = driverNotCompleteRegistration;
update();
} else {
filteredDrivers = driverNotCompleteRegistration
.where((driver) => driver['phone_number']
.toString()
.toLowerCase()
.contains(query.toLowerCase()))
.toList();
update();
}
}
void updateDriverField(String key, dynamic value) async {
// Update locally
driverData['message'][0][key] = value;
Log.print('driverData: ${driverData['message'][0]['driverID']}');
update();
var res = await CRUD().post(link: AppLink.updateDriver, payload: {
'driverID': driverData['message'][0]['driverID'].toString(),
key: value.toString(),
});
if (res == 'failure') {
Get.snackbar('Error', 'Failed to update driver data',
backgroundColor: AppColor.redColor);
} else {
Get.snackbar('Success', 'Driver data updated successfully',
backgroundColor: AppColor.greenColor);
}
// Optionally fetch driver again
// await getDriverData();
}
Future<void> makePhoneCall(String phoneNumber) async {
final Uri launchUri = Uri(
scheme: 'tel',
path: phoneNumber,
);
await launchUrl(launchUri);
}
Future<void> launchCommunication(
String method, String contactInfo, String message) async {
// رقّم فقط (بدون + أو مسافات)
final phone = contactInfo.replaceAll(RegExp(r'[^0-9]'), '');
final encodedMsg = Uri.encodeComponent(message);
Uri? uri;
if (Platform.isIOS) {
switch (method) {
case 'phone':
uri = Uri.parse('tel:$phone');
break;
case 'sms':
uri = Uri.parse('sms:$phone?body=$encodedMsg');
break;
case 'whatsapp':
uri = Uri.parse(
'https://api.whatsapp.com/send?phone=$phone&text=$encodedMsg');
break;
case 'email':
uri =
Uri.parse('mailto:$contactInfo?subject=Subject&body=$encodedMsg');
break;
default:
return;
}
} else if (Platform.isAndroid) {
switch (method) {
case 'phone':
uri = Uri.parse('tel:$phone');
break;
case 'sms':
uri = Uri.parse('sms:$phone?body=$encodedMsg');
break;
case 'whatsapp':
{
final waDeepLink =
Uri.parse('whatsapp://send?phone=$phone&text=$encodedMsg');
if (await canLaunchUrl(waDeepLink)) {
await launchUrl(waDeepLink, mode: LaunchMode.externalApplication);
return;
} else {
final webUri = Uri.parse(
'https://api.whatsapp.com/send?phone=$phone&text=$encodedMsg');
if (await canLaunchUrl(webUri)) {
await launchUrl(webUri, mode: LaunchMode.externalApplication);
return;
}
// لو ما في متصفح أساسًا
throw 'No handler for WhatsApp links';
}
}
case 'email':
uri =
Uri.parse('mailto:$contactInfo?subject=Subject&body=$encodedMsg');
break;
default:
return;
}
} else {
return;
}
final ok = await canLaunchUrl(uri);
if (ok) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
// ممكن تضيف Snackbar/Toast هنا
}
}
List driverNotCompleteRegistration = [];
Future<void> getDriverNotCompleteRegistration() async {
var res = await CRUD()
.get(link: AppLink.getDriverNotCompleteRegistration, payload: {});
if (res != 'failure') {
var d = res['message'];
driverNotCompleteRegistration = d;
filteredDrivers = driverNotCompleteRegistration;
update();
} else {
driverNotCompleteRegistration = [];
update();
}
}
Future<void> deleteDriverNotCompleteRegistration(String phone) async {
var res = await CRUD()
.get(link: AppLink.deleteDriverNotCompleteRegistration, payload: {
'phone': phone,
});
if (res != 'failure') {
Get.snackbar(res, '', backgroundColor: AppColor.greenColor);
// await getDriverWantCompleteRegistration();
update();
} else {
Get.snackbar(res, '');
}
}
List driverWantCompleteRegistration = [];
Future<void> getDriverWantCompleteRegistration() async {
var res =
await CRUD().get(link: AppLink.getDriversWaitingActive, payload: {});
if (res != 'failure') {
var d = res['message'];
driverWantCompleteRegistration = d;
filteredDrivers = driverWantCompleteRegistration;
update();
} else {
driverWantCompleteRegistration = [];
update();
}
}
List driversPhoneNotComplete = [];
Future<void> getDriversPhoneNotComplete() async {
var res =
await CRUD().get(link: AppLink.getDriversPhoneNotComplete, payload: {});
if (res != 'failure') {
var d = res['message'];
driverWantCompleteRegistration = d;
filteredDrivers = driverWantCompleteRegistration;
update();
} else {
Get.snackbar(res, '');
}
}
List newDriverRegister = [];
Future<void> getNewDriverRegister() async {
var res = await CRUD().get(link: AppLink.getNewDriverRegister, payload: {});
if (res != 'failure') {
var d = res['message'];
newDriverRegister = d;
update();
} else {
newDriverRegister = [];
update();
}
}
Future<void> addWelcomeCall(String driveId) async {
var res = await CRUD().post(link: AppLink.addWelcomeDriverNote, payload: {
"driverId": driveId,
"notes": notesController.text,
});
if (res != 'failure') {
Get.snackbar('Success'.tr, '', backgroundColor: AppColor.greenColor);
}
}
String selectedStatus = "I'm not ready yet".tr;
List passengerNotCompleteRegistration = [];
Future<void> getPassengerNotCompleteRegistration() async {
var res = await CRUD()
.get(link: AppLink.getPassengersNotCompleteRegistration, payload: {});
if (res != 'failure') {
var d = res['message'];
passengerNotCompleteRegistration = d;
update();
} else {
Get.snackbar(res, '');
}
}
void setSelectedStatus(String status) {
selectedStatus = status;
update();
}
final List<String> statusOptions = [
"I'm not ready yet".tr,
"I don't have a suitable vehicle".tr,
"I'll register when the app is fully launched".tr,
"I need more help understanding the app".tr,
"My documents have expired".tr,
];
List carPlateNotEdit = [];
getCarPlateNotEdit() async {
var res = await CRUD().get(link: AppLink.getCarPlateNotEdit, payload: {});
if (res != 'failure') {
var d = res['message'];
carPlateNotEdit = d;
update();
} else {
MyDialog().getDialog('No Car found yet'.tr, 'thanks'.tr, const SizedBox(),
() {
Get.back();
});
}
}
List driverWithoutCar = [];
getdriverWithoutCar() async {
var res = await CRUD().get(link: AppLink.getdriverWithoutCar, payload: {});
if (res != 'failure') {
var d = res['message'];
driverWithoutCar = d;
update();
} else {
MyDialog().getDialog('No Car found yet'.tr, 'thanks'.tr, const SizedBox(),
() {
Get.back();
});
}
}
Future<void> addRegistrationCarEgyptHandling({
required String driverId,
required String carPlate,
required String color,
required String colorHex,
required String year,
required String make,
required String model,
required String expirationDate,
required String owner,
}) async {
try {
isLoading = true;
update();
var payload = {
'driverID': driverId,
'vin': 'vin',
'car_plate': carPlate,
'make': make,
'model': model,
'year': year,
'expiration_date': expirationDate,
'color': color,
'owner': owner,
'color_hex': colorHex,
'address': 'addressCar',
'displacement': 'displacement',
'fuel': 'fuel',
'registration_date': '2024-09-06',
};
Log.print('Payload: $payload');
var res =
await CRUD().post(link: AppLink.addCartoDriver, payload: payload);
isLoading = false;
update();
var status = res;
Log.print('res: $res');
Log.print('status: $status');
if (status != 'failure' && status['status'] == 'success') {
await Future.wait([
CRUD().post(
link:
'${AppLink.seferAlexandriaServer}/ride/RegisrationCar/add.php',
payload: payload),
CRUD().post(
link: '${AppLink.seferGizaServer}/ride/RegisrationCar/add.php',
payload: payload),
]);
Get.snackbar('Success', 'Registration successful',
backgroundColor: AppColor.greenColor);
Get.back();
} else {
Log.print('Error: Unexpected status: ${status['status']}');
Get.snackbar('Error', 'Registration failed',
backgroundColor: Colors.red);
}
} catch (e) {
Log.print('Error: $e');
Get.snackbar('Error', 'An error occurred during registration',
backgroundColor: Colors.red);
}
}
editCarPlateNotEdit(
String driverId,
String carPlate,
String color,
String colorHex,
String year,
String make,
String model,
String expirationDate,
String owner,
) async {
var res = await CRUD().post(link: AppLink.editCarPlate, payload: {
"driverId": driverId,
"carPlate": carPlate,
"color": color,
"color_hex": colorHex,
"make": make,
"year": year,
"model": model,
"expiration_date": expirationDate.toString(),
"owner": owner,
"employee": storage.read(key: 'name').toString(),
});
Log.print('res: ${res}');
if (res != 'failure') {
// Get.snackbar(res, '', backgroundColor: AppColor.greenColor);
Get.back();
carplateController.clear();
yearController.clear();
makeController.clear();
modelController.clear();
ownerController.clear();
await getCarPlateNotEdit();
update();
} else {
Get.snackbar(res, '', backgroundColor: AppColor.redColor);
}
}
// editCarPlateNotEdit(String driverId, carPlate) async {
// var res = await CRUD().post(link: AppLink.editCarPlate, payload: {
// "driverId": driverId,
// "carPlate": carPlate,
// });
// if (res != 'failure') {
// Get.snackbar(res, '', backgroundColor: AppColor.greenColor);
// carplateController.clear();
// await getCarPlateNotEdit();
// update();
// } else {
// Get.snackbar(res, '', backgroundColor: AppColor.redColor);
// }
// }
saveNoteForDriverNotCompleteRegistration(String phone, editor, note) async {
var res = await CRUD().post(
link: AppLink.addNotesDriver,
payload: {"phone": phone, "editor": editor, "note": note});
if (res != 'failure') {
Get.snackbar(res, '', backgroundColor: AppColor.greenColor);
getDriversPhoneNotComplete();
notesController.clear();
} else {
Get.snackbar(res, '', backgroundColor: AppColor.redColor);
}
}
saveNoteForPassengerNotCompleteRegistration(
String phone, editor, note) async {
var res = await CRUD().post(
link: AppLink.addNotesPassenger,
payload: {"phone": phone, "editor": editor, "note": note});
if (res != 'failure') {
Get.snackbar(res, '', backgroundColor: AppColor.greenColor);
notesController.clear();
} else {
Get.snackbar(res, '', backgroundColor: AppColor.redColor);
}
}
searchDriverByPhone() async {
if (formKey.currentState == null || formKey.currentState!.validate()) {
isLoading = true;
update();
await getDriverByPhone();
isLoading = false;
update();
Get.back();
if (driverData.isEmpty) {
Get.snackbar('Error'.tr, 'Driver not found'.tr,
backgroundColor: Colors.red, colorText: Colors.white);
return;
}
Get.to(() => DriverPage());
}
}
searchDriverByNational() async {
if (formKey.currentState == null || formKey.currentState!.validate()) {
isLoading = true;
update();
await getDriverByNational();
isLoading = false;
update();
Get.back();
if (driverData.isEmpty) {
Get.snackbar('Error'.tr, 'Driver not found'.tr,
backgroundColor: Colors.red, colorText: Colors.white);
return;
}
Get.to(() => DriverPage());
}
}
getPassengersByPhone() async {
var res = await CRUD().get(
link: AppLink.getPassengersByPhone,
payload: {"phone": passengerPhoneController.text});
if (res != 'failure') {
var d = res;
passengerData = d;
update();
}
}
getDriverByPhone() async {
var res = await CRUD().get(
link: AppLink.getDriverByPhone,
payload: {"phone": driverPhoneController.text});
if (res != 'failure') {
var d = res;
driverData = d;
update();
} else {
Get.snackbar('Error', 'Driver not found', backgroundColor: Colors.red);
}
}
getDriverByNational() async {
var res = await CRUD().get(
link: AppLink.getDriverByNational,
payload: {"national_number": driverPhoneController.text});
if (res != 'failure') {
var d = res;
driverData = d;
update();
} else {
Get.snackbar('Error', 'Driver not found', backgroundColor: Colors.red);
}
}
}

View File

@@ -0,0 +1,378 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:siro_service/constant/style.dart';
import 'package:siro_service/controller/functions/launch.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../views/widgets/my_textField.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/image.dart';
import '../main_controller.dart';
class AddCar extends StatelessWidget {
const AddCar({super.key});
@override
Widget build(BuildContext context) {
Get.put(MainController());
return GetBuilder<MainController>(builder: (mainController) {
return MyScaffold(
title: 'Edit car details'.tr,
isleading: true,
body: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: mainController
.driverWithoutCar.length, // 10 fields + 1 save button
itemBuilder: (context, index) {
var carData = mainController.driverWithoutCar[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
Get.to(AddCarForm(carData: carData));
},
child: Container(
decoration: AppStyle.boxDecoration1,
child: Text((carData['name_arabic']))),
),
);
}),
),
],
);
});
}
}
class AddCarForm extends StatelessWidget {
final Map carData;
const AddCarForm({super.key, required this.carData});
@override
Widget build(BuildContext context) {
Get.put(MainController());
return GetBuilder<MainController>(builder: (mainController) {
return MyScaffold(
title: 'Add Car',
action: Row(
children: [
IconButton(
onPressed: () {
makePhoneCall(carData['phone']);
},
icon: const Icon(Icons.phone),
),
IconButton(
onPressed: () {
launchCommunication('whatsapp', carData['phone'], '');
},
icon: const Icon(
Icons.message,
color: AppColor.greenColor,
),
),
],
),
isleading: true,
body: [
ListView(
children: [
Column(
children: [
GestureDetector(
onLongPress: () async {
await ImageController().choosImage(
AppLink.uploadEgypt, carData['id'], 'car_front');
},
child: Image.network(
'${AppLink.server}/card_image/car_front-${carData['id']}.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
// If the image fails to load, use the _copy version
return Image.network(
'${AppLink.server}/card_image/car_front-${carData['id']}_copy.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
);
},
),
),
GestureDetector(
onLongPress: () async {
await ImageController().choosImage(
AppLink.uploadEgypt, carData['id'], 'car_back');
},
child: Image.network(
'${AppLink.server}/card_image/car_back-${carData['id']}.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
// If the image fails to load, use the _copy version
return Image.network(
'${AppLink.server}/card_image/car_back-${carData['id']}_copy.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
);
},
),
),
],
),
const SizedBox(height: 9),
Form(
key: mainController.formKey,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: Get.width * .6,
child: MyTextForm(
controller: mainController.carplateController,
label: 'car plate'.tr,
hint: 'car plate'.tr,
type: TextInputType.name,
),
),
IconButton(
onPressed: () async {
if (mainController.formKey.currentState!
.validate()) {
await mainController
.addRegistrationCarEgyptHandling(
driverId: carData['id'].toString(),
carPlate:
mainController.carplateController.text,
color: mainController.colorController.text,
colorHex:
mainController.colorHex.value.toString(),
year: mainController.yearController.text,
make: mainController.makeController.text,
model: mainController.modelController.text,
expirationDate: mainController
.expirationDateController.text,
owner: mainController.ownerController.text,
);
}
},
icon: const Icon(
Icons.upload_outlined,
color: AppColor.blueColor,
),
),
],
),
// Other fields
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.yearController,
label: 'Year'.tr,
hint: 'Year'.tr,
type: TextInputType.number,
),
),
SizedBox(
width: Get.width * .4,
child: DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Color'.tr, // Localized label
),
value: mainController.colorHex.value.isEmpty
? null
: mainController.colorHex
.value, // Use the hex value as the current value
items: [
{'red'.tr: '#FF0000'},
{'green'.tr: '#008000'},
{'blue'.tr: '#0000FF'},
{'black'.tr: '#000000'},
{'white'.tr: '#FFFFFF'},
{'yellow'.tr: '#FFFF00'},
{'purple'.tr: '#800080'},
{'orange'.tr: '#FFA500'},
{'pink'.tr: '#FFC0CB'},
{'brown'.tr: '#A52A2A'},
{'gray'.tr: '#808080'},
{'cyan'.tr: '#00FFFF'},
{'magenta'.tr: '#FF00FF'},
{'lime'.tr: '#00FF00'},
{'indigo'.tr: '#4B0082'},
{'violet'.tr: '#EE82EE'},
{'gold'.tr: '#FFD700'},
{'silver'.tr: '#C0C0C0'},
{'teal'.tr: '#008080'},
{'navy'.tr: '#000080'},
].map((colorMap) {
String colorName = colorMap.keys.first;
String colorValue = colorMap.values.first;
return DropdownMenuItem<String>(
value: colorValue,
child: Text(colorName),
);
}).toList(),
onChanged: (value) {
if (value != null) {
// Find the selected color name based on the hex value
String selectedColorName = '';
for (var colorMap in [
{'red'.tr: '#FF0000'},
{'green'.tr: '#008000'},
{'blue'.tr: '#0000FF'},
{'black'.tr: '#000000'},
{'white'.tr: '#FFFFFF'},
{'yellow'.tr: '#FFFF00'},
{'purple'.tr: '#800080'},
{'orange'.tr: '#FFA500'},
{'pink'.tr: '#FFC0CB'},
{'brown'.tr: '#A52A2A'},
{'gray'.tr: '#808080'},
{'cyan'.tr: '#00FFFF'},
{'magenta'.tr: '#FF00FF'},
{'lime'.tr: '#00FF00'},
{'indigo'.tr: '#4B0082'},
{'violet'.tr: '#EE82EE'},
{'gold'.tr: '#FFD700'},
{'silver'.tr: '#C0C0C0'},
{'teal'.tr: '#008080'},
{'navy'.tr: '#000080'},
]) {
if (colorMap.values.first == value) {
selectedColorName = colorMap.keys.first;
break;
}
}
mainController.colorController.text =
selectedColorName;
mainController.colorHex.value = value;
}
},
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.makeController,
label: 'Make'.tr,
hint: 'Make'.tr,
type: TextInputType.name,
),
),
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.modelController,
label: 'Model'.tr,
hint: 'Model'.tr,
type: TextInputType.name,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: TextField(
controller:
mainController.expirationDateController,
decoration: InputDecoration(
labelText: 'Expiration Date'.tr,
hintText: 'Expiration Date'.tr,
),
readOnly:
true, // Make the field read-only to prevent manual input
onTap: () async {
DateTime pickedDate =
DateTime.now(); // Declare the variable here
await showCupertinoModalPopup<void>(
context: context,
builder: (context) => Container(
height: 250,
color: Colors.white,
child: Column(
children: [
SizedBox(
height: 150,
child: CupertinoDatePicker(
initialDateTime: pickedDate,
minimumDate: DateTime(
1955), // Set the starting date
maximumDate: DateTime(
2034), // Set the ending date
mode: CupertinoDatePickerMode.date,
onDateTimeChanged:
(DateTime dateTime) {
pickedDate = dateTime;
},
),
),
CupertinoButton(
child: Text('Done'.tr),
onPressed: () {
String formattedDate =
DateFormat('yyyy-MM-dd')
.format(pickedDate);
mainController
.expirationDateController
.text =
formattedDate.toString();
Navigator.of(context).pop();
},
),
],
),
),
);
},
),
),
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.ownerController,
label: 'Owner'.tr,
hint: 'Owner'.tr,
type: TextInputType.name,
),
),
],
),
],
),
)
],
)
]);
});
}
}

View File

@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../../best_driver_controllers.dart';
import '../../functions/encrypt_decrypt.dart';
class DriverTheBestAlexandria extends StatelessWidget {
const DriverTheBestAlexandria({super.key});
@override
Widget build(BuildContext context) {
Get.put(DriverTheBestAlexandriaController(), permanent: true);
return MyScaffold(
title: 'Alexandria'.tr,
body: [
GetBuilder<DriverTheBestAlexandriaController>(builder: (driverthebest) {
return driverthebest.driver.isNotEmpty
? ListView.builder(
itemCount: driverthebest.driver.length,
itemBuilder: (context, index) {
final driver = driverthebest.driver[index];
return ListTile(
leading: CircleAvatar(
child: Text(
(int.parse(driver['driver_count'] * 5) / 3600)
.toStringAsFixed(0),
),
),
title: Text((driver['name_arabic']) ??
'Unknown Name'),
subtitle: Text(
'Phone: ${(driver['phone']) ?? 'N/A'}'),
trailing: IconButton(
onPressed: () async {
Get.defaultDialog(
title:
'are you sure to pay to this driver gift'.tr,
middleText: '',
onConfirm: () async {},
onCancel: () => Get.back());
},
icon: const Icon(Icons.wallet_giftcard_rounded),
),
);
},
)
: const Center(
child: Text('No drivers available.'),
);
})
],
isleading: true,
);
}
}

View File

@@ -0,0 +1,111 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/controller/functions/encrypt_decrypt.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../views/widgets/elevated_btn.dart';
import '../../functions/crud.dart';
import 'alexandria_besr_driver.dart';
import 'giza_best_driver.dart';
class DriverTheBest extends StatelessWidget {
const DriverTheBest({super.key});
@override
Widget build(BuildContext context) {
Get.put(Driverthebest());
return MyScaffold(
title: 'Best Drivers'.tr,
body: [
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Giza',
onPressed: () {
Get.to(() => DriverTheBestGiza());
}),
MyElevatedButton(
title: 'Alexandria',
onPressed: () {
Get.to(() => DriverTheBestAlexandria());
}),
],
),
GetBuilder<Driverthebest>(builder: (driverthebest) {
return driverthebest.driver.isNotEmpty
? SizedBox(
height: Get.height * .7,
child: ListView.builder(
itemCount: driverthebest.driver.length,
itemBuilder: (context, index) {
final driver = driverthebest.driver[index];
return ListTile(
leading: CircleAvatar(
child: Text(
((int.parse(driver['driver_count']) * 5) / 3600)
.toStringAsFixed(0),
),
),
title: Text((driver['name_arabic']) ??
'Unknown Name'),
subtitle: Text(
'Phone: ${(driver['phone']) ?? 'N/A'}'),
trailing: IconButton(
onPressed: () async {
// Get.defaultDialog(
// title:
// 'are you sure to pay to this driver gift'.tr,
// middleText: '',
// onConfirm: () async {
// // final wallet = Get.put(WalletController());
// // await wallet.addPaymentToDriver('100',
// // driver['id'].toString(), driver['token']);
// // await wallet.addSeferWallet(
// // '100', driver['id'].toString());
// },
// onCancel: () => Get.back());
},
icon: const Icon(Icons.wallet_giftcard_rounded),
),
);
},
),
)
: const Center(
child: Text('No drivers available.'),
);
}),
],
)
],
isleading: true,
);
}
}
class Driverthebest extends GetxController {
bool isLoading = false;
List driver = [];
getBestDriver() async {
var res = await CRUD().get(link: AppLink.getBestDriver, payload: {});
if (res != 'failure') {
driver = res['message'];
update();
} else {
Get.snackbar('error', '', backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}

View File

@@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
class Complaint extends StatelessWidget {
const Complaint({super.key});
@override
Widget build(BuildContext context) {
return MyScaffold(title: "View complaint".tr, isleading: true, body: []);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../main_controller.dart';
class DriverPage extends StatelessWidget {
DriverPage({super.key});
final MainController mainController = Get.find<MainController>();
@override
Widget build(BuildContext context) {
return Scaffold(
body: GetBuilder<MainController>(builder: (mainController) {
Map data = mainController.driverData['message'][0];
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('${data['first_name']} ${data['last_name']}'),
),
child: SafeArea(
child: CupertinoScrollbar(
child: ListView(
children: [
_buildDriverInfoSection(data),
_buildCommunicationSection(data, context),
_buildStatisticsSection(data),
_buildCarInfoSection(data),
_buildLicenseInfoSection(data),
_buildBankInfoSection(data),
const SizedBox(height: 40),
],
),
),
),
);
}),
);
}
// ============================================================
// REUSABLE EDIT ROW
// ============================================================
Widget _buildEditableRow(String label, String key, Map data) {
return CupertinoListTile(
title: Text(label),
trailing: Text(
data[key].toString(),
style: const TextStyle(color: CupertinoColors.systemGrey),
),
onTap: () => _openEditSheet(label, key, data[key]),
);
}
void _openEditSheet(String label, String key, dynamic value) {
final TextEditingController controller =
TextEditingController(text: value.toString());
Get.bottomSheet(
CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Edit $label"),
trailing: GestureDetector(
child: const Text(
"Save",
style: TextStyle(color: CupertinoColors.activeBlue),
),
onTap: () {
mainController.updateDriverField(key, controller.text);
Get.back();
},
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: CupertinoTextField(
controller: controller,
autofocus: true,
),
),
),
),
);
}
// ============================================================
// SECTIONS
// ============================================================
Widget _buildDriverInfoSection(Map data) {
return CupertinoListSection.insetGrouped(
header: Text('Driver Information'.tr),
children: [
_buildEditableRow('Name Arabic'.tr, 'name_arabic', data),
// _buildEditableRow('Name English'.tr, 'name_english', data),
_buildEditableRow('Phone'.tr, 'phone', data),
_buildEditableRow('Email'.tr, 'email', data),
_buildEditableRow('Gender'.tr, 'gender', data),
_buildEditableRow('Birthdate'.tr, 'birthdate', data),
_buildEditableRow('National Number'.tr, 'national_number', data),
// _buildEditableRow('Religion'.tr, 'religion', data),
// _buildEditableRow('Occupation'.tr, 'occupation', data),
// _buildEditableRow('Education'.tr, 'education', data),
],
);
}
Widget _buildStatisticsSection(Map data) {
return CupertinoListSection.insetGrouped(
header: Text('Driver Statistics'.tr),
children: [
_buildInfoRow('Total Rides'.tr, data['countRide'].toString()),
_buildInfoRow('Average Rating'.tr, data['rating'].toString()),
_buildInfoRow('Total Payments'.tr, data['totalPayment'].toString()),
_buildInfoRow(
'Wallet Balance'.tr, data['totalDriverWallet'].toString()),
_buildInfoRow('Complaints'.tr, data['countComplaint'].toString()),
_buildInfoRow('Scam Reports'.tr, data['countScam'].toString()),
_buildInfoRow(
'Passengers Rated'.tr, data['DRatingPassengersCount'].toString()),
_buildInfoRow(
'Avg Passenger Rating'.tr, data['avgDRatingPassenger'].toString()),
],
);
}
// Read-only row widget
Widget _buildInfoRow(String label, String value) {
return CupertinoListTile(
title: Text(label),
trailing: Text(
value,
style: const TextStyle(color: CupertinoColors.systemGrey),
),
);
}
Widget _buildCarInfoSection(Map data) {
return CupertinoListSection.insetGrouped(
header: Text('Vehicle Information'.tr),
children: [
// _buildEditableRow('VIN'.tr, 'vin', data),
_buildEditableRow('Plate Number'.tr, 'car_plate', data),
_buildEditableRow('Make'.tr, 'make', data),
_buildEditableRow('Model'.tr, 'model', data),
_buildEditableRow('Year'.tr, 'year', data),
_buildEditableRow('Color'.tr, 'color', data),
_buildEditableRow('Fuel Type'.tr, 'fuel', data),
// _buildEditableRow('Displacement'.tr, 'displacement', data),
_buildEditableRow('Registration Date'.tr, 'registration_date', data),
_buildEditableRow('Expiration Date'.tr, 'expiration_date', data),
],
);
}
Widget _buildLicenseInfoSection(Map data) {
return CupertinoListSection.insetGrouped(
header: Text('License Information'.tr),
children: [
_buildEditableRow('License Type'.tr, 'license_type', data),
_buildEditableRow('Card ID'.tr, 'card_id', data),
_buildEditableRow('Issue Date'.tr, 'issue_date', data),
_buildEditableRow('Expiry Date'.tr, 'expiry_date', data),
_buildEditableRow('Categories'.tr, 'license_categories', data),
],
);
}
Widget _buildBankInfoSection(Map data) {
return CupertinoListSection.insetGrouped(
header: Text('Bank Information'.tr),
children: [
_buildEditableRow('Account Number'.tr, 'accountBank', data),
_buildEditableRow('Bank Code'.tr, 'bankCode', data),
],
);
}
Widget _buildCommunicationSection(Map data, BuildContext context) {
String phone = data['phone'] ?? '';
String name = data['first_name'] ?? '';
return CupertinoListSection.insetGrouped(
header: Text('Quick Communication'.tr),
children: [
CupertinoListTile(
title: Text('Call Driver'.tr),
leading: const Icon(CupertinoIcons.phone_fill, color: Colors.green),
onTap: () => mainController.makePhoneCall(phone),
),
CupertinoListTile(
title: Text('WhatsApp: Activation'.tr),
leading: const Icon(Icons.send, color: Colors.green),
onTap: () => mainController.launchCommunication(
'whatsapp',
phone,
'أهلاً بك يا كابتن $name في انطلق! تم تفعيل حسابك بنجاح وأصبحت مستعداً لاستقبال الرحلات.',
),
),
CupertinoListTile(
title: Text('WhatsApp: Missing Docs'.tr),
leading: const Icon(Icons.send, color: Colors.orange),
onTap: () => mainController.launchCommunication(
'whatsapp',
phone,
'مرحباً كابتن $name، يرجى تزويدنا بالأوراق الناقصة أو غير الواضحة عبر الواتساب لإكمال تفعيل حسابك.',
),
),
CupertinoListTile(
title: Text('WhatsApp: Support'.tr),
leading: const Icon(Icons.send, color: Colors.blue),
onTap: () => mainController.launchCommunication(
'whatsapp',
phone,
'مرحباً كابتن $name، معك الدعم الفني من شركة انطلق. كيف يمكنني مساعدتك اليوم؟',
),
),
],
);
}
}

View File

@@ -0,0 +1,223 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/colors.dart';
import 'package:siro_service/controller/mainController/main_controller.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import 'package:siro_service/main.dart';
import 'package:siro_service/constant/box_name.dart';
import 'registration_captain_page.dart';
class DriversCantRegister extends StatelessWidget {
DriversCantRegister({super.key});
//
@override
Widget build(BuildContext context) {
Get.put(MainController());
// Unified action button (WhatsApp - Edit - etc)
Widget buildActionButton({
required IconData icon,
required Color color,
required VoidCallback onPressed,
}) {
return CupertinoButton(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
onPressed: onPressed,
child: Icon(icon, color: color, size: 28),
);
}
return MyScaffold(
title: 'Drivers Want Register'.tr,
isleading: true,
body: [
GetBuilder<MainController>(builder: (mainController) {
return Column(
children: [
// Search
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: CupertinoSearchTextField(
keyboardType: TextInputType.phone,
onChanged: (value) => mainController.searchDrivers(value),
placeholder: 'Search by phone number'.tr,
),
),
// List
Expanded(
child: mainController.filteredDrivers.isEmpty
? Center(
child: Text(
'No drivers found'.tr,
style:
TextStyle(color: Colors.grey[600], fontSize: 16),
),
)
: ListView.builder(
itemCount: mainController.filteredDrivers.length,
itemBuilder: (context, index) {
final driver = mainController.filteredDrivers[index];
final notesController =
TextEditingController(text: driver['note'] ?? '');
return Card(
margin: const EdgeInsets.symmetric(
horizontal: 12.0, vertical: 8.0),
elevation: 3,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: driver['note'] != null
? AppColor.secondaryColor
: Colors.transparent,
width: 2.5,
),
),
child: Padding(
padding:
const EdgeInsets.fromLTRB(16, 12, 16, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Text(
'Driver Phone: ${driver['first_name'] ?? ''} ${driver['last_name'] ?? ''}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.black87,
),
),
const Divider(height: 20),
// Phone Row
Row(
children: [
const Icon(
CupertinoIcons.phone_fill,
color: AppColor.primaryColor,
size: 22,
),
const SizedBox(width: 12),
Expanded(
child: InkWell(
onTap: () {
mainController.makePhoneCall(
driver['phone_number']);
},
child: Text(
driver['phone_number'] ?? 'N/A',
style: const TextStyle(
fontSize: 17,
letterSpacing: 1.2,
),
),
),
),
// WhatsApp button
buildActionButton(
icon: Icons.send,
color: const Color(0xFF25D366),
onPressed: () {
String message = "مرحباً،\n\n"
"يظهر لدينا في نظام تطبيق *انطلق* أنك لم تكمل عملية التسجيل بعد.\n"
"ندعوك لإكمال التسجيل للاستفادة من مزايا التطبيق والبدء بالعمل معنا.\n\n"
"إذا احتجت لأي مساعدة، تواصل معنا على خدمة العملاء:\n"
"+963 952 475 742\n\n"
"+963 952 475 740\n\n"
"فريق انطلق يتمنى لك يوماً سعيداً.";
mainController.launchCommunication(
'whatsapp',
driver['phone_number'],
message,
);
},
),
// Edit button → go to registration form
buildActionButton(
icon: CupertinoIcons
.pencil_ellipsis_rectangle,
color: AppColor.gold,
onPressed: () {
Get.to(
() => RegisterCaptain(),
arguments: {
"phone": driver['phone_number'],
"driverId": driver['driverId'],
},
);
},
),
buildActionButton(
icon: CupertinoIcons
.pencil_ellipsis_rectangle,
color: AppColor.redColor,
onPressed: () {
mainController
.deleteDriverNotCompleteRegistration(
driver['phone_number']);
},
),
],
),
const SizedBox(height: 16),
// Notes box
CupertinoTextField(
controller: notesController,
placeholder: "Additional comments".tr,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
maxLines: 2,
),
const SizedBox(height: 12),
// Save button
Align(
alignment: AlignmentDirectional.centerEnd,
child: CupertinoButton(
padding: const EdgeInsets.symmetric(
horizontal: 20),
color: AppColor.secondaryColor,
onPressed: () {
mainController
.saveNoteForDriverNotCompleteRegistration(
driver['phone_number'],
box.read(BoxName.employeename) ??
'none',
notesController.text,
);
},
child: Text('Save Notes'.tr),
),
),
],
),
),
);
},
),
),
],
);
}),
],
);
}
}

View File

@@ -0,0 +1,334 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:siro_service/controller/mainController/main_controller.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
import '../../../constant/links.dart';
import '../../../views/widgets/my_textField.dart';
import '../../functions/image.dart';
import '../../functions/launch.dart';
class EditCar extends StatelessWidget {
final Map carData;
const EditCar({super.key, required this.carData});
@override
Widget build(BuildContext context) {
Get.put(MainController());
return GetBuilder<MainController>(builder: (mainController) {
return MyScaffold(
title: 'Edit',
isleading: true,
action: Row(
children: [
IconButton(
onPressed: () {
makePhoneCall(carData['phone']);
},
icon: const Icon(Icons.phone),
),
IconButton(
onPressed: () {
launchCommunication('whatsapp', carData['phone'], '');
},
icon: const Icon(
Icons.message,
color: AppColor.greenColor,
),
),
],
),
body: [
ListView(
children: [
Column(
children: [
GestureDetector(
onLongPress: () async {
await ImageController().choosImage(AppLink.uploadEgypt,
carData['driverID'], 'car_front');
},
child: Image.network(
'https://sefer.click/sefer/card_image/car_front-${carData['driverID']}.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
// If the image fails to load, use the _copy version
return Image.network(
'https://sefer.click/sefer/card_image/car_front-${carData['driverID']}_copy.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
);
},
),
),
GestureDetector(
onLongPress: () async {
await ImageController().choosImage(
AppLink.uploadEgypt, carData['id'], 'car_back');
},
child: Image.network(
'https://sefer.click/sefer/card_image/car_back-${carData['driverID']}.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
// If the image fails to load, use the _copy version
return Image.network(
'https://sefer.click/sefer/card_image/car_back-${carData['driverID']}_copy.jpg',
height: 200,
width: double.maxFinite,
fit: BoxFit.fill,
);
},
),
),
],
),
const SizedBox(height: 9),
Form(
key: mainController.formKey,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: Get.width * .6,
child: MyTextForm(
controller: mainController.carplateController,
label: 'car plate'.tr,
hint: 'car plate'.tr,
type: TextInputType.name,
),
),
IconButton(
onPressed: () async {
if (mainController.formKey.currentState!
.validate()) {
await mainController.editCarPlateNotEdit(
carData['driverID'].toString(),
mainController.carplateController.text,
mainController.colorController.text,
mainController.colorHex.value.toString(),
mainController.yearController.text,
mainController.makeController.text,
mainController.modelController.text,
mainController.expirationDateController.text,
mainController.ownerController.text,
);
}
},
icon: const Icon(
Icons.upload_outlined,
color: AppColor.blueColor,
),
),
],
),
// Other fields
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.yearController,
label: 'Year'.tr,
hint: 'Year'.tr,
type: TextInputType.number,
),
),
SizedBox(
width: Get.width * .4,
child: DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Color'.tr, // Localized label
),
value: mainController.colorHex.value.isEmpty
? null
: mainController.colorHex
.value, // Use the hex value as the current value
items: [
{'red'.tr: '#FF0000'},
{'green'.tr: '#008000'},
{'blue'.tr: '#0000FF'},
{'black'.tr: '#000000'},
{'white'.tr: '#FFFFFF'},
{'yellow'.tr: '#FFFF00'},
{'purple'.tr: '#800080'},
{'orange'.tr: '#FFA500'},
{'pink'.tr: '#FFC0CB'},
{'brown'.tr: '#A52A2A'},
{'gray'.tr: '#808080'},
{'cyan'.tr: '#00FFFF'},
{'magenta'.tr: '#FF00FF'},
{'lime'.tr: '#00FF00'},
{'indigo'.tr: '#4B0082'},
{'violet'.tr: '#EE82EE'},
{'gold'.tr: '#FFD700'},
{'silver'.tr: '#C0C0C0'},
{'teal'.tr: '#008080'},
{'navy'.tr: '#000080'},
].map((colorMap) {
String colorName = colorMap.keys.first;
String colorValue = colorMap.values.first;
return DropdownMenuItem<String>(
value: colorValue,
child: Text(colorName),
);
}).toList(),
onChanged: (value) {
if (value != null) {
// Find the selected color name based on the hex value
String selectedColorName = '';
for (var colorMap in [
{'red'.tr: '#FF0000'},
{'green'.tr: '#008000'},
{'blue'.tr: '#0000FF'},
{'black'.tr: '#000000'},
{'white'.tr: '#FFFFFF'},
{'yellow'.tr: '#FFFF00'},
{'purple'.tr: '#800080'},
{'orange'.tr: '#FFA500'},
{'pink'.tr: '#FFC0CB'},
{'brown'.tr: '#A52A2A'},
{'gray'.tr: '#808080'},
{'cyan'.tr: '#00FFFF'},
{'magenta'.tr: '#FF00FF'},
{'lime'.tr: '#00FF00'},
{'indigo'.tr: '#4B0082'},
{'violet'.tr: '#EE82EE'},
{'gold'.tr: '#FFD700'},
{'silver'.tr: '#C0C0C0'},
{'teal'.tr: '#008080'},
{'navy'.tr: '#000080'},
]) {
if (colorMap.values.first == value) {
selectedColorName = colorMap.keys.first;
break;
}
}
mainController.colorController.text =
selectedColorName;
mainController.colorHex.value = value;
}
},
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.makeController,
label: 'Make'.tr,
hint: 'Make'.tr,
type: TextInputType.name,
),
),
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.modelController,
label: 'Model'.tr,
hint: 'Model'.tr,
type: TextInputType.name,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * .4,
child: TextField(
controller:
mainController.expirationDateController,
decoration: InputDecoration(
labelText: 'Expiration Date'.tr,
hintText: 'Expiration Date'.tr,
),
readOnly:
true, // Make the field read-only to prevent manual input
onTap: () async {
DateTime pickedDate =
DateTime.now(); // Declare the variable here
await showCupertinoModalPopup<void>(
context: context,
builder: (context) => Container(
height: 250,
color: Colors.white,
child: Column(
children: [
SizedBox(
height: 150,
child: CupertinoDatePicker(
initialDateTime: pickedDate,
minimumDate: DateTime(
1955), // Set the starting date
maximumDate: DateTime(
2034), // Set the ending date
mode: CupertinoDatePickerMode.date,
onDateTimeChanged:
(DateTime dateTime) {
pickedDate = dateTime;
},
),
),
CupertinoButton(
child: Text('Done'.tr),
onPressed: () {
String formattedDate =
DateFormat('yyyy-MM-dd')
.format(pickedDate);
mainController
.expirationDateController
.text =
formattedDate.toString();
Navigator.of(context).pop();
},
),
],
),
),
);
},
),
),
SizedBox(
width: Get.width * .4,
child: MyTextForm(
controller: mainController.ownerController,
label: 'Owner'.tr,
hint: 'Owner'.tr,
type: TextInputType.name,
),
),
],
),
],
),
)
],
)
]);
});
}
}

View File

@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/style.dart';
import 'package:siro_service/controller/mainController/pages/edit_car.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../../functions/encrypt_decrypt.dart';
import '../main_controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class EditCarPlate extends StatelessWidget {
const EditCarPlate({super.key});
@override
Widget build(BuildContext context) {
Get.put(MainController());
return GetBuilder<MainController>(builder: (mainController) {
return MyScaffold(
title: 'Edit car details'.tr,
isleading: true,
body: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: mainController
.carPlateNotEdit.length, // 10 fields + 1 save button
itemBuilder: (context, index) {
var carData = mainController.carPlateNotEdit[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
Get.to(EditCar(carData: carData));
},
child: Container(
decoration: AppStyle.boxDecoration1,
child: Text((carData['owner']))),
),
);
}),
),
],
);
});
}
}

View File

@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../../best_driver_controllers.dart';
import '../../functions/encrypt_decrypt.dart';
class DriverTheBestGiza extends StatelessWidget {
const DriverTheBestGiza({super.key});
@override
Widget build(BuildContext context) {
Get.put(DriverTheBestGizaController(), permanent: true);
return MyScaffold(
title: 'Giza'.tr,
body: [
GetBuilder<DriverTheBestGizaController>(builder: (driverthebest) {
return driverthebest.driver.isNotEmpty
? ListView.builder(
itemCount: driverthebest.driver.length,
itemBuilder: (context, index) {
final driver = driverthebest.driver[index];
return ListTile(
leading: CircleAvatar(
child: Text(
(int.parse(driver['driver_count'] * 5) / 3600)
.toStringAsFixed(0),
),
),
title: Text((driver['name_arabic']) ??
'Unknown Name'),
subtitle: Text(
'Phone: ${(driver['phone']) ?? 'N/A'}'),
trailing: IconButton(
onPressed: () async {
Get.defaultDialog(
title:
'are you sure to pay to this driver gift'.tr,
middleText: '',
onConfirm: () async {},
onCancel: () => Get.back());
},
icon: const Icon(Icons.wallet_giftcard_rounded),
),
);
},
)
: const Center(
child: Text('No drivers available.'),
);
})
],
isleading: true,
);
}
}

View File

@@ -0,0 +1,485 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/colors.dart';
// Importa tu nuevo controlador de servicio
import 'package:siro_service/controller/mainController/ragister_service_controller.dart';
import 'package:siro_service/views/widgets/elevated_btn.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
// El import del antiguo controlador ya no es necesario
// import '../registration_captain_controller.dart';
class RegisterCaptain extends StatelessWidget {
const RegisterCaptain({super.key});
@override
Widget build(BuildContext context) {
// Instancia el NUEVO controlador
final controller = Get.put(RegisterCaptainServiceController());
return MyScaffold(
title: 'Syrian Documents Check'.tr,
isleading: true,
body: [
Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: StepIndicator(
currentStep: controller.currentPageIndex.value,
totalSteps: 4,
),
),
Expanded(
child: PageView(
controller: controller.pageController,
onPageChanged: (index) {
controller.currentPageIndex.value = index;
},
children: [
// <-- PÁGINAS REACTIVADAS CON EL NUEVO CONTROLADOR -->
_buildSyrianDriverLicenseFront(context, controller),
_buildSyrianDriverLicenseBack(context, controller),
_buildSyrianCarLicenseFront(context, controller),
_buildSyrianCarLicenseBack(controller),
],
),
),
_buildNavigationControls(controller),
],
);
}),
],
);
}
// Este método ya estaba usando el controlador correcto
Widget _buildNavigationControls(RegisterCaptainServiceController controller) {
bool isLastPage = controller.currentPageIndex.value == 3;
bool isFirstPage = controller.currentPageIndex.value == 0;
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!isFirstPage)
MyElevatedButton(
title: 'Back'.tr,
onPressed: controller.previousPage,
kolor: Colors.grey,
),
if (isFirstPage) const Spacer(),
MyElevatedButton(
title: isLastPage ? 'Save and Activate'.tr : 'Next'.tr,
onPressed: isLastPage
? controller.updateAndActivateSyrianDriver
: controller.nextPage,
kolor: isLastPage ? AppColor.greenColor : AppColor.primaryColor,
),
],
),
);
}
Widget _buildTextField({
required String label,
required TextEditingController controller,
IconData? icon,
TextInputType keyboardType = TextInputType.text,
VoidCallback? onTap,
String? hintText,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
readOnly: onTap != null,
onTap: onTap,
decoration: InputDecoration(
labelText: label.tr,
hintText: hintText,
prefixIcon:
icon != null ? Icon(icon, color: AppColor.secondaryColor) : null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
const BorderSide(color: AppColor.secondaryColor, width: 2),
),
),
),
);
}
Widget _buildPageContent({
required RxString imageUrl,
required List<Widget> formFields,
}) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ClipRRect(
// borderRadius: BorderRadius.circular(12),
// child: Image.network(
// imageUrl.value,
// fit: BoxFit.fitWidth,
// errorBuilder: (context, error, stackTrace) {
// return Container(
// height: 200,
// color: Colors.grey[200],
// child: Center(
// child: Text(
// 'Image not available'.tr,
// style: const TextStyle(color: Colors.grey),
// ),
// ),
// );
// },
// ),
// ),
// const SizedBox(height: 20),
const Divider(),
...formFields,
],
),
);
}
// <-- MODIFICADO: Usa RegisterCaptainServiceController -->
Widget _buildColorDropdown(RegisterCaptainServiceController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Obx(
() => DropdownButtonFormField<String>(
value: controller.colorHex.value.isEmpty
? null
: controller.colorHex.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Car Color'.tr,
prefixIcon: Icon(Icons.color_lens, color: AppColor.secondaryColor),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
// <-- MODIFICADO: Usa la referencia estática del nuevo controlador -->
items: RegisterCaptainServiceController.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: controller.hexToColor(hex),
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
),
const SizedBox(width: 12),
Expanded(child: Text(key.tr)),
],
),
);
}).toList(),
onChanged: (hex) {
if (hex != null) {
controller.updateColorSelection(hex);
}
},
),
),
);
}
// <-- MODIFICADO: Usa RegisterCaptainServiceController -->
Widget _buildGenderDropdown(RegisterCaptainServiceController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Obx(
() => DropdownButtonFormField<String>(
value: controller.selectedGender.value.isEmpty
? null
: controller.selectedGender.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Gender'.tr,
prefixIcon: Icon(Icons.wc, color: AppColor.secondaryColor),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
items: ['Male', 'Female'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value.tr),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
controller.selectedGender.value = newValue;
}
},
),
),
);
}
// <-- MODIFICADO: Usa RegisterCaptainServiceController -->
Widget _buildFuelDropdown(RegisterCaptainServiceController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Obx(
() => DropdownButtonFormField<String>(
// <-- MODIFICADO: Usa la referencia estática del nuevo controlador -->
value: RegisterCaptainServiceController.kFuelOptions
.contains(controller.selectedFuel.value)
? controller.selectedFuel.value
: null,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Fuel Type'.tr,
prefixIcon:
Icon(Icons.local_gas_station, color: AppColor.secondaryColor),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
// <-- MODIFICADO: Usa la referencia estática del nuevo controlador -->
items:
RegisterCaptainServiceController.kFuelOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value.tr),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
controller.selectedFuel.value = newValue;
}
},
),
),
);
}
// --- PAGE 1: Driver License Front ---
// <-- MODIFICADO: Usa RegisterCaptainServiceController -->
Widget _buildSyrianDriverLicenseFront(
BuildContext context, RegisterCaptainServiceController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['driver_license_front']!,
formFields: [
Row(
children: [
Expanded(
child: _buildTextField(
label: 'First Name',
controller: controller.firstNameController,
keyboardType: TextInputType.name),
),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Last Name',
controller: controller.lastNameController,
keyboardType: TextInputType.name),
),
],
),
_buildTextField(
label: 'Phone Number',
controller: controller.phoneController,
hintText: 'e.g., 963992952235',
icon: Icons.phone,
keyboardType: TextInputType.phone,
),
_buildTextField(
label: 'Place of Registration',
controller: controller.siteController,
icon: Icons.location_city),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'National Number',
controller: controller.nationalNumberController,
keyboardType: TextInputType.number,
icon: Icons.fingerprint),
),
const SizedBox(width: 8),
Expanded(child: _buildGenderDropdown(controller)),
],
),
_buildTextField(
label: 'Birthdate',
controller: controller.birthdateController,
icon: Icons.cake,
onTap: () =>
controller.selectDate(context, controller.birthdateController)),
],
);
}
// --- PAGE 2: Driver License Back ---
// <-- MODIFICADO: Usa RegisterCaptainServiceController -->
Widget _buildSyrianDriverLicenseBack(
BuildContext context, RegisterCaptainServiceController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['driver_license_back']!,
formFields: [
_buildTextField(
label: 'License Category',
controller: controller.licenseCategoriesController),
_buildTextField(
label: 'Expiry Date',
controller: controller.expiryDateController,
icon: Icons.event_busy,
onTap: () => controller.selectDate(
context, controller.expiryDateController)),
],
);
}
// --- PAGE 3: Car License Front ---
// <-- MODIFICADO: Usa RegisterCaptainServiceController -->
Widget _buildSyrianCarLicenseFront(
BuildContext context, RegisterCaptainServiceController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['car_license_front']!,
formFields: [
_buildTextField(
label: 'Owner Name',
controller: controller.ownerController,
keyboardType: TextInputType.name,
icon: Icons.person_search),
Row(
children: [
Expanded(child: _buildColorDropdown(controller)),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Car Plate',
controller: controller.carPlateController,
),
),
],
),
// _buildTextField(
// label: 'VIN',
// controller: controller.vinController,
// icon: Icons.confirmation_number),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'License Issue Date',
controller: controller.licenseIssueDateController,
onTap: () => controller.selectDate(
context, controller.licenseIssueDateController))),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'License Expiry Date',
controller: controller.carLicenseExpiryDateController,
onTap: () => controller.selectDate(
context, controller.carLicenseExpiryDateController))),
],
),
],
);
}
// --- PAGE 4: Car License Back ---
// <-- MODIFICADO: Usa RegisterCaptainServiceController -->
Widget _buildSyrianCarLicenseBack(
RegisterCaptainServiceController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['car_license_back']!,
formFields: [
Row(
children: [
Expanded(
child: _buildTextField(
label: 'Make',
controller: controller.makeController,
)),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Model',
controller: controller.modelController,
)),
],
),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'Year',
controller: controller.yearController,
keyboardType: TextInputType.number)),
const SizedBox(width: 8),
Expanded(child: _buildFuelDropdown(controller)),
],
),
],
);
}
}
class StepIndicator extends StatelessWidget {
final int currentStep;
final int totalSteps;
const StepIndicator({
super.key,
required this.currentStep,
required this.totalSteps,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'${'Step'.tr} ${currentStep + 1} ${'of'.tr} $totalSteps',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black54),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(totalSteps, (index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
width: 30,
height: 8,
decoration: BoxDecoration(
color: index <= currentStep
? AppColor.primaryColor
: Colors.grey.shade300,
borderRadius: BorderRadius.circular(4),
),
);
}),
),
],
);
}
}

View File

@@ -0,0 +1,79 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/controller/mainController/main_controller.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../../functions/encrypt_decrypt.dart';
class PassengersCantRegister extends StatelessWidget {
PassengersCantRegister({super.key});
@override
Widget build(BuildContext context) {
Get.put(MainController());
return MyScaffold(
title: 'Passengers Cant Register'.tr,
isleading: true,
body: [
GetBuilder<MainController>(builder: (mainController) {
return ListView.builder(
itemCount: mainController.passengerNotCompleteRegistration.length,
itemBuilder: (context, index) {
final passenger =
mainController.passengerNotCompleteRegistration[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: CupertinoFormSection(
header: Text('Passenger ID: ${passenger['id']}'),
children: [
InkWell(
onTap: () =>
mainController.makePhoneCall(passenger['phone']),
child: CupertinoFormRow(
prefix: Text(' phone'.tr),
child: CupertinoTextFormFieldRow(
initialValue: ((passenger['phone'])),
readOnly: true,
placeholder: 'Phone Number'.tr,
),
),
),
CupertinoFormRow(
prefix: Text('Created At'.tr),
child: CupertinoTextFormFieldRow(
initialValue: passenger['created_at'],
readOnly: true,
placeholder: 'Created At',
),
),
CupertinoFormRow(
prefix: Text('Notes'.tr),
child: CupertinoTextFormFieldRow(
controller: mainController.notesController,
placeholder:
passenger['note'] ?? 'Enter notes after call'.tr,
),
),
CupertinoButton(
child: Text('Save Notes'.tr),
onPressed: () {
// Save the notes for the Passenger
String notes = mainController.notesController.text;
mainController
.saveNoteForPassengerNotCompleteRegistration(
passenger['phone_number'], 'girls name', notes);
print('Notes for Passenger ${passenger['id']}: $notes');
},
),
],
),
);
},
);
}),
],
);
}
}

View File

@@ -0,0 +1,146 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/controller/mainController/main_controller.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
class PassengersPage extends StatelessWidget {
PassengersPage({super.key});
final MainController mainController = MainController();
@override
Widget build(BuildContext context) {
return GetBuilder<MainController>(
builder: (mainController) {
final data = _extractPassengerData(mainController);
return MyScaffold(
title: data != null
? '${data['first_name'] ?? ''} ${data['last_name'] ?? ''}'
: 'Passenger Not Found'.tr,
isleading: true,
body: [
if (data != null)
ListView(
children: [
_buildPersonalInfoCard(data),
_buildLatestRideCard(data),
_buildWalletInfoCard(data),
],
)
else
const Center(
child: Padding(
padding: EdgeInsets.all(24.0),
child: Text(
'No passenger data available.',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
),
),
),
],
);
},
);
}
/// Helper method to extract the passenger data safely.
Map? _extractPassengerData(MainController controller) {
final passengerData = controller.passengerData;
if (passengerData == null ||
passengerData['message'] == null ||
passengerData['message'].isEmpty) {
return null;
}
return passengerData['message'][0];
}
Widget _buildPersonalInfoCard(Map data) {
return _buildCard(
title: 'Personal Information'.tr,
children: [
_buildInfoRow('Phone'.tr, data['phone']),
_buildInfoRow('Email'.tr, data['email']),
_buildInfoRow('Gender'.tr, data['gender']),
_buildInfoRow('Birthdate'.tr, data['birthdate']),
// _buildInfoRow('Education'.tr, data['education']),
_buildInfoRow('Employment'.tr, data['employmentType']),
_buildInfoRow('Marital Status'.tr, data['maritalStatus']),
],
);
}
Widget _buildLatestRideCard(Map data) {
return _buildCard(
title: 'Latest Ride'.tr,
children: [
_buildInfoRow('Ride ID'.tr, data['ride_id']),
_buildInfoRow('Date'.tr, data['ride_date']),
_buildInfoRow('Start Time'.tr, data['ride_time']),
_buildInfoRow('End Time'.tr, data['ride_endtime']),
_buildInfoRow(
'Price'.tr, data['price'] != null ? '\$${data['price']}' : null),
_buildInfoRow('Status'.tr, data['ride_status']),
_buildInfoRow('Payment Method'.tr, data['ride_payment_method']),
_buildInfoRow('Car Type'.tr, data['car_type']),
_buildInfoRow(
'Distance'.tr,
data['distance'] != null
? '${(data['distance'] as num).toStringAsFixed(2)} km'
: null),
],
);
}
Widget _buildWalletInfoCard(Map data) {
return _buildCard(
title: 'Wallet Information'.tr,
children: [
_buildInfoRow(
'Wallet Balance'.tr,
data['passenger_wallet_balance'] != null
? '\$${data['passenger_wallet_balance']}'
: null),
_buildInfoRow(
'Last Payment Amount'.tr,
data['passenger_payment_amount'] != null
? '\$${data['passenger_payment_amount']}'
: null),
_buildInfoRow(
'Last Payment Method'.tr, data['passenger_payment_method']),
],
);
}
Widget _buildCard({required String title, required List<Widget> children}) {
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
...children,
],
),
),
);
}
Widget _buildInfoRow(String label, dynamic value) {
final displayValue = value?.toString() ?? 'N/A';
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
Flexible(child: Text(displayValue, overflow: TextOverflow.ellipsis)),
],
),
);
}
}

View File

@@ -0,0 +1,458 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/colors.dart';
import 'package:siro_service/views/widgets/elevated_btn.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../registration_captain_controller.dart';
class RegisterCaptain extends StatelessWidget {
const RegisterCaptain({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(RegisterCaptainController());
return MyScaffold(
title: 'Syrian Documents Check'.tr,
isleading: true,
body: [
Obx(() {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: StepIndicator(
currentStep: controller.currentPageIndex.value,
totalSteps: 4,
),
),
Expanded(
child: PageView(
controller: controller.pageController,
onPageChanged: (index) {
controller.currentPageIndex.value = index;
},
children: [
_buildSyrianDriverLicenseFront(context, controller),
_buildSyrianDriverLicenseBack(context, controller),
_buildSyrianCarLicenseFront(context, controller),
_buildSyrianCarLicenseBack(controller),
],
),
),
_buildNavigationControls(controller),
],
);
}),
],
);
}
Widget _buildNavigationControls(RegisterCaptainController controller) {
bool isLastPage = controller.currentPageIndex.value == 3;
bool isFirstPage = controller.currentPageIndex.value == 0;
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!isFirstPage)
MyElevatedButton(
title: 'Back'.tr,
onPressed: controller.previousPage,
kolor: Colors.grey,
),
if (isFirstPage) const Spacer(),
MyElevatedButton(
title: isLastPage ? 'Save and Activate'.tr : 'Next'.tr,
onPressed: isLastPage
? controller.updateAndActivateSyrianDriver
: controller.nextPage,
kolor: isLastPage ? AppColor.greenColor : AppColor.primaryColor,
),
],
),
);
}
Widget _buildTextField({
required String label,
required TextEditingController controller,
IconData? icon,
TextInputType keyboardType = TextInputType.text,
VoidCallback? onTap,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
readOnly: onTap != null,
onTap: onTap,
decoration: InputDecoration(
labelText: label.tr,
prefixIcon:
icon != null ? Icon(icon, color: AppColor.secondaryColor) : null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide:
const BorderSide(color: AppColor.secondaryColor, width: 2),
),
),
),
);
}
Widget _buildPageContent({
required RxString imageUrl,
required List<Widget> formFields,
}) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
imageUrl.value,
fit: BoxFit.fitWidth,
errorBuilder: (context, error, stackTrace) {
return Container(
height: 200,
color: Colors.grey[200],
child: Center(
child: Text(
'Image not available'.tr,
style: const TextStyle(color: Colors.grey),
),
),
);
},
),
),
const SizedBox(height: 20),
const Divider(),
...formFields,
],
),
);
}
Widget _buildColorDropdown(RegisterCaptainController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Obx(
() => DropdownButtonFormField<String>(
value: controller.colorHex.value.isEmpty
? null
: controller.colorHex.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Car Color'.tr,
prefixIcon: Icon(Icons.color_lens, color: AppColor.secondaryColor),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
items: RegisterCaptainController.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: controller.hexToColor(hex),
shape: BoxShape.circle,
border: Border.all(color: Colors.black12),
),
),
const SizedBox(width: 12),
Expanded(child: Text(key.tr)),
],
),
);
}).toList(),
onChanged: (hex) {
if (hex != null) {
controller.updateColorSelection(hex);
}
},
),
),
);
}
Widget _buildGenderDropdown(RegisterCaptainController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Obx(
() => DropdownButtonFormField<String>(
value: controller.selectedGender.value.isEmpty
? null
: controller.selectedGender.value,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Gender'.tr,
prefixIcon: Icon(Icons.wc, color: AppColor.secondaryColor),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
items: ['Male', 'Female'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value.tr),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
controller.selectedGender.value = newValue;
}
},
),
),
);
}
Widget _buildFuelDropdown(RegisterCaptainController controller) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Obx(
() => DropdownButtonFormField<String>(
value: RegisterCaptainController.kFuelOptions
.contains(controller.selectedFuel.value)
? controller.selectedFuel.value
: null,
isExpanded: true,
decoration: InputDecoration(
labelText: 'Fuel Type'.tr,
prefixIcon:
Icon(Icons.local_gas_station, color: AppColor.secondaryColor),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
items: RegisterCaptainController.kFuelOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value.tr),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
controller.selectedFuel.value = newValue;
}
},
),
),
);
}
// --- PAGE 1: Driver License Front ---
Widget _buildSyrianDriverLicenseFront(
BuildContext context, RegisterCaptainController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['driver_license_front']!,
formFields: [
Row(
children: [
Expanded(
child: _buildTextField(
label: 'First Name',
controller: controller.firstNameController,
keyboardType: TextInputType.name),
),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Last Name',
controller: controller.lastNameController,
keyboardType: TextInputType.name),
),
],
),
_buildTextField(
label: 'Place of Registration',
controller: controller.siteController,
icon: Icons.location_city),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'National Number',
controller: controller.nationalNumberController,
keyboardType: TextInputType.number,
icon: Icons.fingerprint),
),
const SizedBox(width: 8),
Expanded(child: _buildGenderDropdown(controller)),
],
),
// _buildTextField(
// label: 'Birthdate',
// controller: controller.birthdateController,
// icon: Icons.cake,
// onTap: () =>
// controller.selectDate(context, controller.birthdateController)),
],
);
}
// --- PAGE 2: Driver License Back ---
Widget _buildSyrianDriverLicenseBack(
BuildContext context, RegisterCaptainController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['driver_license_back']!,
formFields: [
_buildTextField(
label: 'License Category',
controller: controller.licenseCategoriesController),
_buildTextField(
label: 'Expiry Date',
controller: controller.expiryDateController,
icon: Icons.event_busy,
onTap: () => controller.selectDate(
context, controller.expiryDateController)),
],
);
}
// --- PAGE 3: Car License Front ---
Widget _buildSyrianCarLicenseFront(
BuildContext context, RegisterCaptainController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['car_license_front']!,
formFields: [
_buildTextField(
label: 'Owner Name',
controller: controller.ownerController,
keyboardType: TextInputType.name,
icon: Icons.person_search),
Row(
children: [
Expanded(child: _buildColorDropdown(controller)),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Car Plate',
controller: controller.carPlateController,
),
),
],
),
// _buildTextField(
// label: 'VIN',
// controller: controller.vinController,
// icon: Icons.confirmation_number),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'License Issue Date',
controller: controller.licenseIssueDateController,
onTap: () => controller.selectDate(
context, controller.licenseIssueDateController))),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'License Expiry Date',
controller: controller.carLicenseExpiryDateController,
onTap: () => controller.selectDate(
context, controller.carLicenseExpiryDateController))),
],
),
],
);
}
// --- PAGE 4: Car License Back ---
Widget _buildSyrianCarLicenseBack(RegisterCaptainController controller) {
return _buildPageContent(
imageUrl: controller.docUrls['car_license_back']!,
formFields: [
Row(
children: [
Expanded(
child: _buildTextField(
label: 'Make',
controller: controller.makeController,
)),
const SizedBox(width: 8),
Expanded(
child: _buildTextField(
label: 'Model',
controller: controller.modelController,
)),
],
),
Row(
children: [
Expanded(
child: _buildTextField(
label: 'Year',
controller: controller.yearController,
keyboardType: TextInputType.number)),
const SizedBox(width: 8),
Expanded(child: _buildFuelDropdown(controller)),
],
),
],
);
}
}
class StepIndicator extends StatelessWidget {
final int currentStep;
final int totalSteps;
const StepIndicator({
super.key,
required this.currentStep,
required this.totalSteps,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'${'Step'.tr} ${currentStep + 1} ${'of'.tr} $totalSteps',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black54),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(totalSteps, (index) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
width: 30,
height: 8,
decoration: BoxDecoration(
color: index <= currentStep
? AppColor.primaryColor
: Colors.grey.shade300,
borderRadius: BorderRadius.circular(4),
),
);
}),
),
],
);
}
}

View File

@@ -0,0 +1,223 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_service/constant/colors.dart';
import 'package:siro_service/constant/style.dart';
import 'package:siro_service/views/widgets/elevated_btn.dart';
import 'package:siro_service/views/widgets/my_scafold.dart';
import '../main_controller.dart';
class WelcomeCall extends StatelessWidget {
const WelcomeCall({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(MainController());
return MyScaffold(
title: 'Welcome Drivers'.tr,
isleading: true,
body: [
GetBuilder<MainController>(builder: (mainController) {
final drivers = mainController.newDriverRegister;
if (drivers.isEmpty) {
return const Padding(
padding: EdgeInsets.all(32.0),
child: Center(
child: Text(
'No new drivers found.',
style: TextStyle(fontSize: 18),
),
),
);
}
return CupertinoScrollbar(
child: ListView.builder(
padding: const EdgeInsets.only(bottom: 20),
itemCount: drivers.length,
itemBuilder: (context, index) {
final driver = drivers[index];
return DriverCard(driver: driver);
},
),
);
}),
],
);
}
}
class DriverCard extends StatelessWidget {
final Map<String, dynamic> driver;
const DriverCard({super.key, required this.driver});
Widget buildActionButton({
required IconData icon,
required Color color,
required VoidCallback onPressed,
}) {
return CupertinoButton(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
onPressed: onPressed,
child: Icon(icon, color: color, size: 28),
);
}
@override
Widget build(BuildContext context) {
final controller = Get.find<MainController>();
return CupertinoCard(
margin: const EdgeInsets.all(16.0),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// حالة التلوين حسب isCall
Container(
decoration: AppStyle.boxDecoration1.copyWith(
color: driver['isCall'].toString() == '1'
? AppColor.greenColor
: AppColor.accentColor,
),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Text(
'Driver Information'.tr,
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
),
),
const SizedBox(height: 12),
InfoText(
'Name'.tr, driver['first_name'] + ' ' + driver['last_name']),
InfoText('Phone'.tr, driver['phone']),
InfoText('Email'.tr, driver['email']),
InfoText('License Type'.tr, driver['license_type']),
InfoText('License Categories'.tr, driver['license_categories']),
InfoText('National Number'.tr, driver['national_number']),
InfoText('Occupation'.tr, driver['occupation']),
const SizedBox(height: 12),
Text('Notes:'.tr,
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle),
const SizedBox(height: 8),
CupertinoTextField(
controller: controller.notesController,
placeholder: driver['notes'] ?? 'Enter notes here...'.tr,
maxLines: 3,
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
border: Border.all(color: CupertinoColors.systemGrey),
borderRadius: BorderRadius.circular(8.0),
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: MyElevatedButton(
title: 'Call Driver'.tr,
onPressed: () {
controller.makePhoneCall(driver['phone'].toString());
},
),
),
const SizedBox(width: 16),
Expanded(
child: CupertinoButton(
onPressed: () async {
await controller.addWelcomeCall(driver['id'].toString());
},
child: Text('Save Changes'.tr),
),
),
Expanded(
child: CupertinoButton(
onPressed: () async {
final phone = driver['phone'];
if (phone == null || phone.toString().isEmpty) {
Get.snackbar("خطأ", "لا يوجد رقم هاتف لهذا السائق");
return;
}
String message = "مرحباً،\n\n"
"يعطيك العافية أستاذ. نرحب بك في شركة *انطلق* للنقل الذكي.\n"
"نود تعريفك بالتطبيق ومميزاته لتتمكن من الاستفادة الكاملة وبدء العمل معنا بسهولة.\n\n"
"لأي استفسار أو مساعدة، يمكنك التواصل معنا على الأرقام التالية:\n\n"
"+963 952 475 742\n"
"+963 952 475 740\n"
"+963 952 475 734\n\n"
"فريق انطلق يتمنى لك تجربة موفقة ويوم سعيد.";
Get.find<MainController>().launchCommunication(
'whatsapp',
phone,
message,
);
},
child: Text('send'.tr),
),
),
],
),
],
),
),
);
}
}
class InfoText extends StatelessWidget {
final String label;
final dynamic value;
const InfoText(this.label, this.value, {super.key});
@override
Widget build(BuildContext context) {
final display = value?.toString() ?? 'N/A';
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Text(
'$label: $display',
style: CupertinoTheme.of(context).textTheme.textStyle,
),
);
}
}
class CupertinoCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry margin;
const CupertinoCard(
{super.key, required this.child, this.margin = EdgeInsets.zero});
@override
Widget build(BuildContext context) {
return Container(
margin: margin,
decoration: BoxDecoration(
color: CupertinoColors.systemBackground,
borderRadius: BorderRadius.circular(12.0),
boxShadow: [
BoxShadow(
color: CupertinoColors.systemGrey.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: child,
);
}
}

View File

@@ -0,0 +1,352 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:siro_service/constant/box_name.dart';
import 'package:siro_service/constant/links.dart';
import 'package:siro_service/controller/functions/crud.dart';
import 'package:siro_service/main.dart';
import 'package:siro_service/print.dart';
import 'package:siro_service/views/widgets/my_dialog.dart';
import 'pages/registration_captain_page.dart';
class RegisterCaptainServiceController extends GetxController {
// --- UI State Management ---
var isLoading = true.obs;
late PageController pageController;
var currentPageIndex = 0.obs;
var driverId = ''.obs;
var phone = ''.obs;
var serverData = <String, dynamic>{}.obs;
// En tu archivo registration_captain_controller.dart
final phoneController = TextEditingController();
// --- Dynamic Document Image URLs ---
final Map<String, RxString> docUrls = {
'driver_license_front': ''.obs,
'driver_license_back': ''.obs,
'car_license_front': ''.obs,
'car_license_back': ''.obs,
};
// --- Text Field Controllers ---
late TextEditingController firstNameController;
late TextEditingController lastNameController;
late TextEditingController siteController;
late TextEditingController nationalNumberController;
late TextEditingController birthdateController;
late TextEditingController licenseCategoriesController;
late TextEditingController expiryDateController;
late TextEditingController ownerController;
late TextEditingController colorController;
late TextEditingController carPlateController;
late TextEditingController vinController;
late TextEditingController licenseIssueDateController;
late TextEditingController carLicenseExpiryDateController;
late TextEditingController makeController;
late TextEditingController modelController;
late TextEditingController yearController;
// --- Reactive State for Dropdowns ---
var selectedGender = ''.obs;
var colorHex = ''.obs;
var selectedFuel = ''.obs;
@override
void onInit() {
super.onInit();
pageController = PageController();
// final arguments = Get.arguments;
// driverId.value = arguments?['driverId']; // Fallback for testing
// phone.value = arguments?['phone']; // Fallback for testing
_initializeTextControllers();
_initializeDocUrls();
fetchDataFromServer();
}
void _initializeDocUrls() {
const baseUrl =
"https://syria.intaleq.xyz/intaleq/auth/syria/driversDocs/syria.intaleq.xyz-";
docUrls['driver_license_front']!.value =
"$baseUrl${driverId.value}-driver_license_front.jpg";
docUrls['driver_license_back']!.value =
"$baseUrl${driverId.value}-driver_license_back.jpg";
docUrls['car_license_front']!.value =
"$baseUrl${driverId.value}-car_license_front.jpg";
docUrls['car_license_back']!.value =
"$baseUrl${driverId.value}-car_license_back.jpg";
}
void _initializeTextControllers() {
firstNameController = TextEditingController();
lastNameController = TextEditingController();
siteController = TextEditingController();
nationalNumberController = TextEditingController();
birthdateController = TextEditingController();
licenseCategoriesController = TextEditingController();
expiryDateController = TextEditingController();
ownerController = TextEditingController();
colorController = TextEditingController();
carPlateController = TextEditingController();
vinController = TextEditingController();
licenseIssueDateController = TextEditingController();
carLicenseExpiryDateController = TextEditingController();
makeController = TextEditingController();
modelController = TextEditingController();
yearController = TextEditingController();
}
Future<void> fetchDataFromServer() async {
isLoading.value = true;
try {
var responseString = await CRUD().post(
link: AppLink.getDriverDetailsForActivate,
payload: {'driverId': driverId.value});
if (responseString != 'failure') {
var decodedResponse = responseString;
if (decodedResponse['status'] == 'success' &&
(decodedResponse['message'] as List).isNotEmpty) {
var rawData = decodedResponse['message'][0] as Map<String, dynamic>;
// Sanitize data: ensure all values are strings to prevent type errors
serverData.value = rawData
.map((key, value) => MapEntry(key, value?.toString() ?? ''));
_populateControllersWithServerData();
}
}
} catch (e) {
Get.snackbar('Error', 'An unexpected error occurred: $e');
} finally {
isLoading.value = false;
}
}
void _populateControllersWithServerData() {
firstNameController.text = serverData['first_name'] ?? '';
lastNameController.text = serverData['last_name'] ?? '';
siteController.text = serverData['site'] ?? '';
nationalNumberController.text = serverData['national_number'] ?? '';
birthdateController.text = serverData['birthdate'] ?? '';
selectedGender.value = serverData['gender'] ?? '';
licenseCategoriesController.text = serverData['license_categories'] ?? '';
expiryDateController.text = serverData['expiry_date'] ?? '';
ownerController.text = serverData['owner'] ?? '';
final serverColorName = serverData['color'] ?? '';
final colorOption = kCarColorOptions.firstWhere(
(opt) => opt['key'] == serverColorName,
orElse: () => {'key': serverColorName, 'hex': '#000000'}); // Fallback
colorController.text = colorOption['key']!;
colorHex.value = colorOption['hex']!;
carPlateController.text = serverData['car_plate'] ?? '';
vinController.text = serverData['vin'] ?? '';
licenseIssueDateController.text = serverData['issue_date'] ?? '';
carLicenseExpiryDateController.text = serverData['expiration_date'] ?? '';
makeController.text = serverData['make'] ?? '';
modelController.text = serverData['model'] ?? '';
selectedFuel.value = serverData['fuel'] ?? '';
yearController.text = serverData['year'] ?? '';
}
Future<void> selectDate(
BuildContext context, TextEditingController controller) async {
DateTime initialDate = DateTime.now();
try {
if (controller.text.isNotEmpty)
initialDate = DateFormat('yyyy-MM-dd').parse(controller.text);
} catch (e) {/* Use default if parsing fails */}
DateTime? pickedDate = initialDate;
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
height: 280,
color: CupertinoColors.systemBackground.resolveFrom(context),
child: Column(
children: [
SizedBox(
height: 200,
child: CupertinoDatePicker(
initialDateTime: initialDate,
minimumDate: DateTime(1950),
maximumDate: DateTime(2050),
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) {
pickedDate = newDate;
},
),
),
CupertinoButton(
child: Text('Done'.tr),
onPressed: () {
if (pickedDate != null)
controller.text =
DateFormat('yyyy-MM-dd').format(pickedDate!);
Navigator.of(context).pop();
},
),
],
),
),
);
}
void nextPage() {
if (currentPageIndex.value < 3)
pageController.nextPage(
duration: const Duration(milliseconds: 400), curve: Curves.easeInOut);
}
void previousPage() {
if (currentPageIndex.value > 0)
pageController.previousPage(
duration: const Duration(milliseconds: 400), curve: Curves.easeInOut);
}
void updateAndActivateSyrianDriver() {
isLoading.value = true;
var bytes = utf8.encode(phoneController.text);
// 2. Genera el hash MD5 completo
var digest = md5.convert(bytes);
// 3. Convierte el hash a un string (tendrá 32 caracteres)
String fullMd5Hash = digest.toString();
// 4. Trunca el string a los primeros 20 caracteres
driverId.value = fullMd5Hash.substring(0, 20);
Map<String, dynamic> dataToSend = {
// Driver fields from all pages
'id': driverId.value,
'phone': phoneController.text,
'first_name': firstNameController.text,
'last_name': lastNameController.text,
'site': siteController.text,
'national_number': nationalNumberController.text,
'gender': selectedGender.value,
'birthdate': birthdateController.text,
'license_categories': licenseCategoriesController.text,
'expiry_date': expiryDateController.text,
'license_issue_date': licenseIssueDateController.text,
'password': md5.convert(utf8.encode('123456')).toString(),
'status': 'actives',
// Car fields from all pages
'owner': ownerController.text,
'color': colorController.text,
'color_hex': colorHex.value,
'car_plate': carPlateController.text,
'maritalStatus': box.read(BoxName.employeename) ?? 'unknown',
// 'vin': vinController.text,
'expiration_date': carLicenseExpiryDateController.text,
'make': makeController.text,
'model': modelController.text,
'fuel': selectedFuel.value,
'year': yearController.text,
};
print("--- Submitting Data to Server ---");
print(dataToSend);
CRUD()
.post(
link:
'${AppLink.server}/serviceapp/registerDriverAndCarService.php',
payload: dataToSend)
.then((response) {
isLoading.value = false;
var decodedResponse = (response);
Log.print('decodedResponse: ${decodedResponse}');
if (decodedResponse != 'failure' && decodedResponse['status'] == 'success') {
MyDialog().getDialog('Success'.tr, '',
Text('Driver has been activated successfully!'.tr), () {
Get.back();
Get.back();
// fetchDataFromServer();
Get.back();
// Get.off(() => RegisterCaptain());
});
} else {
Get.snackbar(
'Error'.tr,
'Failed to update driver: ${decodedResponse is Map ? decodedResponse['message'] : 'failure'}'.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}).catchError((error) {
isLoading.value = false;
Get.snackbar(
'Error'.tr,
'An error occurred: $error'.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
);
});
}
static const List<Map<String, String>> kCarColorOptions = [
{'key': 'white', 'hex': '#FFFFFF'},
{'key': 'black', 'hex': '#000000'},
{'key': 'silver', 'hex': '#C0C0C0'},
{'key': 'gray', 'hex': '#808080'},
{'key': 'gunmetal', 'hex': '#2A3439'},
{'key': 'red', 'hex': '#C62828'},
{'key': 'blue', 'hex': '#1565C0'},
{'key': 'navy', 'hex': '#0D47A1'},
{'key': 'green', 'hex': '#2E7D32'},
{'key': 'darkGreen', 'hex': '#1B5E20'},
{'key': 'beige', 'hex': '#D7CCC8'},
{'key': 'brown', 'hex': '#5D4037'},
{'key': 'maroon', 'hex': '#800000'},
{'key': 'burgundy', 'hex': '#800020'},
{'key': 'yellow', 'hex': '#F9A825'},
{'key': 'orange', 'hex': '#EF6C00'},
{'key': 'gold', 'hex': '#D4AF37'},
{'key': 'bronze', 'hex': '#CD7F32'},
{'key': 'champagne', 'hex': '#EFE1C6'},
{'key': 'purple', 'hex': '#6A1B9A'},
];
static const List<String> kFuelOptions = [
'بنزين',
'ديزل',
'هايبرد',
'كهربائي'
];
Color hexToColor(String code) =>
Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000);
void updateColorSelection(String hex) {
colorHex.value = hex;
colorController.text =
kCarColorOptions.firstWhere((o) => o['hex'] == hex)['key']!;
}
@override
void onClose() {
pageController.dispose();
firstNameController.dispose();
lastNameController.dispose();
siteController.dispose();
nationalNumberController.dispose();
birthdateController.dispose();
licenseCategoriesController.dispose();
expiryDateController.dispose();
ownerController.dispose();
colorController.dispose();
carPlateController.dispose();
vinController.dispose();
licenseIssueDateController.dispose();
carLicenseExpiryDateController.dispose();
makeController.dispose();
modelController.dispose();
yearController.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,338 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:siro_service/constant/box_name.dart';
import 'package:siro_service/constant/links.dart';
import 'package:siro_service/controller/functions/crud.dart';
import 'package:siro_service/main.dart';
import 'package:siro_service/print.dart';
import 'package:siro_service/views/widgets/my_dialog.dart';
import 'pages/registration_captain_page.dart';
class RegisterCaptainController extends GetxController {
// --- UI State Management ---
var isLoading = true.obs;
late PageController pageController;
var currentPageIndex = 0.obs;
var driverId = ''.obs;
var phone = ''.obs;
var serverData = <String, dynamic>{}.obs;
// En tu archivo registration_captain_controller.dart
final phoneController = TextEditingController();
// --- Dynamic Document Image URLs ---
final Map<String, RxString> docUrls = {
'driver_license_front': ''.obs,
'driver_license_back': ''.obs,
'car_license_front': ''.obs,
'car_license_back': ''.obs,
};
// --- Text Field Controllers ---
late TextEditingController firstNameController;
late TextEditingController lastNameController;
late TextEditingController siteController;
late TextEditingController nationalNumberController;
late TextEditingController birthdateController;
late TextEditingController licenseCategoriesController;
late TextEditingController expiryDateController;
late TextEditingController ownerController;
late TextEditingController colorController;
late TextEditingController carPlateController;
late TextEditingController vinController;
late TextEditingController licenseIssueDateController;
late TextEditingController carLicenseExpiryDateController;
late TextEditingController makeController;
late TextEditingController modelController;
late TextEditingController yearController;
// --- Reactive State for Dropdowns ---
var selectedGender = ''.obs;
var colorHex = ''.obs;
var selectedFuel = ''.obs;
@override
void onInit() {
super.onInit();
pageController = PageController();
final arguments = Get.arguments;
driverId.value = arguments?['driverId']; // Fallback for testing
phone.value = arguments?['phone']; // Fallback for testing
_initializeTextControllers();
_initializeDocUrls();
fetchDataFromServer();
}
void _initializeDocUrls() {
const baseUrl =
"https://syria.intaleq.xyz/intaleq/auth/syria/driversDocs/syria.intaleq.xyz-";
docUrls['driver_license_front']!.value =
"$baseUrl${driverId.value}-driver_license_front.jpg";
docUrls['driver_license_back']!.value =
"$baseUrl${driverId.value}-driver_license_back.jpg";
docUrls['car_license_front']!.value =
"$baseUrl${driverId.value}-car_license_front.jpg";
docUrls['car_license_back']!.value =
"$baseUrl${driverId.value}-car_license_back.jpg";
}
void _initializeTextControllers() {
firstNameController = TextEditingController();
lastNameController = TextEditingController();
siteController = TextEditingController();
nationalNumberController = TextEditingController();
birthdateController = TextEditingController();
licenseCategoriesController = TextEditingController();
expiryDateController = TextEditingController();
ownerController = TextEditingController();
colorController = TextEditingController();
carPlateController = TextEditingController();
vinController = TextEditingController();
licenseIssueDateController = TextEditingController();
carLicenseExpiryDateController = TextEditingController();
makeController = TextEditingController();
modelController = TextEditingController();
yearController = TextEditingController();
}
Future<void> fetchDataFromServer() async {
isLoading.value = true;
try {
var responseString = await CRUD().post(
link: AppLink.getDriverDetailsForActivate,
payload: {'driverId': driverId.value});
if (responseString != 'failure') {
var decodedResponse = responseString;
if (decodedResponse['status'] == 'success' &&
(decodedResponse['message'] as List).isNotEmpty) {
var rawData = decodedResponse['message'][0] as Map<String, dynamic>;
// Sanitize data: ensure all values are strings to prevent type errors
serverData.value = rawData
.map((key, value) => MapEntry(key, value?.toString() ?? ''));
_populateControllersWithServerData();
}
}
} catch (e) {
Get.snackbar('Error', 'An unexpected error occurred: $e');
} finally {
isLoading.value = false;
}
}
void _populateControllersWithServerData() {
firstNameController.text = serverData['first_name'] ?? '';
lastNameController.text = serverData['last_name'] ?? '';
siteController.text = serverData['site'] ?? '';
nationalNumberController.text = serverData['national_number'] ?? '';
birthdateController.text = serverData['birthdate'] ?? '';
selectedGender.value = serverData['gender'] ?? '';
licenseCategoriesController.text = serverData['license_categories'] ?? '';
expiryDateController.text = serverData['expiry_date'] ?? '';
ownerController.text = serverData['owner'] ?? '';
final serverColorName = serverData['color'] ?? '';
final colorOption = kCarColorOptions.firstWhere(
(opt) => opt['key'] == serverColorName,
orElse: () => {'key': serverColorName, 'hex': '#000000'}); // Fallback
colorController.text = colorOption['key']!;
colorHex.value = colorOption['hex']!;
carPlateController.text = serverData['car_plate'] ?? '';
vinController.text = serverData['vin'] ?? '';
licenseIssueDateController.text = serverData['issue_date'] ?? '';
carLicenseExpiryDateController.text = serverData['expiration_date'] ?? '';
makeController.text = serverData['make'] ?? '';
modelController.text = serverData['model'] ?? '';
selectedFuel.value = serverData['fuel'] ?? '';
yearController.text = serverData['year'] ?? '';
}
Future<void> selectDate(
BuildContext context, TextEditingController controller) async {
DateTime initialDate = DateTime.now();
try {
if (controller.text.isNotEmpty)
initialDate = DateFormat('yyyy-MM-dd').parse(controller.text);
} catch (e) {/* Use default if parsing fails */}
DateTime? pickedDate = initialDate;
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => Container(
height: 280,
color: CupertinoColors.systemBackground.resolveFrom(context),
child: Column(
children: [
SizedBox(
height: 200,
child: CupertinoDatePicker(
initialDateTime: initialDate,
minimumDate: DateTime(1950),
maximumDate: DateTime(2050),
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime newDate) {
pickedDate = newDate;
},
),
),
CupertinoButton(
child: Text('Done'.tr),
onPressed: () {
if (pickedDate != null)
controller.text =
DateFormat('yyyy-MM-dd').format(pickedDate!);
Navigator.of(context).pop();
},
),
],
),
),
);
}
void nextPage() {
if (currentPageIndex.value < 3)
pageController.nextPage(
duration: const Duration(milliseconds: 400), curve: Curves.easeInOut);
}
void previousPage() {
if (currentPageIndex.value > 0)
pageController.previousPage(
duration: const Duration(milliseconds: 400), curve: Curves.easeInOut);
}
void updateAndActivateSyrianDriver() {
isLoading.value = true;
Map<String, dynamic> dataToSend = {
// Driver fields from all pages
'driverId': driverId.value,
'phone': phone.value,
'first_name': firstNameController.text,
'last_name': lastNameController.text,
'site': siteController.text,
'national_number': nationalNumberController.text,
'gender': selectedGender.value,
'birthdate': '2000-01-01', //birthdateController.text,
'license_categories': licenseCategoriesController.text,
'expiry_date': expiryDateController.text,
'license_issue_date': licenseIssueDateController.text,
// Car fields from all pages
'owner': ownerController.text,
'color': colorController.text,
'color_hex': colorHex.value,
'car_plate': carPlateController.text,
'maritalStatus': box.read(BoxName.employeename) ?? 'unknown',
// 'vin': vinController.text,
'expiration_date': carLicenseExpiryDateController.text,
'make': makeController.text,
'model': modelController.text,
'fuel': selectedFuel.value,
'year': yearController.text,
};
print("--- Submitting Data to Server ---");
print(dataToSend);
CRUD()
.post(link: AppLink.updateDriverToActive, payload: dataToSend)
.then((response) {
isLoading.value = false;
var decodedResponse = (response);
Log.print('decodedResponse: ${decodedResponse}');
if (decodedResponse != 'failure' && decodedResponse['status'] == 'success') {
MyDialog().getDialog('Success'.tr, '',
Text('Driver has been activated successfully!'.tr), () {
Get.back();
Get.back();
// fetchDataFromServer();
Get.back();
// Get.off(() => RegisterCaptain());
});
} else {
Get.snackbar(
'Error'.tr,
'Failed to update driver: ${decodedResponse is Map ? decodedResponse['message'] : 'failure'}'.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}).catchError((error) {
isLoading.value = false;
Get.snackbar(
'Error'.tr,
'An error occurred: $error'.tr,
backgroundColor: Colors.red,
colorText: Colors.white,
);
});
}
static const List<Map<String, String>> kCarColorOptions = [
{'key': 'white', 'hex': '#FFFFFF'},
{'key': 'black', 'hex': '#000000'},
{'key': 'silver', 'hex': '#C0C0C0'},
{'key': 'gray', 'hex': '#808080'},
{'key': 'gunmetal', 'hex': '#2A3439'},
{'key': 'red', 'hex': '#C62828'},
{'key': 'blue', 'hex': '#1565C0'},
{'key': 'navy', 'hex': '#0D47A1'},
{'key': 'green', 'hex': '#2E7D32'},
{'key': 'darkGreen', 'hex': '#1B5E20'},
{'key': 'beige', 'hex': '#D7CCC8'},
{'key': 'brown', 'hex': '#5D4037'},
{'key': 'maroon', 'hex': '#800000'},
{'key': 'burgundy', 'hex': '#800020'},
{'key': 'yellow', 'hex': '#F9A825'},
{'key': 'orange', 'hex': '#EF6C00'},
{'key': 'gold', 'hex': '#D4AF37'},
{'key': 'bronze', 'hex': '#CD7F32'},
{'key': 'champagne', 'hex': '#EFE1C6'},
{'key': 'purple', 'hex': '#6A1B9A'},
];
static const List<String> kFuelOptions = [
'بنزين',
'ديزل',
'هايبرد',
'كهربائي'
];
Color hexToColor(String code) =>
Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000);
void updateColorSelection(String hex) {
colorHex.value = hex;
colorController.text =
kCarColorOptions.firstWhere((o) => o['hex'] == hex)['key']!;
}
@override
void onClose() {
pageController.dispose();
firstNameController.dispose();
lastNameController.dispose();
siteController.dispose();
nationalNumberController.dispose();
birthdateController.dispose();
licenseCategoriesController.dispose();
expiryDateController.dispose();
ownerController.dispose();
colorController.dispose();
carPlateController.dispose();
vinController.dispose();
licenseIssueDateController.dispose();
carLicenseExpiryDateController.dispose();
makeController.dispose();
modelController.dispose();
yearController.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,147 @@
import 'package:flutter/material.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
ThemeData lightThemeEnglish = ThemeData(
brightness: Brightness.light,
fontFamily: "SFPro",
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
);
ThemeData darkThemeEnglish = ThemeData(
brightness: Brightness.dark,
fontFamily: "SFPro",
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
);
ThemeData lightThemeArabic = ThemeData(
brightness: Brightness.light,
fontFamily: 'SFArabic',
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
);
ThemeData darkThemeArabic = ThemeData(
brightness: Brightness.dark,
fontFamily: 'SFArabic',
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
);