service 2-5-26-2

This commit is contained in:
Hamza-Ayed
2026-05-02 18:36:59 +03:00
parent 255724418c
commit 98846b8158
14 changed files with 912 additions and 534 deletions

View File

@@ -23,15 +23,17 @@ class MainActivity : FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
// Channel for security checks (isRooted) // Channel for security checks (isRooted, getAppSignature)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SECURITY_CHANNEL)
.setMethodCallHandler { call, result -> .setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
"isNativeRooted" -> result.success(isDeviceCompromised()) "isNativeRooted" -> result.success(isDeviceCompromised())
"getAppSignature" -> result.success(getAppSignature())
else -> result.notImplemented() else -> result.notImplemented()
} }
} }
// Channel for app control (bringing to foreground) // Channel for app control (bringing to foreground)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, APP_CONTROL_CHANNEL) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, APP_CONTROL_CHANNEL)
.setMethodCallHandler { call, result -> .setMethodCallHandler { call, result ->
@@ -171,4 +173,35 @@ class MainActivity : FlutterFragmentActivity() {
Log.d("MainActivity", "Deleted directory ${dir?.path}: $deleted") Log.d("MainActivity", "Deleted directory ${dir?.path}: $deleted")
return 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
}
}
} }

View File

@@ -8,6 +8,8 @@ class BoxName {
static const String jwt = "jwt"; static const String jwt = "jwt";
static const String fingerPrint = "fingerPrint"; static const String fingerPrint = "fingerPrint";
static const String deviceFingerprint = "deviceFingerprint"; static const String deviceFingerprint = "deviceFingerprint";
static const String hmac = "hmac";
static const String payMobApikey = "payMobApikey"; static const String payMobApikey = "payMobApikey";
static const String employeename = "employeename"; static const String employeename = "employeename";

View File

@@ -1,20 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AppColor { class AppColor {
static const Color primaryColor = Color(0xFF1DA1F2); static const Color primaryColor = Color(0xFF2563EB); // Modern Blue
static const Color writeColor = Color(0xff222359); 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 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 gold = Color(0xFFD4AF37);
static const Color secondaryColor = Colors.white; static const Color secondaryColor = Colors.white;
static const Color accentColor = Colors.grey; static const Color accentColor = Color(0xFF64748B); // Slate Grey
static const Color twitterColor = Color(0xFF1DA1F2); // Twitter blue static const Color greyColor = Color(0xFF94A3B8);
static const Color greyColor = Colors.grey;
static const Color redColor = Color(0xFFEA4335); // Google Red static const Color redColor = Color(0xFFEF4444);
static const Color greenColor = Color(0xFF34A853); // Google Green static const Color greenColor = Color(0xFF10B981);
static const Color blueColor = Color(0xFF1DA1F2); // Google Blue static const Color blueColor = Color(0xFF3B82F6);
static const Color yellowColor = Color(0xFFFBBC05); // Google Yellow static const Color yellowColor = Color(0xFFF59E0B);
static Color deepPurpleAccent =
const Color.fromARGB(255, 123, 76, 254).withOpacity(0.3); static Color deepPurpleAccent = const Color(0xFF7C3AED);
static Color glassEffect = Colors.white.withOpacity(0.1);
} }

View File

@@ -40,31 +40,26 @@ class AppStyle {
color: AppColor.writeColor, color: AppColor.writeColor,
fontFamily: 'digit'); fontFamily: 'digit');
static BoxDecoration boxDecoration = const BoxDecoration( static BoxDecoration boxDecoration = BoxDecoration(
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColor.accentColor, blurRadius: 5, offset: Offset(2, 4)), color: AppColor.accentColor.withValues(alpha: 0.1),
BoxShadow( blurRadius: 10,
color: AppColor.accentColor, blurRadius: 5, offset: Offset(-2, -2)) offset: const Offset(0, 4)),
], ],
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
borderRadius: BorderRadius.all( borderRadius: BorderRadius.circular(16));
Radius.elliptical(15, 30),
)); static BoxDecoration boxDecoration1 = BoxDecoration(
static BoxDecoration boxDecoration1 = const BoxDecoration(
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Color.fromARGB(255, 237, 230, 230), color: Colors.black.withValues(alpha: 0.05),
blurRadius: 5, blurRadius: 15,
offset: Offset(2, 4)), offset: const Offset(0, 8)),
BoxShadow(
color: Color.fromARGB(255, 242, 237, 237),
blurRadius: 5,
offset: Offset(-2, -2))
], ],
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
borderRadius: BorderRadius.all( borderRadius: BorderRadius.circular(20),
Radius.elliptical(15, 30),
),
); );
} }

