From 98846b815843bbfdc1a2fc107d183abc5f818d9b Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 2 May 2026 18:36:59 +0300 Subject: [PATCH] service 2-5-26-2 --- .../example/service_intaleq/MainActivity.kt | 35 +- lib/constant/box_name.dart | 2 + lib/constant/colors.dart | 31 +- lib/constant/style.dart | 31 +- lib/controller/firbase_messge.dart | 25 +- lib/controller/functions/crud.dart | 63 +- lib/controller/functions/security_helper.dart | 32 + lib/controller/login_controller.dart | 16 +- .../mainController/main_controller.dart | 91 +- .../mainController/pages/driver_page.dart | 48 + lib/main.dart | 2 + lib/views/home/main.dart | 1015 ++++++++++------- lib/views/widgets/elevated_btn.dart | 46 +- lib/views/widgets/my_textField.dart | 9 +- 14 files changed, 912 insertions(+), 534 deletions(-) create mode 100644 lib/controller/functions/security_helper.dart diff --git a/android/app/src/main/kotlin/com/example/service_intaleq/MainActivity.kt b/android/app/src/main/kotlin/com/example/service_intaleq/MainActivity.kt index 302888c..02bc7c4 100644 --- a/android/app/src/main/kotlin/com/example/service_intaleq/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/service_intaleq/MainActivity.kt @@ -23,15 +23,17 @@ class MainActivity : FlutterFragmentActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - // Channel for security checks (isRooted) + // Channel for security checks (isRooted, getAppSignature) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "isNativeRooted" -> result.success(isDeviceCompromised()) + "getAppSignature" -> result.success(getAppSignature()) else -> result.notImplemented() } } + // Channel for app control (bringing to foreground) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, APP_CONTROL_CHANNEL) .setMethodCallHandler { call, result -> @@ -171,4 +173,35 @@ class MainActivity : FlutterFragmentActivity() { Log.d("MainActivity", "Deleted directory ${dir?.path}: $deleted") return deleted } + + private fun getAppSignature(): String? { + return try { + val packageInfo = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { + packageManager.getPackageInfo(packageName, android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES) + } else { + @Suppress("DEPRECATION") + packageManager.getPackageInfo(packageName, android.content.pm.PackageManager.GET_SIGNATURES) + } + + val signatures = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { + packageInfo.signingInfo?.signingCertificateHistory + } else { + @Suppress("DEPRECATION") + packageInfo.signatures + } + + if (signatures != null && signatures.isNotEmpty()) { + val signature = signatures[0] + val md = java.security.MessageDigest.getInstance("SHA-256") + val digest = md.digest(signature.toByteArray()) + digest.joinToString("") { "%02x".format(it) } + } else { + null + } + } catch (e: Exception) { + Log.e("MainActivity", "Error getting app signature: ${e.message}", e) + null + } + } } + diff --git a/lib/constant/box_name.dart b/lib/constant/box_name.dart index 0277ddd..a2c9c6b 100644 --- a/lib/constant/box_name.dart +++ b/lib/constant/box_name.dart @@ -8,6 +8,8 @@ class BoxName { static const String jwt = "jwt"; static const String fingerPrint = "fingerPrint"; static const String deviceFingerprint = "deviceFingerprint"; + static const String hmac = "hmac"; + static const String payMobApikey = "payMobApikey"; static const String employeename = "employeename"; diff --git a/lib/constant/colors.dart b/lib/constant/colors.dart index aca88ed..5200e6d 100644 --- a/lib/constant/colors.dart +++ b/lib/constant/colors.dart @@ -1,20 +1,25 @@ import 'package:flutter/material.dart'; class AppColor { - static const Color primaryColor = Color(0xFF1DA1F2); - static const Color writeColor = Color(0xff222359); - + static const Color primaryColor = Color(0xFF2563EB); // Modern Blue + static const Color primaryLight = Color(0xFFDBEAFE); + static const Color writeColor = Color(0xFF1E293B); // Darker Slate + static const Color surfaceColor = Color(0xFFF8FAFC); + static const Color bronze = Color(0xFFCD7F32); - static const Color goldenBronze = Color(0xFFB87333); // Golden bronze color + static const Color goldenBronze = Color(0xFFB87333); static const Color gold = Color(0xFFD4AF37); + static const Color secondaryColor = Colors.white; - static const Color accentColor = Colors.grey; - static const Color twitterColor = Color(0xFF1DA1F2); // Twitter blue - static const Color greyColor = Colors.grey; - static const Color redColor = Color(0xFFEA4335); // Google Red - static const Color greenColor = Color(0xFF34A853); // Google Green - static const Color blueColor = Color(0xFF1DA1F2); // Google Blue - static const Color yellowColor = Color(0xFFFBBC05); // Google Yellow - static Color deepPurpleAccent = - const Color.fromARGB(255, 123, 76, 254).withOpacity(0.3); + static const Color accentColor = Color(0xFF64748B); // Slate Grey + static const Color greyColor = Color(0xFF94A3B8); + + static const Color redColor = Color(0xFFEF4444); + static const Color greenColor = Color(0xFF10B981); + static const Color blueColor = Color(0xFF3B82F6); + static const Color yellowColor = Color(0xFFF59E0B); + + static Color deepPurpleAccent = const Color(0xFF7C3AED); + static Color glassEffect = Colors.white.withOpacity(0.1); } + diff --git a/lib/constant/style.dart b/lib/constant/style.dart index 131f5bf..5492521 100644 --- a/lib/constant/style.dart +++ b/lib/constant/style.dart @@ -40,31 +40,26 @@ class AppStyle { color: AppColor.writeColor, fontFamily: 'digit'); - static BoxDecoration boxDecoration = const BoxDecoration( + static BoxDecoration boxDecoration = BoxDecoration( boxShadow: [ BoxShadow( - color: AppColor.accentColor, blurRadius: 5, offset: Offset(2, 4)), - BoxShadow( - color: AppColor.accentColor, blurRadius: 5, offset: Offset(-2, -2)) + color: AppColor.accentColor.withValues(alpha: 0.1), + blurRadius: 10, + offset: const Offset(0, 4)), ], color: AppColor.secondaryColor, - borderRadius: BorderRadius.all( - Radius.elliptical(15, 30), - )); - static BoxDecoration boxDecoration1 = const BoxDecoration( + borderRadius: BorderRadius.circular(16)); + + static BoxDecoration boxDecoration1 = BoxDecoration( boxShadow: [ BoxShadow( - color: Color.fromARGB(255, 237, 230, 230), - blurRadius: 5, - offset: Offset(2, 4)), - BoxShadow( - color: Color.fromARGB(255, 242, 237, 237), - blurRadius: 5, - offset: Offset(-2, -2)) + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 15, + offset: const Offset(0, 8)), ], color: AppColor.secondaryColor, - borderRadius: BorderRadius.all( - Radius.elliptical(15, 30), - ), + borderRadius: BorderRadius.circular(20), ); + + } diff --git a/lib/controller/firbase_messge.dart b/lib/controller/firbase_messge.dart index bccff4d..fab5b64 100644 --- a/lib/controller/firbase_messge.dart +++ b/lib/controller/firbase_messge.dart @@ -6,6 +6,17 @@ import '../../constant/box_name.dart'; import '../../main.dart'; import '../../print.dart'; +@pragma('vm:entry-point') +Future _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; @@ -60,24 +71,18 @@ class FirebaseMessagesController extends GetxController { }); // 🔹 الاشتراك في topic await fcmToken.subscribeToTopic("service"); // أو "users" حسب نوع المستخدم - print("Subscribed to 'service' topic ✅"); + 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 - 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.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { if (message.data.isNotEmpty && message.notification != null) { fireBaseTitles(message); diff --git a/lib/controller/functions/crud.dart b/lib/controller/functions/crud.dart index 1e679f4..baa240e 100644 --- a/lib/controller/functions/crud.dart +++ b/lib/controller/functions/crud.dart @@ -2,12 +2,14 @@ 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:service/constant/box_name.dart'; import 'package:service/constant/links.dart'; import 'package:service/controller/functions/encrypt_decrypt.dart'; import 'package:service/env/env.dart'; +import 'package:service/controller/functions/security_helper.dart'; import 'package:service/main.dart'; import 'package:service/print.dart'; @@ -15,6 +17,8 @@ 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); @@ -81,6 +85,22 @@ class CRUD { 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 // ─────────────────────────────────────────────────────────────── @@ -91,6 +111,28 @@ class CRUD { }) 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 doPost() { final url = Uri.parse(link); return http @@ -105,9 +147,9 @@ class CRUD { 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++; @@ -190,10 +232,14 @@ class CRUD { } } + // 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); @@ -219,8 +265,12 @@ class CRUD { '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( @@ -230,8 +280,17 @@ class CRUD { response is Map && response['status'] == 'success') { final jwt = response['message']['jwt']; + final hmac = response['message']['hmac']; + Log.print('jwt: $jwt'); - box.write(BoxName.jwt, c(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'); + } } } diff --git a/lib/controller/functions/security_helper.dart b/lib/controller/functions/security_helper.dart new file mode 100644 index 0000000..fa80c7d --- /dev/null +++ b/lib/controller/functions/security_helper.dart @@ -0,0 +1,32 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:service/print.dart'; + +class SecurityHelper { + static const platform = MethodChannel('com.service_intaleq/security'); + + static Future 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 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; + } + } +} diff --git a/lib/controller/login_controller.dart b/lib/controller/login_controller.dart index 952eaab..2f03afe 100644 --- a/lib/controller/login_controller.dart +++ b/lib/controller/login_controller.dart @@ -43,11 +43,16 @@ class LoginController extends GetxController { 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 + var d = res[ + 'message']; // V1 returns {status, message: {jwt, data: {user...}}} + + // Store JWT & HMAC final jwt = d['jwt']; - box.write(BoxName.jwt, c(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']; @@ -72,14 +77,13 @@ class LoginController extends GetxController { 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(); } } diff --git a/lib/controller/mainController/main_controller.dart b/lib/controller/mainController/main_controller.dart index 4a91b6f..7141679 100644 --- a/lib/controller/mainController/main_controller.dart +++ b/lib/controller/mainController/main_controller.dart @@ -1,10 +1,7 @@ -import 'dart:convert'; import 'dart:io'; -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:service/constant/box_name.dart'; import 'package:service/constant/colors.dart'; import 'package:service/constant/links.dart'; import 'package:service/controller/functions/crud.dart'; @@ -49,10 +46,41 @@ class MainController extends GetxController { var color = ''.obs; var colorHex = ''.obs; - searchPassengerByPhone() async { - if (formKey.currentState!.validate()) { + @override + void onInit() { + super.onInit(); + // refreshDashboardStats(); // Removed to save data consumption at start + } + + Future refreshDashboardStats() async { + isLoading = true; + update(); + try { + await Future.wait([ + getDriverWantCompleteRegistration(), + getDriverNotCompleteRegistration(), + getNewDriverRegister(), + ]); + } catch (e) { + Log.print('Error refreshing stats: $e'); + } + isLoading = false; + update(); + } + + Future 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()); } } @@ -165,18 +193,16 @@ class MainController extends GetxController { return; } - if (uri != null) { - final ok = await canLaunchUrl(uri); - if (ok) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else { - // ممكن تضيف Snackbar/Toast هنا - } + final ok = await canLaunchUrl(uri); + if (ok) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + // ممكن تضيف Snackbar/Toast هنا } } List driverNotCompleteRegistration = []; - getDriverNotCompleteRegistration() async { + Future getDriverNotCompleteRegistration() async { var res = await CRUD() .get(link: AppLink.getDriverNotCompleteRegistration, payload: {}); if (res != 'failure') { @@ -185,11 +211,12 @@ class MainController extends GetxController { filteredDrivers = driverNotCompleteRegistration; update(); } else { - Get.snackbar(res, ''); + driverNotCompleteRegistration = []; + update(); } } - deleteDriverNotCompleteRegistration(String phone) async { + Future deleteDriverNotCompleteRegistration(String phone) async { var res = await CRUD() .get(link: AppLink.deleteDriverNotCompleteRegistration, payload: { 'phone': phone, @@ -204,7 +231,7 @@ class MainController extends GetxController { } List driverWantCompleteRegistration = []; - getDriverWantCompleteRegistration() async { + Future getDriverWantCompleteRegistration() async { var res = await CRUD().get(link: AppLink.getDriversWaitingActive, payload: {}); if (res != 'failure') { @@ -213,12 +240,13 @@ class MainController extends GetxController { filteredDrivers = driverWantCompleteRegistration; update(); } else { - Get.snackbar(res, ''); + driverWantCompleteRegistration = []; + update(); } } List driversPhoneNotComplete = []; - getDriversPhoneNotComplete() async { + Future getDriversPhoneNotComplete() async { var res = await CRUD().get(link: AppLink.getDriversPhoneNotComplete, payload: {}); if (res != 'failure') { @@ -232,18 +260,19 @@ class MainController extends GetxController { } List newDriverRegister = []; - getNewDriverRegister() async { + Future getNewDriverRegister() async { var res = await CRUD().get(link: AppLink.getNewDriverRegister, payload: {}); if (res != 'failure') { var d = res['message']; newDriverRegister = d; update(); } else { - Get.snackbar(res, ''); + newDriverRegister = []; + update(); } } - addWelcomeCall(String driveId) async { + Future addWelcomeCall(String driveId) async { var res = await CRUD().post(link: AppLink.addWelcomeDriverNote, payload: { "driverId": driveId, "notes": notesController.text, @@ -255,7 +284,7 @@ class MainController extends GetxController { String selectedStatus = "I'm not ready yet".tr; List passengerNotCompleteRegistration = []; - getPassengerNotCompleteRegistration() async { + Future getPassengerNotCompleteRegistration() async { var res = await CRUD() .get(link: AppLink.getPassengersNotCompleteRegistration, payload: {}); if (res != 'failure') { @@ -463,11 +492,16 @@ class MainController extends GetxController { } searchDriverByPhone() async { - if (formKey.currentState!.validate()) { + if (formKey.currentState == null || formKey.currentState!.validate()) { + isLoading = true; + update(); await getDriverByPhone(); + isLoading = false; + update(); Get.back(); if (driverData.isEmpty) { - Get.snackbar('Error', 'Driver not found', backgroundColor: Colors.red); + Get.snackbar('Error'.tr, 'Driver not found'.tr, + backgroundColor: Colors.red, colorText: Colors.white); return; } Get.to(() => DriverPage()); @@ -475,11 +509,16 @@ class MainController extends GetxController { } searchDriverByNational() async { - if (formKey.currentState!.validate()) { + if (formKey.currentState == null || formKey.currentState!.validate()) { + isLoading = true; + update(); await getDriverByNational(); + isLoading = false; + update(); Get.back(); if (driverData.isEmpty) { - Get.snackbar('Error', 'Driver not found', backgroundColor: Colors.red); + Get.snackbar('Error'.tr, 'Driver not found'.tr, + backgroundColor: Colors.red, colorText: Colors.white); return; } Get.to(() => DriverPage()); diff --git a/lib/controller/mainController/pages/driver_page.dart b/lib/controller/mainController/pages/driver_page.dart index 8cb8b2d..b1efaea 100644 --- a/lib/controller/mainController/pages/driver_page.dart +++ b/lib/controller/mainController/pages/driver_page.dart @@ -24,10 +24,12 @@ class DriverPage extends StatelessWidget { child: ListView( children: [ _buildDriverInfoSection(data), + _buildCommunicationSection(data, context), _buildStatisticsSection(data), _buildCarInfoSection(data), _buildLicenseInfoSection(data), _buildBankInfoSection(data), + const SizedBox(height: 40), ], ), ), @@ -175,4 +177,50 @@ class DriverPage extends StatelessWidget { ], ); } + + 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، معك الدعم الفني من شركة انطلق. كيف يمكنني مساعدتك اليوم؟', + ), + ), + + ], + ); + } } + + diff --git a/lib/main.dart b/lib/main.dart index cc48a0e..65c5733 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,8 +16,10 @@ const storage = FlutterSecureStorage(); void main() async { WidgetsFlutterBinding.ensureInitialized(); + await GetStorage.init(); await EncryptionHelper.initialize(); + if (Firebase.apps.isEmpty) { await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform); diff --git a/lib/views/home/main.dart b/lib/views/home/main.dart index b7e3ae9..7a402e5 100644 --- a/lib/views/home/main.dart +++ b/lib/views/home/main.dart @@ -12,28 +12,14 @@ import 'package:service/views/widgets/elevated_btn.dart'; import 'package:service/views/widgets/my_dialog.dart'; import 'package:service/views/widgets/my_textField.dart'; -import '../../constant/box_name.dart'; import '../../constant/style.dart'; import '../../controller/mainController/pages/add_car.dart'; import '../../controller/mainController/pages/drivers_cant_register.dart'; import '../../controller/mainController/pages/new_driver.dart'; import '../../controller/mainController/pages/welcome_call.dart'; import '../../main.dart'; -import '../../print.dart'; import '../widgets/my_scafold.dart'; -// --- Service Item Model --- -// A helper class to structure the data for each service card. -// This makes the code cleaner and easier to manage. -class ServiceItem { - final String title; - final IconData icon; - final VoidCallback onTap; - - ServiceItem({required this.title, required this.icon, required this.onTap}); -} - -// --- Main Screen Widget (Redesigned) --- class Main extends StatelessWidget { Main({super.key}); @@ -41,449 +27,382 @@ class Main extends StatelessWidget { @override Widget build(BuildContext context) { - // --- List of Services --- - // All services are defined here in a list. This makes it easy to add, remove, or reorder them. - // The original onTap logic is preserved exactly as it was. - final List services = [ - ServiceItem( - title: 'passenger details by phone'.tr, - icon: Icons.person_search_rounded, - onTap: () { - MyDialog().getDialog( - 'insert passenger phone'.tr, - 'midTitle', - Column(children: [ - Form( - key: mainController.formKey, - child: MyTextForm( - controller: mainController.passengerPhoneController, - label: 'insert passenger phone'.tr, - hint: 'insert passenger phone'.tr, - type: TextInputType.phone, - ), - ), - ]), - () => mainController.searchPassengerByPhone(), - ); - }, - ), - ServiceItem( - title: 'Driver details by phone'.tr, - icon: Icons.support_agent_rounded, - onTap: () { - MyDialog().getDialog( - 'insert Driver phone'.tr, - 'midTitle', - Column(children: [ - Form( - key: mainController.formKey, - child: MyTextForm( - controller: mainController.driverPhoneController, - label: 'insert Driver phone'.tr, - hint: 'insert Driver phone'.tr, - type: TextInputType.phone, - ), - ), - ]), - () => mainController.searchDriverByPhone(), - ); - }, - ), - ServiceItem( - title: 'Driver details by national number'.tr, - icon: Icons.support_agent_rounded, - onTap: () { - MyDialog().getDialog( - 'insert Driver national'.tr, - 'midTitle', - Column(children: [ - Form( - key: mainController.formKey, - child: MyTextForm( - controller: mainController.driverPhoneController, - label: 'insert Driver national'.tr, - hint: 'insert Driver national'.tr, - type: TextInputType.number, - ), - ), - ]), - () => mainController.searchDriverByNational(), - ); - }, - ), - ServiceItem( - title: 'Drivers waitting Register'.tr, - icon: Icons.pending_actions_rounded, - onTap: () async { - await mainController.getDriverWantCompleteRegistration(); - Get.to(() => DriversCantRegister()); - }, - ), - ServiceItem( - title: 'Register new driver'.tr, - icon: Icons.person, - onTap: () { - // await mainController.getDriverWantCompleteRegistration(); - Get.to(() => RegisterCaptain()); - }, - ), - ServiceItem( - title: 'Drivers Cant Register'.tr, - icon: Icons.car_crash, - onTap: () async { - await mainController.getDriverNotCompleteRegistration(); - Get.to(() => DriversCantRegister()); - }, - ), - ServiceItem( - title: 'Drivers phones not register'.tr, - icon: Icons.person, - onTap: () async { - await mainController.getDriversPhoneNotComplete(); - Get.to(() => DriversCantRegister()); - }, - ), - ServiceItem( - title: 'Drivers Activity'.tr, - icon: Icons.person, - onTap: () async { - await mainController.getDriversPhoneNotComplete(); - Get.to(() => DriversCantRegister()); - }, - ), - ServiceItem( - title: 'Passengers Cant Register'.tr, - icon: Icons.group_off_rounded, - onTap: () async { - await mainController.getPassengerNotCompleteRegistration(); - Get.to(() => PassengersCantRegister()); - }, - ), - ServiceItem( - title: 'Add car'.tr, - icon: Icons.add_circle_outline_rounded, - onTap: () async { - await mainController.getdriverWithoutCar(); - if (mainController.driverWithoutCar.isNotEmpty) { - Get.to(() => const AddCar()); - } - }, - ), - ServiceItem( - title: 'Edit car plate'.tr, - icon: Icons.edit_note_rounded, - onTap: () async { - await mainController.getCarPlateNotEdit(); - if (mainController.carPlateNotEdit.isNotEmpty) { - Get.to(() => const EditCarPlate()); - } - }, - ), - ServiceItem( - title: "View complaint".tr, - icon: Icons.report_problem_rounded, - onTap: () { - Get.to(() => const Complaint()); - }, - ), - ServiceItem( - title: "Welcome call".tr, - icon: Icons.ring_volume_rounded, - onTap: () async { - await mainController.getNewDriverRegister(); - Get.to(() => const WelcomeCall()); - }, - ), - ServiceItem( - title: "best driver".tr, - icon: Icons.emoji_events_rounded, - onTap: () async { - await mainController.getNewDriverRegister(); - Get.to(() => DriverTheBest()); - }, - ), - ServiceItem( - title: "Add Driver Who Wants to Work".tr, - icon: Icons.person_add_alt_1_rounded, - onTap: () { - Get.defaultDialog( - barrierDismissible: false, - title: "Add Driver Who Wants to Work".tr, - content: SizedBox( - width: Get.width * .7, - height: 300, - child: Form( - key: mainController.formKey, - child: ListView( - children: [ - MyTextForm( - controller: mainController.driverNameController, - label: 'Insert Name of Driver'.tr, - hint: 'Insert Name of Driver'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.nationalIdController, - label: 'Insert national ID of Driver'.tr, - hint: 'Insert national ID of Driver'.tr, - type: TextInputType.number), - MyTextForm( - controller: mainController.phoneController, - label: 'Insert phone of Driver'.tr, - hint: 'Insert phone of Driver'.tr, - type: TextInputType.phone), - MyTextForm( - controller: mainController.licenseTypeController, - label: 'Insert license type of Driver'.tr, - hint: 'Insert license type of Driver'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.siteDriverController, - label: 'Insert site of Driver'.tr, - hint: 'Insert site of Driver'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.birthDateController, - label: 'Insert birth_date of Driver'.tr, - hint: 'Insert license type of Driver'.tr, - type: TextInputType.number), - ], - )), - ), - confirm: MyElevatedButton( - title: 'Add'.tr, - onPressed: () async { - // if (mainController.formKey.currentState!.validate()) { - var res = await CRUD() - .post(link: AppLink.addDriverWantWork, payload: { - "driver_name": mainController.driverNameController.text, - "national_id": mainController.nationalIdController.text, - "birth_date": mainController.birthDateController.text, - "site": mainController.siteDriverController.text, - "license_type": mainController.licenseTypeController.text, - "phone": mainController.phoneController.text, - }); - if (res != 'failure' && res['status'] == 'success') { - Get.back(); - mainController.driverNameController.clear(); - mainController.nationalIdController.clear(); - mainController.birthDateController.clear(); - mainController.licenseTypeController.clear(); - mainController.siteDriverController.clear(); - mainController.phoneController.clear(); - Get.snackbar('done', '', - backgroundColor: AppColor.greenColor); - } - // } - }, - kolor: AppColor.greenColor, - ), - cancel: MyElevatedButton( - title: 'Cancel'.tr, - kolor: AppColor.redColor, - onPressed: () { - Get.back(); - }), - ); - }, - ), - ServiceItem( - title: "Add Car Who Wants to Work".tr, - icon: Icons.directions_car_filled_rounded, - onTap: () { - Get.defaultDialog( - barrierDismissible: false, - title: "Add Car Who Wants to Work".tr, - content: SizedBox( - width: Get.width * .7, - height: 300, - child: Form( - key: mainController.formKey, - child: ListView( - children: [ - MyTextForm( - controller: mainController.carOwnerWorkController, - label: 'Insert Name of Owner'.tr, - hint: 'Insert Name of Owner'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.carNumberController, - label: 'Insert car_number of Driver'.tr, - hint: 'Insert car_number of Driver'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.phoneCarController, - label: 'Insert phone of Owner'.tr, - hint: 'Insert phone of Owner'.tr, - type: TextInputType.phone), - MyTextForm( - controller: mainController.manufactureYearController, - label: 'Insert year of Car'.tr, - hint: 'Insert year of Car'.tr, - type: TextInputType.number), - MyTextForm( - controller: mainController.carModelController, - label: 'Insert car_model of Driver'.tr, - hint: 'Insert car_model of Driver'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.siteCarController, - label: 'Insert site of Owner'.tr, - hint: 'Insert site of Owner'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.carTypeController, - label: 'Insert car_type of Driver'.tr, - hint: 'Insert car_type of Driver'.tr, - type: TextInputType.name), - MyTextForm( - controller: mainController.registrationDateController, - label: 'Insert registration_date of Car'.tr, - hint: 'Insert registration_date of Car'.tr, - type: TextInputType.datetime), - ], - )), - ), - confirm: MyElevatedButton( - title: 'Add'.tr, - onPressed: () async { - // if (mainController.formKey.currentState!.validate()) { - var res = - await CRUD().post(link: AppLink.addCarWantWork, payload: { - "owner_name": mainController.carOwnerWorkController.text, - "car_number": mainController.carNumberController.text, - "manufacture_year": - mainController.manufactureYearController.text, - "car_model": mainController.carModelController.text, - "car_type": mainController.carTypeController.text, - "site": mainController.siteCarController.text, - "registration_date": - mainController.registrationDateController.text, - "phone": mainController.phoneCarController.text, - }); - Log.print('res: ${res}'); - if (res != 'failure' && res['status'] == 'success') { - Get.back(); - mainController.ownerController.clear(); - mainController.carNumberController.clear(); - mainController.manufactureYearController.clear(); - mainController.carModelController.clear(); - mainController.siteCarController.clear(); - mainController.carTypeController.clear(); - mainController.registrationDateController.clear(); - mainController.phoneController.clear(); - Get.snackbar('done', '', - backgroundColor: AppColor.greenColor); - } - // } - }, - kolor: AppColor.greenColor, - ), - cancel: MyElevatedButton( - title: 'Cancel'.tr, - kolor: AppColor.redColor, - onPressed: () { - Get.back(); - }), - ); - }, - ), - ]; + return MyScaffold(title: 'Intaleq Service'.tr, isleading: false, body: [ + Container( + color: AppColor.surfaceColor, + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // --- Header Section --- + _buildHeader(), - // --- Building the UI --- - return MyScaffold( - title: 'Intaleq Service'.tr, - isleading: false, - action: MyElevatedButton( - title: 'Logout'.tr, - onPressed: () async { - // box.write(BoxName.employeename, 'masa'); - print(box.read(BoxName.employeename).toString()); - }, - kolor: AppColor.redColor, - ), - // The body now uses a GridView for a better layout. - // You can replace the color with your main theme color. - // backgroundColor: const Color(0xFFF5F7FA), - body: [ - GridView.builder( - padding: const EdgeInsets.all(16.0), - itemCount: services.length, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // Two columns - crossAxisSpacing: 16.0, // Horizontal space between cards - mainAxisSpacing: 16.0, // Vertical space between cards - childAspectRatio: 1.1, // Adjust card shape (width/height ratio) + // --- Statistics Section --- + _buildStatsSection(), + + // --- Actions Section --- + _buildCategoryTitle('🔍 Search & Inquiries'.tr), + _buildGridSection([ + ServiceItem( + title: 'passenger details by phone'.tr, + icon: Icons.person_search_rounded, + color: Colors.blue, + onTap: () => _showSearchDialog( + title: 'insert passenger phone'.tr, + controller: mainController.passengerPhoneController, + onSearch: () => mainController.searchPassengerByPhone(), + ), + ), + ServiceItem( + title: 'Driver details by phone'.tr, + icon: Icons.contact_phone_rounded, + color: Colors.indigo, + onTap: () => _showSearchDialog( + title: 'insert Driver phone'.tr, + controller: mainController.driverPhoneController, + onSearch: () => mainController.searchDriverByPhone(), + ), + ), + ServiceItem( + title: 'Driver details by national number'.tr, + icon: Icons.badge_rounded, + color: Colors.teal, + onTap: () => _showSearchDialog( + title: 'insert Driver national'.tr, + controller: mainController.driverPhoneController, + type: TextInputType.number, + onSearch: () => mainController.searchDriverByNational(), + ), + ), + ]), + + _buildCategoryTitle('⏳ Approval Queue'.tr), + _buildGridSection([ + ServiceItem( + title: 'Drivers waitting Register'.tr, + icon: Icons.pending_actions_rounded, + color: Colors.orange, + onTap: () async { + await mainController.getDriverWantCompleteRegistration(); + Get.to(() => DriversCantRegister()); + }, + ), + ServiceItem( + title: 'Drivers phones not register'.tr, + icon: Icons.phone_disabled_rounded, + color: Colors.blueGrey, + onTap: () async { + await mainController.getDriversPhoneNotComplete(); + Get.to(() => DriversCantRegister()); + }, + ), + ServiceItem( + title: 'Drivers Activity'.tr, + icon: Icons.assessment_rounded, + color: Colors.blueAccent, + onTap: () async { + await mainController.getDriversPhoneNotComplete(); + Get.to(() => DriversCantRegister()); + }, + ), + ServiceItem( + title: 'Drivers Cant Register'.tr, + icon: Icons.car_crash_rounded, + color: Colors.redAccent, + onTap: () async { + await mainController.getDriverNotCompleteRegistration(); + Get.to(() => DriversCantRegister()); + }, + ), + ServiceItem( + title: 'Passengers Cant Register'.tr, + icon: Icons.group_off_rounded, + color: Colors.deepOrange, + onTap: () async { + await mainController.getPassengerNotCompleteRegistration(); + Get.to(() => PassengersCantRegister()); + }, + ), + ]), + + _buildCategoryTitle('⚡ Quick Actions'.tr), + _buildGridSection([ + ServiceItem( + title: 'Register new driver'.tr, + icon: Icons.person_add_rounded, + color: Colors.green, + onTap: () => Get.to(() => RegisterCaptain()), + ), + ServiceItem( + title: 'Add Driver Who Wants to Work'.tr, + icon: Icons.handshake_rounded, + color: Colors.lightGreen, + onTap: () => _showAddDriverDialog(), + ), + ServiceItem( + title: 'Add Car Who Wants to Work'.tr, + icon: Icons.add_road_rounded, + color: Colors.cyan, + onTap: () => _showAddCarDialog(), + ), + ]), + + _buildCategoryTitle('🚗 Vehicle Management'.tr), + _buildGridSection([ + ServiceItem( + title: 'Add car'.tr, + icon: Icons.add_circle_outline_rounded, + color: Colors.purple, + onTap: () async { + await mainController.getdriverWithoutCar(); + if (mainController.driverWithoutCar.isNotEmpty) { + Get.to(() => const AddCar()); + } + }, + ), + ServiceItem( + title: 'Edit car plate'.tr, + icon: Icons.edit_attributes_rounded, + color: Colors.amber, + onTap: () async { + await mainController.getCarPlateNotEdit(); + if (mainController.carPlateNotEdit.isNotEmpty) { + Get.to(() => const EditCarPlate()); + } + }, + ), + ]), + + _buildCategoryTitle('📊 Reporting & Quality'.tr), + _buildGridSection([ + ServiceItem( + title: "View complaint".tr, + icon: Icons.report_problem_rounded, + color: Colors.deepOrange, + onTap: () => Get.to(() => const Complaint()), + ), + ServiceItem( + title: "Welcome call".tr, + icon: Icons.ring_volume_rounded, + color: Colors.pink, + onTap: () async { + await mainController.getNewDriverRegister(); + Get.to(() => const WelcomeCall()); + }, + ), + ServiceItem( + title: "best driver".tr, + icon: Icons.emoji_events_rounded, + color: Colors.orangeAccent, + onTap: () async { + await mainController.getNewDriverRegister(); + Get.to(() => DriverTheBest()); + }, + ), + ]), + + const SizedBox(height: 32), + ], ), - itemBuilder: (context, index) { - final service = services[index]; - return ServiceCard( - title: service.title, - icon: service.icon, - onTap: service.onTap, - ); - }, ), - ], + ), + ]); + } + + Widget _buildHeader() { + return Container( + padding: const EdgeInsets.all(24), + decoration: const BoxDecoration( + color: AppColor.primaryColor, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(32), + bottomRight: Radius.circular(32), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Welcome back,'.tr, + style: AppStyle.title.copyWith(color: Colors.white70), + ), + Text( + 'Service Agent'.tr, + style: AppStyle.headTitle2.copyWith(color: Colors.white), + ), + ], + ), + Row( + children: [ + IconButton( + icon: + const Icon(Icons.refresh_rounded, color: Colors.white), + onPressed: () => mainController.refreshDashboardStats(), + ), + const SizedBox(width: 8), + CircleAvatar( + radius: 24, + backgroundColor: Colors.white24, + child: IconButton( + icon: const Icon(Icons.logout, color: Colors.white), + onPressed: () { + box.erase(); + Get.offAllNamed('/login'); + }, + ), + ), + ], + ), + ], + ), + const SizedBox(height: 24), + // --- Custom Search Bar --- + Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: AppStyle.boxDecoration.copyWith( + color: Colors.white.withValues(alpha: 0.95), + borderRadius: BorderRadius.circular(16), + ), + child: TextField( + decoration: InputDecoration( + hintText: 'Quick Search...'.tr, + border: InputBorder.none, + icon: const Icon(Icons.search, color: AppColor.primaryColor), + ), + onSubmitted: (val) { + // Global search logic + }, + ), + ), + ], + ), ); } -} -// --- Service Card Widget --- -// A reusable widget for displaying each service. -class ServiceCard extends StatelessWidget { - final String title; - final IconData icon; - final VoidCallback onTap; + Widget _buildStatsSection() { + return GetBuilder( + builder: (controller) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0), + child: Row( + children: [ + _buildStatCard( + 'Pending'.tr, + controller.isLoading + ? '...' + : controller.driverWantCompleteRegistration.length + .toString(), + Icons.timer_rounded, + Colors.orange, + ), + const SizedBox(width: 12), + _buildStatCard( + 'New'.tr, + controller.isLoading + ? '...' + : controller.newDriverRegister.length.toString(), + Icons.person_add_alt_1_rounded, + Colors.green, + ), + const SizedBox(width: 12), + _buildStatCard( + 'Issues'.tr, + controller.isLoading + ? '...' + : controller.driverNotCompleteRegistration.length + .toString(), + Icons.warning_rounded, + Colors.red, + ), + ], + ), + ); + }, + ); + } - const ServiceCard({ - super.key, - required this.title, - required this.icon, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(16.0), + Widget _buildStatCard( + String title, String count, IconData icon, Color color) { + return Expanded( child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16.0), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.15), - spreadRadius: 2, - blurRadius: 8, - offset: const Offset(0, 4), // changes position of shadow - ), + padding: const EdgeInsets.all(16), + decoration: AppStyle.boxDecoration, + child: Column( + children: [ + Icon(icon, color: color, size: 24), + const SizedBox(height: 8), + Text(count, style: AppStyle.headTitle2.copyWith(fontSize: 20)), + Text(title, + style: AppStyle.subtitle.copyWith(color: AppColor.accentColor)), ], ), + ), + ); + } + + Widget _buildCategoryTitle(String title) { + return Padding( + padding: const EdgeInsets.fromLTRB(20, 24, 20, 12), + child: Text( + title, + style: AppStyle.headTitle2.copyWith(fontSize: 18), + ), + ); + } + + Widget _buildGridSection(List items) { + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 16), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + childAspectRatio: 1.15, + ), + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return _buildServiceCard(item); + }, + ); + } + + Widget _buildServiceCard(ServiceItem item) { + return InkWell( + onTap: item.onTap, + borderRadius: BorderRadius.circular(20), + child: Container( + decoration: AppStyle.boxDecoration1, child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Icon - Icon( - icon, - size: 48.0, - // You can replace this color with your AppStyle color - color: Theme.of(context).primaryColor, + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: item.color.withValues(alpha: 0.1), + shape: BoxShape.circle, + ), + child: Icon(item.icon, color: item.color, size: 30), ), const SizedBox(height: 12), - // Title Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( - title, + item.title, textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 14, - // You can replace this color with your AppStyle color - color: Color(0xFF4A4A4A), - ), + style: AppStyle.title + .copyWith(fontSize: 13, fontWeight: FontWeight.w600), maxLines: 2, overflow: TextOverflow.ellipsis, ), @@ -493,4 +412,224 @@ class ServiceCard extends StatelessWidget { ), ); } + + void _showSearchDialog({ + required String title, + required TextEditingController controller, + required VoidCallback onSearch, + TextInputType type = TextInputType.phone, + }) { + MyDialog().getDialog( + title, + 'Search Details'.tr, + Form( + key: mainController.formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MyTextForm( + controller: controller, + label: title, + hint: title, + type: type, + validator: (val) { + if (val == null || val.isEmpty) { + return 'Please enter a value'.tr; + } + return null; + }, + ), + ], + ), + ), + onSearch, + ); + } + + void _showAddDriverDialog() { + Get.defaultDialog( + barrierDismissible: false, + title: "Add Driver Who Wants to Work".tr, + titleStyle: AppStyle.headTitle2, + content: SizedBox( + width: Get.width * .8, + height: 350, + child: Form( + key: mainController.formKey, + child: ListView( + padding: const EdgeInsets.all(8), + children: [ + MyTextForm( + controller: mainController.driverNameController, + label: 'Insert Name of Driver'.tr, + hint: 'Insert Name of Driver'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.nationalIdController, + label: 'Insert national ID of Driver'.tr, + hint: 'Insert national ID of Driver'.tr, + type: TextInputType.number), + MyTextForm( + controller: mainController.phoneController, + label: 'Insert phone of Driver'.tr, + hint: 'Insert phone of Driver'.tr, + type: TextInputType.phone), + MyTextForm( + controller: mainController.licenseTypeController, + label: 'Insert license type of Driver'.tr, + hint: 'Insert license type of Driver'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.siteDriverController, + label: 'Insert site of Driver'.tr, + hint: 'Insert site of Driver'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.birthDateController, + label: 'Insert birth_date of Driver'.tr, + hint: 'YYYY-MM-DD'.tr, + type: TextInputType.datetime), + ], + ), + ), + ), + confirm: MyElevatedButton( + title: 'Add'.tr, + onPressed: () async { + var res = + await CRUD().post(link: AppLink.addDriverWantWork, payload: { + "driver_name": mainController.driverNameController.text, + "national_id": mainController.nationalIdController.text, + "birth_date": mainController.birthDateController.text, + "site": mainController.siteDriverController.text, + "license_type": mainController.licenseTypeController.text, + "phone": mainController.phoneController.text, + }); + if (res != 'failure' && res['status'] == 'success') { + Get.back(); + mainController.driverNameController.clear(); + mainController.nationalIdController.clear(); + mainController.birthDateController.clear(); + mainController.licenseTypeController.clear(); + mainController.siteDriverController.clear(); + mainController.phoneController.clear(); + Get.snackbar('Success'.tr, 'Added successfully'.tr, + backgroundColor: AppColor.greenColor, colorText: Colors.white); + } + }, + kolor: AppColor.greenColor, + ), + cancel: MyElevatedButton( + title: 'Cancel'.tr, + kolor: AppColor.redColor, + onPressed: () => Get.back()), + ); + } + + void _showAddCarDialog() { + Get.defaultDialog( + barrierDismissible: false, + title: "Add Car Who Wants to Work".tr, + titleStyle: AppStyle.headTitle2, + content: SizedBox( + width: Get.width * .8, + height: 350, + child: Form( + key: mainController.formKey, + child: ListView( + padding: const EdgeInsets.all(8), + children: [ + MyTextForm( + controller: mainController.carOwnerWorkController, + label: 'Insert Name of Owner'.tr, + hint: 'Insert Name of Owner'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.carNumberController, + label: 'Insert car_number of Driver'.tr, + hint: 'Insert car_number of Driver'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.phoneCarController, + label: 'Insert phone of Owner'.tr, + hint: 'Insert phone of Owner'.tr, + type: TextInputType.phone), + MyTextForm( + controller: mainController.manufactureYearController, + label: 'Insert year of Car'.tr, + hint: 'Insert year of Car'.tr, + type: TextInputType.number), + MyTextForm( + controller: mainController.carModelController, + label: 'Insert car_model of Driver'.tr, + hint: 'Insert car_model of Driver'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.siteCarController, + label: 'Insert site of Owner'.tr, + hint: 'Insert site of Owner'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.carTypeController, + label: 'Insert car_type of Driver'.tr, + hint: 'Insert car_type of Driver'.tr, + type: TextInputType.name), + MyTextForm( + controller: mainController.registrationDateController, + label: 'Insert registration_date of Car'.tr, + hint: 'YYYY-MM-DD'.tr, + type: TextInputType.datetime), + ], + ), + ), + ), + confirm: MyElevatedButton( + title: 'Add'.tr, + onPressed: () async { + var res = await CRUD().post(link: AppLink.addCarWantWork, payload: { + "owner_name": mainController.carOwnerWorkController.text, + "car_number": mainController.carNumberController.text, + "manufacture_year": mainController.manufactureYearController.text, + "car_model": mainController.carModelController.text, + "car_type": mainController.carTypeController.text, + "site": mainController.siteCarController.text, + "registration_date": mainController.registrationDateController.text, + "phone": mainController.phoneCarController.text, + }); + if (res != 'failure' && res['status'] == 'success') { + Get.back(); + mainController.carOwnerWorkController.clear(); + mainController.carNumberController.clear(); + mainController.manufactureYearController.clear(); + mainController.carModelController.clear(); + mainController.siteCarController.clear(); + mainController.carTypeController.clear(); + mainController.registrationDateController.clear(); + mainController.phoneCarController.clear(); + Get.snackbar('Success'.tr, 'Added successfully'.tr, + backgroundColor: AppColor.greenColor, colorText: Colors.white); + } + }, + kolor: AppColor.greenColor, + ), + cancel: MyElevatedButton( + title: 'Cancel'.tr, + kolor: AppColor.redColor, + onPressed: () => Get.back()), + ); + } +} + +class ServiceItem { + final String title; + final IconData icon; + final Color color; + final VoidCallback onTap; + + ServiceItem({ + required this.title, + required this.icon, + required this.color, + required this.onTap, + }); } diff --git a/lib/views/widgets/elevated_btn.dart b/lib/views/widgets/elevated_btn.dart index 3aa28f8..44dfab9 100644 --- a/lib/views/widgets/elevated_btn.dart +++ b/lib/views/widgets/elevated_btn.dart @@ -13,6 +13,8 @@ class MyElevatedButton extends StatelessWidget { final VoidCallback onPressed; final Color kolor; final int vibrateDuration; + final bool loading; + final Widget? child; const MyElevatedButton({ Key? key, @@ -20,6 +22,8 @@ class MyElevatedButton extends StatelessWidget { required this.onPressed, this.kolor = AppColor.primaryColor, this.vibrateDuration = 100, + this.loading = false, + this.child, }) : super(key: key); @override @@ -33,21 +37,33 @@ class MyElevatedButton extends StatelessWidget { borderRadius: BorderRadius.circular(12.0), ), ), - onPressed: () async { - if (vibrate == true) { - if (Platform.isIOS) { - HapticFeedback.selectionClick(); - } else if (Platform.isAndroid) { - await Vibration.vibrate(duration: vibrateDuration); - } else {} - } - onPressed(); - }, - child: Text( - title, - textAlign: TextAlign.center, - style: AppStyle.title.copyWith(color: AppColor.secondaryColor), - ), + onPressed: loading + ? null + : () async { + if (vibrate == true) { + if (Platform.isIOS) { + HapticFeedback.selectionClick(); + } else if (Platform.isAndroid) { + await Vibration.vibrate(duration: vibrateDuration); + } else {} + } + onPressed(); + }, + child: loading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : child ?? + Text( + title, + textAlign: TextAlign.center, + style: AppStyle.title.copyWith(color: AppColor.secondaryColor), + ), ); } } diff --git a/lib/views/widgets/my_textField.dart b/lib/views/widgets/my_textField.dart index 4e589e2..91b4bba 100644 --- a/lib/views/widgets/my_textField.dart +++ b/lib/views/widgets/my_textField.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../constant/box_name.dart'; import '../../constant/colors.dart'; import '../../constant/style.dart'; -import '../../main.dart'; class MyTextForm extends StatelessWidget { const MyTextForm({ @@ -13,10 +11,12 @@ class MyTextForm extends StatelessWidget { required this.label, required this.hint, required this.type, + this.validator, }); final TextEditingController controller; final String label, hint; final TextInputType type; + final String? Function(String?)? validator; @override Widget build(BuildContext context) { @@ -45,8 +45,8 @@ class MyTextForm extends StatelessWidget { hintStyle: AppStyle.title, labelStyle: AppStyle.title, ), - validator: (value) { - if (value!.isEmpty) { + validator: validator ?? (value) { + if (value == null || value.isEmpty) { return '${'Please enter'.tr} $label.'.tr; } @@ -56,7 +56,6 @@ class MyTextForm extends StatelessWidget { } } else if (type == TextInputType.phone) { if (value.length > 14) { - //for this you will return to 10 but now for service egypt return 'Please enter a valid phone number.'.tr; } }