service 2-5-26-2
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,17 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<http.Response> 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
lib/controller/functions/security_helper.dart
Normal file
32
lib/controller/functions/security_helper.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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());
|
||||
}
|
||||
}
|
||||
@@ -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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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());
|
||||
|
||||
@@ -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، معك الدعم الفني من شركة انطلق. كيف يمكنني مساعدتك اليوم؟',
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user