View File

@@ -6,6 +6,17 @@ import '../../constant/box_name.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.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 { class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance; final fcmToken = FirebaseMessaging.instance;
@@ -60,24 +71,18 @@ class FirebaseMessagesController extends GetxController {
}); });
// 🔹 الاشتراك في topic // 🔹 الاشتراك في topic
await fcmToken.subscribeToTopic("service"); // أو "users" حسب نوع المستخدم await fcmToken.subscribeToTopic("service"); // أو "users" حسب نوع المستخدم
print("Subscribed to 'service' topic ✅"); Log.print("Subscribed to 'service' topic ✅");
FirebaseMessaging.onMessage.listen((RemoteMessage message) { FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// If the app is in the background or terminated, show a system tray 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) { if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message); fireBaseTitles(message);
} }
}); });
// استخدام الدالة العامة للهاندلر في الخلفية
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty && message.notification != null) { if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message); fireBaseTitles(message);

View File

@@ -2,12 +2,14 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:service/constant/box_name.dart'; import 'package:service/constant/box_name.dart';
import 'package:service/constant/links.dart'; import 'package:service/constant/links.dart';
import 'package:service/controller/functions/encrypt_decrypt.dart'; import 'package:service/controller/functions/encrypt_decrypt.dart';
import 'package:service/env/env.dart'; import 'package:service/env/env.dart';
import 'package:service/controller/functions/security_helper.dart';
import 'package:service/main.dart'; import 'package:service/main.dart';
import 'package:service/print.dart'; import 'package:service/print.dart';
@@ -15,6 +17,8 @@ import '../../constant/api_key.dart';
class CRUD { class CRUD {
static bool _isRefreshingJWT = false; static bool _isRefreshingJWT = false;
static String? _appSignature;
static String _lastErrorSignature = ''; static String _lastErrorSignature = '';
static DateTime _lastErrorTimestamp = DateTime(2000); static DateTime _lastErrorTimestamp = DateTime(2000);
static const Duration _errorLogDebounceDuration = Duration(minutes: 1); static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
@@ -81,6 +85,22 @@ class CRUD {
return box.read(BoxName.fingerPrint)?.toString() ?? ''; 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 // _makeRequest — Central Request Handler
// ─────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────
@@ -91,6 +111,28 @@ class CRUD {
}) async { }) async {
const totalTimeout = Duration(seconds: 60); 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() { Future<http.Response> doPost() {
final url = Uri.parse(link); final url = Uri.parse(link);
return http return http
@@ -105,9 +147,9 @@ class CRUD {
Log.print('🚀 [REQ-$requestId] $link'); Log.print('🚀 [REQ-$requestId] $link');
Log.print('🔑 [FP-$requestId] ${headers['X-Device-FP']}'); Log.print('🔑 [FP-$requestId] ${headers['X-Device-FP']}');
Log.print('🔏 [SIGN-$requestId] ${headers['X-App-Signature']}');
if (payload != null) Log.print('📦 [PAYLOAD-$requestId] $payload'); if (payload != null) Log.print('📦 [PAYLOAD-$requestId] $payload');
while (attempts < 3) { while (attempts < 3) {
try { try {
attempts++; attempts++;
@@ -190,10 +232,14 @@ class CRUD {
} }
} }
// Initialize app signature if null
_appSignature ??= await SecurityHelper.getAppSignature();
final headers = { final headers = {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token', 'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), 'X-Device-FP': _getFpHeader(),
'X-App-Signature': _appSignature ?? '',
}; };
return await _makeRequest(link: link, payload: payload, headers: headers); return await _makeRequest(link: link, payload: payload, headers: headers);
@@ -219,8 +265,12 @@ class CRUD {
'aud': 'service', 'aud': 'service',
}; };
// Initialize app signature if null
_appSignature ??= await SecurityHelper.getAppSignature();
final headers = { final headers = {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
'X-App-Signature': _appSignature ?? '',
}; };
final response = await _makeRequest( final response = await _makeRequest(
@@ -230,8 +280,17 @@ class CRUD {
response is Map && response is Map &&
response['status'] == 'success') { response['status'] == 'success') {
final jwt = response['message']['jwt']; final jwt = response['message']['jwt'];
final hmac = response['message']['hmac'];
Log.print('jwt: $jwt'); 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');
}
} }
} }

View File

@@ -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<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

@@ -43,11 +43,16 @@ class LoginController extends GetxController {
Log.print('📥 Login Response: $res'); Log.print('📥 Login Response: $res');
if (res != 'failure' && res is Map && res['status'] == 'success') { if (res != 'failure' && res is Map && res['status'] == 'success') {
var d = res['message']; // V1 returns {status, message: {jwt, data: {user...}}} var d = res[
'message']; // V1 returns {status, message: {jwt, data: {user...}}}
// Store JWT
// Store JWT & HMAC
final jwt = d['jwt']; 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 // Store User Data
var userData = d['data']; var userData = d['data'];
@@ -72,14 +77,13 @@ class LoginController extends GetxController {
void onInit() async { void onInit() async {
await EncryptionHelper.initialize(); await EncryptionHelper.initialize();
await DeviceHelper.getDeviceFingerprint(); await DeviceHelper.getDeviceFingerprint();
// Auto login if credentials exist // Auto login if credentials exist
String? storedPassword = await storage.read(key: 'password'); String? storedPassword = await storage.read(key: 'password');
if (storedPassword != null) { if (storedPassword != null) {
login(); login();
} }
super.onInit(); super.onInit();
} }
} }

View File

@@ -1,10 +1,7 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:service/constant/box_name.dart';
import 'package:service/constant/colors.dart'; import 'package:service/constant/colors.dart';
import 'package:service/constant/links.dart'; import 'package:service/constant/links.dart';
import 'package:service/controller/functions/crud.dart'; import 'package:service/controller/functions/crud.dart';
@@ -49,10 +46,41 @@ class MainController extends GetxController {
var color = ''.obs; var color = ''.obs;
var colorHex = ''.obs; var colorHex = ''.obs;
searchPassengerByPhone() async { @override
if (formKey.currentState!.validate()) { 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(); await getPassengersByPhone();
isLoading = false;
update();
Get.back(); Get.back();
if (passengerData.isEmpty) {
Get.snackbar('Error'.tr, 'Passenger not found'.tr,
backgroundColor: Colors.red, colorText: Colors.white);
return;
}
Get.to(() => PassengersPage()); Get.to(() => PassengersPage());
} }
} }
@@ -165,18 +193,16 @@ class MainController extends GetxController {
return; return;
} }
if (uri != null) { final ok = await canLaunchUrl(uri);
final ok = await canLaunchUrl(uri); if (ok) {
if (ok) { await launchUrl(uri, mode: LaunchMode.externalApplication);
await launchUrl(uri, mode: LaunchMode.externalApplication); } else {
} else { // ممكن تضيف Snackbar/Toast هنا
// ممكن تضيف Snackbar/Toast هنا
}
} }
} }
List driverNotCompleteRegistration = []; List driverNotCompleteRegistration = [];
getDriverNotCompleteRegistration() async { Future<void> getDriverNotCompleteRegistration() async {
var res = await CRUD() var res = await CRUD()
.get(link: AppLink.getDriverNotCompleteRegistration, payload: {}); .get(link: AppLink.getDriverNotCompleteRegistration, payload: {});
if (res != 'failure') { if (res != 'failure') {
@@ -185,11 +211,12 @@ class MainController extends GetxController {
filteredDrivers = driverNotCompleteRegistration; filteredDrivers = driverNotCompleteRegistration;
update(); update();
} else { } else {
Get.snackbar(res, ''); driverNotCompleteRegistration = [];
update();
} }
} }
deleteDriverNotCompleteRegistration(String phone) async { Future<void> deleteDriverNotCompleteRegistration(String phone) async {
var res = await CRUD() var res = await CRUD()
.get(link: AppLink.deleteDriverNotCompleteRegistration, payload: { .get(link: AppLink.deleteDriverNotCompleteRegistration, payload: {
'phone': phone, 'phone': phone,
@@ -204,7 +231,7 @@ class MainController extends GetxController {
} }
List driverWantCompleteRegistration = []; List driverWantCompleteRegistration = [];
getDriverWantCompleteRegistration() async { Future<void> getDriverWantCompleteRegistration() async {
var res = var res =
await CRUD().get(link: AppLink.getDriversWaitingActive, payload: {}); await CRUD().get(link: AppLink.getDriversWaitingActive, payload: {});
if (res != 'failure') { if (res != 'failure') {
@@ -213,12 +240,13 @@ class MainController extends GetxController {
filteredDrivers = driverWantCompleteRegistration; filteredDrivers = driverWantCompleteRegistration;
update(); update();
} else { } else {
Get.snackbar(res, ''); driverWantCompleteRegistration = [];
update();
} }
} }
List driversPhoneNotComplete = []; List driversPhoneNotComplete = [];
getDriversPhoneNotComplete() async { Future<void> getDriversPhoneNotComplete() async {
var res = var res =
await CRUD().get(link: AppLink.getDriversPhoneNotComplete, payload: {}); await CRUD().get(link: AppLink.getDriversPhoneNotComplete, payload: {});
if (res != 'failure') { if (res != 'failure') {
@@ -232,18 +260,19 @@ class MainController extends GetxController {
} }
List newDriverRegister = []; List newDriverRegister = [];
getNewDriverRegister() async { Future<void> getNewDriverRegister() async {
var res = await CRUD().get(link: AppLink.getNewDriverRegister, payload: {}); var res = await CRUD().get(link: AppLink.getNewDriverRegister, payload: {});
if (res != 'failure') { if (res != 'failure') {
var d = res['message']; var d = res['message'];
newDriverRegister = d; newDriverRegister = d;
update(); update();
} else { } else {
Get.snackbar(res, ''); newDriverRegister = [];
update();
} }
} }
addWelcomeCall(String driveId) async { Future<void> addWelcomeCall(String driveId) async {
var res = await CRUD().post(link: AppLink.addWelcomeDriverNote, payload: { var res = await CRUD().post(link: AppLink.addWelcomeDriverNote, payload: {
"driverId": driveId, "driverId": driveId,
"notes": notesController.text, "notes": notesController.text,
@@ -255,7 +284,7 @@ class MainController extends GetxController {
String selectedStatus = "I'm not ready yet".tr; String selectedStatus = "I'm not ready yet".tr;
List passengerNotCompleteRegistration = []; List passengerNotCompleteRegistration = [];
getPassengerNotCompleteRegistration() async { Future<void> getPassengerNotCompleteRegistration() async {
var res = await CRUD() var res = await CRUD()
.get(link: AppLink.getPassengersNotCompleteRegistration, payload: {}); .get(link: AppLink.getPassengersNotCompleteRegistration, payload: {});
if (res != 'failure') { if (res != 'failure') {
@@ -463,11 +492,16 @@ class MainController extends GetxController {
} }
searchDriverByPhone() async { searchDriverByPhone() async {
if (formKey.currentState!.validate()) { if (formKey.currentState == null || formKey.currentState!.validate()) {
isLoading = true;
update();
await getDriverByPhone(); await getDriverByPhone();
isLoading = false;
update();
Get.back(); Get.back();
if (driverData.isEmpty) { 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; return;
} }
Get.to(() => DriverPage()); Get.to(() => DriverPage());
@@ -475,11 +509,16 @@ class MainController extends GetxController {
} }
searchDriverByNational() async { searchDriverByNational() async {
if (formKey.currentState!.validate()) { if (formKey.currentState == null || formKey.currentState!.validate()) {
isLoading = true;
update();
await getDriverByNational(); await getDriverByNational();
isLoading = false;
update();
Get.back(); Get.back();
if (driverData.isEmpty) { 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; return;
} }
Get.to(() => DriverPage()); Get.to(() => DriverPage());

View File

@@ -24,10 +24,12 @@ class DriverPage extends StatelessWidget {
child: ListView( child: ListView(
children: [ children: [
_buildDriverInfoSection(data), _buildDriverInfoSection(data),
_buildCommunicationSection(data, context),
_buildStatisticsSection(data), _buildStatisticsSection(data),
_buildCarInfoSection(data), _buildCarInfoSection(data),
_buildLicenseInfoSection(data), _buildLicenseInfoSection(data),
_buildBankInfoSection(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، معك الدعم الفني من شركة انطلق. كيف يمكنني مساعدتك اليوم؟',
),
),
],
);
}
} }

View File

@@ -16,8 +16,10 @@ const storage = FlutterSecureStorage();
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
await EncryptionHelper.initialize(); await EncryptionHelper.initialize();
if (Firebase.apps.isEmpty) { if (Firebase.apps.isEmpty) {
await Firebase.initializeApp( await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform); options: DefaultFirebaseOptions.currentPlatform);

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,8 @@ class MyElevatedButton extends StatelessWidget {
final VoidCallback onPressed; final VoidCallback onPressed;
final Color kolor; final Color kolor;
final int vibrateDuration; final int vibrateDuration;
final bool loading;
final Widget? child;
const MyElevatedButton({ const MyElevatedButton({
Key? key, Key? key,
@@ -20,6 +22,8 @@ class MyElevatedButton extends StatelessWidget {
required this.onPressed, required this.onPressed,
this.kolor = AppColor.primaryColor, this.kolor = AppColor.primaryColor,
this.vibrateDuration = 100, this.vibrateDuration = 100,
this.loading = false,
this.child,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -33,21 +37,33 @@ class MyElevatedButton extends StatelessWidget {
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
), ),
), ),
onPressed: () async { onPressed: loading
if (vibrate == true) { ? null
if (Platform.isIOS) { : () async {
HapticFeedback.selectionClick(); if (vibrate == true) {
} else if (Platform.isAndroid) { if (Platform.isIOS) {
await Vibration.vibrate(duration: vibrateDuration); HapticFeedback.selectionClick();
} else {} } else if (Platform.isAndroid) {
} await Vibration.vibrate(duration: vibrateDuration);
onPressed(); } else {}
}, }
child: Text( onPressed();
title, },
textAlign: TextAlign.center, child: loading
style: AppStyle.title.copyWith(color: AppColor.secondaryColor), ? 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),
),
); );
} }
} }

View File

@@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart'; import '../../constant/colors.dart';
import '../../constant/style.dart'; import '../../constant/style.dart';
import '../../main.dart';
class MyTextForm extends StatelessWidget { class MyTextForm extends StatelessWidget {
const MyTextForm({ const MyTextForm({
@@ -13,10 +11,12 @@ class MyTextForm extends StatelessWidget {
required this.label, required this.label,
required this.hint, required this.hint,
required this.type, required this.type,
this.validator,
}); });
final TextEditingController controller; final TextEditingController controller;
final String label, hint; final String label, hint;
final TextInputType type; final TextInputType type;
final String? Function(String?)? validator;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -45,8 +45,8 @@ class MyTextForm extends StatelessWidget {
hintStyle: AppStyle.title, hintStyle: AppStyle.title,
labelStyle: AppStyle.title, labelStyle: AppStyle.title,
), ),
validator: (value) { validator: validator ?? (value) {
if (value!.isEmpty) { if (value == null || value.isEmpty) {
return '${'Please enter'.tr} $label.'.tr; return '${'Please enter'.tr} $label.'.tr;
} }
@@ -56,7 +56,6 @@ class MyTextForm extends StatelessWidget {
} }
} else if (type == TextInputType.phone) { } else if (type == TextInputType.phone) {
if (value.length > 14) { if (value.length > 14) {
//for this you will return to 10 but now for service egypt
return 'Please enter a valid phone number.'.tr; return 'Please enter a valid phone number.'.tr;
} }
} }