first commit

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

View File

@@ -0,0 +1,82 @@
import 'package:secure_string_operations/secure_string_operations.dart';
import '../env/env.dart';
import 'char_map.dart';
class AK {
static final String sss_pass = X.r(X.r(X.r(Env.sss_pass, cn), cC), cs);
static final String allowed = Env.allowed;
static final String allowedWallet = Env.allowedWallet;
static final String passnpassenger = X
.r(X.r(X.r(Env.passnpassenger, cn), cC), cs)
.toString()
.split(Env.addd)[0];
static final String newId = Env.newId;
static final String sss_encryptionSalt =
X.r(X.r(X.r(Env.sss_encryptionSalt, cn), cC), cs);
static final String basicAuthCredentials =
X.r(X.r(X.r(Env.basicAuthCredentials, cn), cC), cs);
static final String basicCompareFaces =
X.r(X.r(X.r(Env.basicCompareFaces, cn), cC), cs);
static final String accountSIDTwillo =
X.r(X.r(X.r(Env.accountSIDTwillo, cn), cC), cs);
static final String serverAPI = X.r(X.r(X.r(Env.serverAPI, cn), cC), cs);
static final String mapAPIKEY = Env.mapAPIKEY;
static final String mapSaasKey = Env.mapSaasKey;
static final String twilloRecoveryCode =
X.r(X.r(X.r(Env.twilloRecoveryCode, cn), cC), cs);
static final String authTokenTwillo =
X.r(X.r(X.r(Env.authTokenTwillo, cn), cC), cs);
static final String chatGPTkey = X.r(X.r(X.r(Env.chatGPTkey, cn), cC), cs);
static final String transactionCloude =
X.r(X.r(X.r(Env.transactionCloude, cn), cC), cs);
static final String visionApi = X.r(X.r(X.r(Env.visionApi, cn), cC), cs);
static final String chatGPTkeySefer =
X.r(X.r(X.r(Env.chatGPTkeySefer, cn), cC), cs);
static final String chatGPTkeySeferNew =
X.r(X.r(X.r(Env.chatGPTkeySeferNew, cn), cC), cs);
static final String serverPHP = Env.serverPHP;
static final String llamaKey = X.r(X.r(X.r(Env.llamaKey, cn), cC), cs);
static final String cohere = X.r(X.r(X.r(Env.cohere, cn), cC), cs);
static final String claudeAiAPI = X.r(X.r(X.r(Env.claudeAiAPI, cn), cC), cs);
static final String geminiApi = X.r(X.r(X.r(Env.geminiApi, cn), cC), cs);
static final String agoraAppId = X.r(X.r(X.r(Env.agoraAppId, cn), cC), cs);
static final String agoraAppCertificate =
X.r(X.r(X.r(Env.agoraAppCertificate, cn), cC), cs);
static final String integrationIdPayMob =
X.r(X.r(X.r(Env.integrationIdPayMob, cn), cC), cs);
static final String passwordPayMob =
X.r(X.r(X.r(Env.passwordPayMob, cn), cC), cs);
static final String usernamePayMob =
X.r(X.r(X.r(Env.usernamePayMob, cn), cC), cs);
static final String payMobApikey =
X.r(X.r(X.r(Env.payMobApikey, cn), cC), cs);
static final String integrationIdPayMobWallet =
X.r(X.r(X.r(Env.integrationIdPayMobWallet, cn), cC), cs);
static final String smsPasswordEgypt =
X.r(X.r(X.r(Env.smsPasswordEgypt, cn), cC), cs);
static final String ocpApimSubscriptionKey = Env.ocpApimSubscriptionKey;
static final String chatGPTkeySeferNew4 =
X.r(X.r(X.r(Env.chatGPTkeySeferNew4, cn), cC), cs);
static final String anthropicAIkeySeferNew =
X.r(X.r(X.r(Env.anthropicAIkeySeferNew, cn), cC), cs);
static final String llama3Key = X.r(X.r(X.r(Env.llama3Key, cn), cC), cs);
static final String payMobOutPassword =
X.r(X.r(X.r(Env.payMobOutPassword, cn), cC), cs);
// static final String privateKeyFCM = Env.privateKeyFCM;
static final String payMobOutUserName =
X.r(X.r(X.r(Env.payMobOutUserName, cn), cC), cs);
///////////
static final String keyOfApp = X.r(X.r(X.r(Env.keyOfApp, cn), cC), cs);
}
class KN {
static const String pmobsec = 'PAYMOBOUTCLIENTSECRET';
static const String pmobid = 'PAYMOBOUTCLIENT_ID';
}

View File

@@ -0,0 +1,124 @@
class BoxName {
static const String driverID = "driverID";
static const String countryCode = "countryCode";
static const String googlaMapApp = "googlaMapApp";
static const String keyOfApp = 'keyOfApp';
static const String initializationVector = 'initializationVector';
static const String firstTimeLoadKey = 'firstTimeLoadKey';
static const String jwt = "jwt";
static const String blockUntilDate = "blockUntilDate";
static const String rideId = "rideId";
static const String rideArgumentsFromBackground =
"rideArgumentsFromBackground";
static const String FCM_PRIVATE_KEY = "FCM_PRIVATE_KEY";
static const String hmac = "hmac";
static const String ttsEnabled = "ttsEnabled";
static const String deviceFingerprint = "deviceFingerprint";
static const String security_check = "security_check";
static const String rideType = "rideType";
static const String walletType = "walletType";
static const String fingerPrint = "fingerPrint";
static const String updateInterval = "updateInterval";
static const String payMobApikey = "payMobApikey";
static const String refreshToken = "refreshToken";
static const String lang = "lang";
static const String isvibrate = "isvibrate";
static const String myListString = "myListString";
static const String myList = "myList";
static const String bodyOrder = "bodyOrder";
static const String gender = "gender";
static const String phoneWallet = "phoneWallet";
static const String locationName = "locationName";
static const String basicLink = "basicLink";
static const String paymentLink = "paymentLink";
static const String isSavedPhones = "IsSavedPhones";
static const String isTest = "isTest";
static const String carType = "carType";
static const String carTypeOfDriver = "carTypeOfDriver";
static const String isFirstTime = "isFirstTime";
static const String deviceInfo = "deviceInfo";
static const String packagInfo = "packagInfo";
static const String phoneVerified = "phoneVerified";
static const String carPlate = "carPlate";
static const String statusDriverLocation = "statusDriverLocation";
static const String isAppInForeground = "isAppInForeground";
static const String rideStatus = "rideStatus";
static const String nameArabic = "nameArabic";
static const String carYear = "carYear";
static const String password = "password";
static const String isVerified = '0';
static const String arrivalTime = "arrivalTime";
static const String locationPermission = "locationPermission";
static const String passwordDriver = "passwordDriver";
static const String agreeTerms = "agreeTerms";
static const String addWork = 'addWork';
static const String addHome = 'addHome';
static const String rideArguments = 'rideArguments';
static const String durationData = 'durationData';
static const String rideStaticDriverData = 'rideStaticDriverData';
static const String lastTimeStaticThrottle = 'lastTimeStaticThrottle';
static const String lastTimeCaptainWalletCashOut =
'lastTimeCaptainWalletCashOut';
static const String tipPercentage = 'tipPercentage';
static const String faceDetectTimes = "faceDetectTimes";
static const String sosPhonePassenger = "sosPhonePassenger";
static const String sosPhoneDriver = "sosPhoneDriver";
static const String passengerID = "pasengerID";
static const String phone = "phone";
static const String phoneDriver = "phoneDriver";
static const String is_claimed = "is_claimed";
static const String isInstall = "isInstall";
static const String isGiftToken = "isGiftToken";
static const String lastOtpTime = "lastOtpTime";
static const String bankCodeDriver = "bankCodeDriver";
static const String accountBankNumberDriver = "accountBankNumberDriver";
static const String dobDriver = "dobDriver";
static const String sexDriver = "sexDriver";
static const String lastNameDriver = "lastNameDriver";
static const String name = "name";
static const String nameDriver = "nameDriver";
static const String driverPhotoUrl = "driverPhotoUrl";
static const String email = "email";
static const String emailDriver = "emailDriver";
static const String tokens = "tokens";
static const String tokenFCM = "tokenFCM";
static const String tokenDriver = "tokenDriver";
static const String cardNumber = "cardNumber";
static const String cardNumberDriver = "cardNumberDriver";
static const String cardHolderName = "cardHolderName";
static const String cardHolderNameDriver = "cardHolderNameDriver";
static const String expiryDate = "expiryDate";
static const String expiryDateDriver = "expiryDateDriver";
static const String cvvCode = "cvvCode";
static const String cvvCodeDriver = "cvvCodeDriver";
static const String passengerWalletDetails = "passengerWalletDetails";
static const String passengerWalletTotal = "passengerWalletTotal";
static const String passengerWalletFound = "passengerWalletFound";
static const String periods = 'periods';
static const String onBoarding = 'onBoarding';
static const String apiKeyRun = 'apiKeyRun';
static const String serverAPI = 'serverAPI';
static const String serverChosen = 'serverChosen';
static const String secretKey = 'secretKey';
static const String basicAuthCredentials = 'basicAuthCredentials';
static const String mapAPIKEY = 'mapAPIKEY';
static const String twilloRecoveryCode = 'twilloRecoveryCode';
static const String accountSIDTwillo = 'accountSIDTwillo';
static const String authTokenTwillo = 'authTokenTwillo';
static const String chatGPTkey = 'chatGPTkey';
static const String chatGPTkeySefer = 'chatGPTkeySefer';
static const String transactionCloude = 'transactionCloude';
static const String visionApi = 'visionApi';
static const String vin = "vin";
static const String make = "make";
static const String model = "model";
static const String year = "year";
static const String expirationDate = "expirationDate";
static const String color = "color";
static const String owner = "owner";
static const String registrationDate = "registrationDate";
static const String recentLocations = 'recentLocations';
static const String tripData = 'tripData';
}

View File

@@ -0,0 +1,75 @@
import '../env/env.dart';
Map<String, String> cs = {
"a": Env.a,
"b": Env.b,
"c": Env.c,
"d": Env.d,
"e": Env.e,
"f": Env.f,
"g": Env.g,
"h": Env.h,
"i": Env.i,
"j": Env.j,
"k": Env.k,
"l": Env.l,
"m": Env.m,
"n": Env.n,
"o": Env.o,
"p": Env.p,
"q": Env.q,
"r": Env.r,
"s": Env.s,
"t": Env.t,
"u": Env.u,
"v": Env.v,
"w": Env.w,
"x": Env.x,
"y": Env.y,
"z": Env.z,
};
Map<String, String> cC = {
"A": Env.A,
"B": Env.B,
"C": Env.C,
"D": Env.D,
"E": Env.E,
"F": Env.F,
"G": Env.G,
"H": Env.H,
"I": Env.I,
"J": Env.J,
"K": Env.K,
"L": Env.L,
"M": Env.M,
"N": Env.N,
"O": Env.O,
"P": Env.P,
"Q": Env.Q,
"R": Env.R,
"S": Env.S,
"T": Env.T,
"U": Env.U,
"V": Env.V,
"W": Env.W,
"X": Env.X,
"Y": Env.Y,
"Z": Env.Z
};
//
//
Map<String, String> cn = {
"0": "3",
"1": "7",
"2": "1",
"3": "9",
"4": "0",
"5": "5",
"6": "2",
"7": "6",
"8": "4",
"9": "8"
};

View File

@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AppColor {
// --- Core Brand Colors ---
/// **Primary Color:** The brand's signature blue.
static const Color primaryColor = Color(0xFF1DA1F2);
/// **Text/Write Color:** Dynamic based on theme.
static Color get writeColor =>
Get.isDarkMode ? Colors.white : const Color(0xFF1A1A1A);
/// **Secondary Color:** Main background color, dynamic based on theme.
static Color get secondaryColor =>
Get.isDarkMode ? const Color(0xFF121212) : Colors.white;
/// **Surface Color:** For cards and elevated elements.
static Color get surfaceColor =>
Get.isDarkMode ? const Color(0xFF1E1E1E) : Colors.white;
/// **Card Color:** Specifically for card backgrounds.
static Color get cardColor =>
Get.isDarkMode ? const Color(0xFF1E1E1E) : Colors.white;
/// **Border Color:** Subtle borders for both modes.
static Color get borderColor =>
Get.isDarkMode ? Colors.white10 : Colors.black12;
/// **Accent Color:** Greyish accent.
static const Color accentColor = Color.fromARGB(255, 148, 140, 141);
// --- Neutral & Status Colors ---
/// **Grey Color:** Dynamic based on theme.
static Color get grayColor =>
Get.isDarkMode ? Colors.grey[400]! : const Color(0xFF8E8E93);
/// **Red Color (Error):** Clear red for alerts.
static const Color redColor = Color(0xFFD32F2F);
/// **Green Color (Success):** Positive green.
static const Color greenColor = Color(0xFF388E3C);
/// **Blue Color (Info):** Info text or success green variant.
static const Color blueColor = Color(0xFF1DA1F2);
/// **Yellow Color (Warning):** Warm yellow.
static const Color yellowColor = Color(0xFFFFA000);
// --- Tier & Social Colors ---
static const Color gold = Color(0xFFFFD700);
static const Color bronze = Color(0xFFCD7F32);
static const Color goldenBronze = Color(0xFFB87333);
static const Color twitterColor = Color(0xFF1DA1F2);
// --- Utility Colors ---
static Color get greyColor => grayColor;
static Color get cyanBlue => const Color(0xFF1DA1F2);
static Color get cyanAccent => const Color(0xFF1DA1F2).withOpacity(0.12);
static Color get deepPurpleAccent => const Color(0xFFCE1126).withOpacity(0.1);
// --- Theme Helpers ---
static Brightness get brightness => Get.isDarkMode ? Brightness.dark : Brightness.light;
}

View File

@@ -0,0 +1,107 @@
// في ملف: constant/country_polygons.dart
import 'package:intaleq_maps/intaleq_maps.dart';
class CountryPolygons {
// ==========================================================
// 1. الأردن: تغطية الممر الحضري الرئيسي (من إربد شمالاً حتى العقبة جنوباً)
// حوالي 12 نقطة
// ==========================================================
static final List<LatLng> jordanBoundary = [
// شمال إربد (قرب الحدود)
const LatLng(32.65, 35.80),
// شمال شرق المفرق
const LatLng(32.35, 37.00),
// شرق الزرقاء / الأزرق
const LatLng(31.85, 36.80),
// جنوب شرق (نهاية الزحف السكاني)
const LatLng(31.00, 36.50),
// جنوب / معان
const LatLng(30.30, 35.75),
// العقبة
const LatLng(29.50, 35.00),
// البحر الأحمر / الحدود الغربية
const LatLng(29.50, 34.85),
// غرب وادي عربة
const LatLng(30.80, 35.25),
// منطقة البحر الميت / السلط
const LatLng(32.00, 35.50),
// العودة عبر وادي الأردن إلى الشمال
const LatLng(32.45, 35.60),
// العودة لنقطة إربد
const LatLng(32.65, 35.80),
];
// ==========================================================
// 2. سوريا: تغطية الممر الغربي والساحلي (درعا، دمشق، حمص، حماة، حلب، الساحل)
// حوالي 14 نقطة
// ==========================================================
static final List<LatLng> syriaBoundary = [
// درعا / الجنوب
const LatLng(32.65, 35.95),
// شرق السويداء (حدود المنطقة المأهولة)
const LatLng(32.85, 37.10),
// أطراف دمشق الشرقية
const LatLng(33.50, 36.65),
// تدمر (أقصى امتداد شرقي للمضلع)
const LatLng(34.50, 38.30),
// الرقة (شمال شرق)
const LatLng(35.95, 38.80),
// حلب (الشمال)
const LatLng(36.45, 37.15),
// الحدود الشمالية الغربية (إدلب / تركيا)
const LatLng(36.50, 36.50),
// اللاذقية (الساحل)
const LatLng(35.50, 35.75),
// طرطوس (الساحل)
const LatLng(34.80, 35.85),
// حمص
const LatLng(34.70, 36.70),
// حماة
const LatLng(35.10, 36.70),
// العودة إلى منطقة دمشق
const LatLng(33.40, 36.30),
// العودة إلى درعا
const LatLng(32.65, 35.95),
];
// ==========================================================
// 3. مصر: تغطية القاهرة الكبرى، الدلتا، والإسكندرية والإسماعيلية
// حوالي 10 نقاط
// ==========================================================
static final List<LatLng> egyptBoundary = [
// جنوب الفيوم (أقصى امتداد جنوبي غربي)
const LatLng(29.20, 30.60),
// جنوب القاهرة (العياط)
const LatLng(29.80, 31.30),
// شرق السويس
const LatLng(29.95, 32.70),
// الإسماعيلية / القناة
const LatLng(30.60, 32.25),
// بورسعيد / أطراف الدلتا الشمالية الشرقية
const LatLng(31.30, 31.80),
// دمياط / ساحل الدلتا
const LatLng(31.50, 31.25),
// الإسكندرية (أقصى الشمال الغربي)
const LatLng(31.20, 29.80),
// غرب الدلتا
const LatLng(30.50, 30.20),
// العودة لنقطة البداية
const LatLng(29.20, 30.60),
];
// دالة تُرجع رابط API بناءً على اسم الدولة
static String getRoutingApiUrl(String countryName) {
switch (countryName) {
case 'Jordan':
return 'https://routec.intaleq.xyz/route-jo';
case 'Syria':
return 'https://routec.intaleq.xyz/route';
case 'Egypt':
return 'https://routec.intaleq.xyz/route-eg';
default:
// الافتراضي في حالة لم يقع الموقع ضمن أي من المضلعات
return 'https://routec.intaleq.xyz/route';
}
}
}

View File

@@ -0,0 +1,104 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import '../controller/functions/crud.dart';
import '../main.dart';
import 'box_name.dart';
import 'char_map.dart';
import 'links.dart';
class AC {
gAK() async {
if (box.read(BoxName.apiKeyRun).toString() != 'run') {
var res = await CRUD().get(link: AppLink.getApiKey, payload: {});
var decod = jsonDecode(res);
// print(decod);
Map<String, dynamic> jsonData = {};
for (var i = 0; i < decod['message'].length; i++) {
String h = decod['message'][i]['hashed_key'].toString();
String retrievedString = X.r(X.r(X.r(h, cn), cC), cs);
await storage.write(
key: decod['message'][i]['name'].toString(),
value: retrievedString.toString(),
);
//
String name = decod['message'][i]['name'].toString();
String value = decod['message'][i]['hashed_key'].toString();
jsonData[name] = value;
}
String jsonString = json.encode(jsonData);
print(jsonString);
box.write(BoxName.apiKeyRun, 'run');
}
}
String q(String b, String c) {
final d = utf8.encode(c);
final e = utf8.encode(b);
final f = Hmac(sha256, d);
final g = f.convert(e);
final h = g.bytes;
final i = base64Url.encode(h);
return i;
}
String j(String k, String l) {
final m = utf8.encode(l);
final n = base64Url.decode(k);
final o = Hmac(sha256, m);
final p = o.convert(n);
final q = utf8.decode(p.bytes);
return q;
}
String a(String b, String c) {
int d = b.length;
int e = d ~/ 4;
List<String> f = [];
for (int g = 0; g < d; g += e) {
int h = g + e;
if (h > d) {
h = d;
}
String i = b.substring(g, h);
f.add(i);
}
print(f);
Map<String, String> j = {};
j['birinci'] = f[4];
j['ikinci'] = f[2];
j['üçüncü'] = c + f[1];
j['dördüncü'] = f[0];
j['beş'] = f[3];
String k = '';
j.forEach((l, m) {
k += m;
});
return k;
}
Map<String, String> n(String o, String c) {
String p = o.replaceAll(c, '');
Map<String, String> q = {};
q['birinci'] = p[p.length - 5] + p[p.length - 3];
q['ikinci'] = p[p.length - 1] + p[p.length - 15];
q['üçüncü'] = p[p.length - 9] + p[p.length - 12];
q['dördüncü'] = p[p.length - 11] + p[p.length - 6];
q['beş'] = p[p.length - 2] + p[p.length - 8];
return q;
}
}

View File

@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class FinanceDesignSystem {
// --- Colors ---
static Color get primaryDark => Get.isDarkMode ? const Color(0xFFE0E0E0) : const Color(0xFF0A0E21);
static Color get accentBlue => const Color(0xFF3D5AFE);
static Color get successGreen => const Color(0xFF00C853);
static Color get dangerRed => const Color(0xFFD50000);
static Color get backgroundColor => Get.isDarkMode ? const Color(0xFF0A0E21) : const Color(0xFFF6F8FA);
static Color get cardColor => Get.isDarkMode ? const Color(0xFF1E1E2E) : const Color(0xFFFFFFFF);
static Color get textSecondary => Get.isDarkMode ? Colors.white70 : const Color(0xFF757575);
static Color get textMuted => Get.isDarkMode ? Colors.white38 : const Color(0xFFBDBDBD);
static Color get borderColor => Get.isDarkMode ? Colors.white10 : Colors.grey.withOpacity(0.1);
// --- Gradients ---
static LinearGradient get balanceGradient => LinearGradient(
colors: Get.isDarkMode
? [const Color(0xFF1E1E2E), const Color(0xFF2A2A3E)]
: [const Color(0xFF0A0E21), const Color(0xFF1A237E)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const LinearGradient dangerGradient = LinearGradient(
colors: [Color(0xFFD50000), Color(0xFFFF5252)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
// --- Radius ---
static const double mainRadius = 16.0;
static const double cardRadius = 20.0;
static const double buttonRadius = 12.0;
// --- Spacing ---
static const double horizontalPadding = 16.0;
static const double verticalSectionPadding = 24.0;
// --- Text Styles ---
static TextStyle get balanceStyle => const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
);
static TextStyle get headingStyle => TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryDark,
);
static TextStyle get subHeadingStyle => TextStyle(
fontSize: 14,
color: textSecondary,
);
}

View File

@@ -0,0 +1,291 @@
class AppInformation {
static const String companyName = 'Intaleq';
static const String appName = 'Intaleq DRIVER';
static const String appVersion = 'Intaleq DRIVER';
static const String phoneNumber = '962798583052';
static const String linkedInProfile =
'https://www.linkedin.com/in/hamza-ayed/';
static const String website = 'https://intaleqapp.com';
static const String email = 'hamzaayed@intaleqapp.com';
static const String complaintPrompt =
'for this data for complaint from driver or passenger i collect all data i want you analyze this complaint and show what is reason and what is solution .this data collected from many table to find solution if payment in visa not complete and if ride status is finished it will be paymnet in payment table if ride status is not finished there is no need to pay and payment table is null for this ride and if paymentFromPaymentTable not null and visa type not cash the payment sucssessed . if ratingpassenger is low or passengr rating drivers low grade then dont mine of this passenger ,look at driver too like passengerratingdriver with rating or ratingtopassenger .in json add status of complaint and message to passenger and message to driver and message to call center write in arabic in json output with key in english .for output please just json i want';
static const String addd = 'BlBlNl';
static const String privacyPolicy = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intaleq - Privacy Policy & Terms of Use</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.7;
color: #333;
padding: 10px;
}
h1, h2, h3 {
color: #2c3e50;
}
h1 {
font-size: 1.8em;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
}
h2 {
font-size: 1.5em;
margin-top: 30px;
color: #2980b9;
}
h3 {
font-size: 1.2em;
margin-top: 20px;
color: #16a085;
}
strong {
color: #2c3e50;
}
ul {
padding-left: 20px;
list-style-position: outside;
}
.highlight {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #3498db;
margin: 20px 0;
border-radius: 4px;
}
a {
color: #3498db;
text-decoration: none;
}
</style>
</head>
<body>
<h1>Privacy Policy & Terms of Use</h1>
<div class="highlight">
<p><strong>Effective Date:</strong> August 9, 2025</p>
<p><strong>Last Updated:</strong> August 9, 2025</p>
</div>
<h2>1. Introduction and Acceptance</h2>
<p>By downloading, registering, or using the Intaleq application ("App"), you agree to be bound by this Privacy Policy and our Terms of Use. If you do not agree, you must stop using the App immediately. Your continued use constitutes acceptance of these terms and any future updates.</p>
<h2>2. Definitions</h2>
<ul>
<li><strong>"Intaleq", "we", "us":</strong> Refers to the Intaleq for Ride Hailing company, Damascus Syria (Owner & operator), which provides the technology platform.</li>
<li><strong>"Driver":</strong> An independent service provider who uses the App to offer transportation services.</li>
<li><strong>"Passenger", "you":</strong> An individual who uses the App to request transportation services.</li>
<li><strong>"Services":</strong> The connection between Passengers and Drivers facilitated by our App.</li>
</ul>
<h2>3. Privacy Policy</h2>
<h3>3.1 Information We Collect</h3>
<p>We collect information necessary to provide and improve our Services.</p>
<h4>A. Information You Provide:</h4>
<ul>
<li><strong>For Drivers:</strong> To ensure safety and compliance, we collect identity information, including your full name, phone number, personal photo, and official documents (e.g., driver's license, vehicle registration).</li>
<li><strong>For Passengers:</strong> We only require a phone number for registration and communication. We are not authorized to request or view official identity documents for passengers.</li>
</ul>
<h4>B. Information Collected Automatically:</h4>
<ul>
<li><strong>Location Data:</strong> We collect precise location data when the App is in use to facilitate ride matching, navigation, and for safety purposes.</li>
<li><strong>Device Data:</strong> We collect information about your device, such as model, operating system, and unique identifiers, to ensure App functionality and for security verification.</li>
<li><strong>Usage Data:</strong> We log how you interact with our App, including trip history and features used, to improve our services.</li>
</ul>
<h3>3.2 Payment Information</h3>
<p><strong>We do not collect, process, or store any sensitive payment information like credit/debit card numbers.</strong> We facilitate payments by connecting you to licensed, local third-party providers:</p>
<ul>
<li><strong>Mobile Carrier Billing:</strong> Payments via MTN and Syriatel are processed directly by them based on your registered phone number. A one-time password (OTP) sent by the carrier is required to confirm the transaction.</li>
<li><strong>Bank Card Payments:</strong> We connect you with the Syrian company "eCash" to process card payments. They handle the transaction, and your bank will send an OTP to your phone to authorize it.</li>
</ul>
<h3>3.3 How We Use Your Information</h3>
<ul>
<li>To operate and maintain the Services (e.g., connect Drivers and Passengers).</li>
<li>To verify Driver identity and eligibility.</li>
<li>To improve App security and prevent fraud.</li>
<li>To provide customer support.</li>
<li>To comply with legal obligations.</li>
</ul>
<h3>3.4 Data Sharing</h3>
<p>We do not sell your personal data. We only share it in the following limited circumstances:</p>
<ul>
<li><strong>Between Passenger and Driver:</strong> To facilitate a ride, we share necessary information like name, photo, and real-time location.</li>
<li><strong>With Service Providers:</strong> For services like payment processing and mapping. These providers are contractually obligated to protect your data.</li>
<li><strong>For Legal Reasons:</strong> If required by law or a valid legal order.</li>
</ul>
<h3>3.5 Policy for Minors</h3>
<p>Our services are intended for individuals over the age of 18.
<strong>For Drivers:</strong> We strictly verify the identity and age of all drivers to ensure no minors are operating on our platform.
<strong>For Passengers:</strong> While we do not verify passenger identity, the service is not directed at children under 18. If a parent or guardian becomes aware that their child has provided us with information without their consent, they should contact us immediately.</p>
<h2>4. User Obligations & Conduct</h2>
<ul>
<li>You must provide accurate and current information during registration.</li>
<li>You are responsible for maintaining the security of your account.</li>
<li>You agree not to use the App for any illegal activities, to harass others, or to cause damage to a Driver's vehicle.</li>
</ul>
<h2>5. Disclaimer of Liability</h2>
<p>The App is provided "as is". Intaleq is an intermediary platform and is not liable for the actions of Drivers or Passengers, accidents, delays, or any disputes between users. Our liability is limited to the fullest extent permitted by law.</p>
<h2>6. Policy Updates</h2>
<p>We may update these terms. If we make significant changes, we will notify you within the App. You will be required to review and accept the new terms to continue using the Services, ensuring your consent is active and informed.</p>
<h2>7. Account Deletion & Contact</h2>
<p>You have the right to request the deletion of your account and personal data. To do so, or for any other questions, please contact us. We will respond to deletion requests within 30 days.</p>
<p><strong>Email:</strong> <a href="mailto:support@intaleqapp.com">support@intaleqapp.com</a></p>
</body>
</html>
''';
static const String privacyPolicyArabic = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>انطلق - سياسة الخصوصية وشروط الاستخدام</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.7;
color: #333;
padding: 10px;
text-align: right;
}
h1, h2, h3 {
color: #2c3e50;
}
h1 {
font-size: 1.8em;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
}
h2 {
font-size: 1.5em;
margin-top: 30px;
color: #2980b9;
}
h3 {
font-size: 1.2em;
margin-top: 20px;
color: #16a085;
}
strong {
color: #2c3e50;
}
ul {
padding-right: 20px;
list-style-position: outside;
}
.highlight {
background-color: #f8f9fa;
padding: 15px;
border-right: 4px solid #3498db;
margin: 20px 0;
border-radius: 4px;
}
a {
color: #3498db;
text-decoration: none;
}
</style>
</head>
<body>
<h1>سياسة الخصوصية وشروط الاستخدام</h1>
<div class="highlight">
<p><strong>تاريخ النفاذ:</strong> 9 أغسطس 2025</p>
<p><strong>آخر تحديث:</strong> 9 أغسطس 2025</p>
</div>
<h2>1. المقدمة والقبول</h2>
<p>عبر تحميل أو تسجيل أو استخدام تطبيق "انطلق" ("التطبيق")، فإنك توافق على الالتزام بسياسة الخصوصية وشروط الاستخدام هذه. إذا كنت لا توافق، يجب عليك التوقف فورًا عن استخدام التطبيق. استمرارك في الاستخدام يُعد قبولاً لهذه الشروط وأي تحديثات مستقبلية لها.</p>
<h2>2. التعريفات</h2>
<ul>
<li><strong>"انطلق"، "نحن":</strong> تشير إلى شركة انطلق لنقل الركاب، دمشق سوريا (مالك ومشغل التطبيق)، التي توفر المنصة التقنية.</li>
<li><strong>"السائق":</strong> مقدم خدمة مستقل يستخدم التطبيق لتقديم خدمات النقل.</li>
<li><strong>"الراكب"، "أنت":</strong> الفرد الذي يستخدم التطبيق لطلب خدمات النقل.</li>
<li><strong>"الخدمات":</strong> عملية الربط بين الركاب والسائقين التي يسهلها تطبيقنا.</li>
</ul>
<h2>3. سياسة الخصوصية</h2>
<h3>3.1 المعلومات التي نجمعها</h3>
<p>نحن نجمع المعلومات الضرورية لتقديم خدماتنا وتحسينها.</p>
<h4>أ. المعلومات التي تقدمها بنفسك:</h4>
<ul>
<li><strong>بالنسبة للسائقين:</strong> لضمان السلامة والامتثال للقوانين، نجمع بيانات الهوية الشخصية، بما في ذلك الاسم الكامل، رقم الهاتف، صورة شخصية، والوثائق الرسمية (مثل رخصة القيادة وتسجيل المركبة).</li>
<li><strong>بالنسبة للركاب:</strong> نطلب فقط رقم هاتف للتسجيل والتواصل. نحن غير مخولين بطلب أو الاطلاع على وثائق الهوية الرسمية للركاب.</li>
</ul>
<h4>ب. المعلومات التي تُجمع تلقائيًا:</h4>
<ul>
<li><strong>بيانات الموقع:</strong> نجمع بيانات الموقع الجغرافي الدقيقة عند استخدام التطبيق لتسهيل تحديد أماكن الانطلاق والوصول، الملاحة، ولأغراض السلامة.</li>
<li><strong>بيانات الجهاز:</strong> نجمع معلومات عن جهازك (طراز، نظام تشغيل، معرفات فريدة) لضمان عمل التطبيق وللتحقق الأمني.</li>
<li><strong>بيانات الاستخدام:</strong> نسجل كيفية تفاعلك مع التطبيق، بما في ذلك سجل الرحلات والميزات المستخدمة، بهدف تحسين خدماتنا.</li>
</ul>
<h3>3.2 معلومات الدفع</h3>
<p><strong>نحن لا نجمع أو نعالج أو نخزن أي معلومات دفع حساسة</strong> مثل أرقام بطاقات الائتمان/الخصم. نحن نسهل عمليات الدفع عبر ربطك بمزودي خدمات محليين مرخصين:</p>
<ul>
<li><strong>الدفع عبر رصيد الهاتف المحمول:</strong> تتم معالجة الدفعات عبر شركتي MTN و Syriatel مباشرة من خلالهما بناءً على رقم هاتفك المسجل لديهم. يتطلب تأكيد العملية إدخال رمز تحقق (OTP) يُرسل من قبل شركة الاتصالات.</li>
<li><strong>الدفع عبر البطاقات البنكية:</strong> نربطك بشركة "eCash" السورية لمعالجة الدفعات بالبطاقات. هي التي تتولى المعاملة، وسيقوم البنك الذي تتعامل معه بإرسال رمز تحقق (OTP) إلى هاتفك لتفويض العملية.</li>
</ul>
<h3>3.3 كيف نستخدم معلوماتك</h3>
<ul>
<li>لتشغيل وصيانة الخدمات (مثل الربط بين السائقين والركاب).</li>
<li>للتحقق من هوية السائقين وأهليتهم.</li>
<li>لتحسين أمان التطبيق ومنع الاحتيال.</li>
<li>لتقديم الدعم الفني للعملاء.</li>
<li>للامتثال للالتزامات القانونية.</li>
</ul>
<h3>3.4 مشاركة البيانات</h3>
<p>نحن لا نبيع بياناتك الشخصية. نشاركها فقط في الحالات المحدودة التالية:</p>
<ul>
<li><strong>بين الراكب والسائق:</strong> لتسهيل الرحلة، نشارك المعلومات الضرورية مثل الاسم، الصورة، والموقع المباشر.</li>
<li><strong>مع مزودي الخدمات:</strong> مثل معالجي الدفع وخدمات الخرائط. هؤلاء المزودون ملزمون تعاقديًا بحماية بياناتك.</li>
<li><strong>لأسباب قانونية:</strong> إذا طُلب ذلك بموجب القانون أو أمر قضائي ساري المفعول.</li>
</ul>
<h3>3.5 سياسة القاصرين</h3>
<p>خدماتنا موجهة للأفراد الذين تزيد أعمارهم عن 18 عامًا.
<strong>بالنسبة للسائقين:</strong> نحن نتحقق بدقة من هوية وعمر جميع السائقين لضمان عدم وجود قاصرين يعملون على منصتنا.
<strong>بالنسبة للركاب:</strong> على الرغم من أننا لا نتحقق من هوية الركاب، فإن الخدمة غير موجهة للأطفال دون سن 18. إذا علم ولي الأمر أن طفله قد زودنا بمعلومات دون موافقته، فيجب عليه الاتصال بنا على الفور.</p>
<h2>4. التزامات المستخدم وسلوكه</h2>
<ul>
<li>يجب عليك تقديم معلومات دقيقة وحديثة عند التسجيل.</li>
<li>أنت مسؤول عن الحفاظ على أمان حسابك.</li>
<li>أنت توافق على عدم استخدام التطبيق لأي أنشطة غير قانونية، أو لمضايقة الآخرين، أو التسبب في ضرر لمركبة السائق.</li>
</ul>
<h2>5. إخلاء المسؤولية</h2>
<p>يتم تقديم التطبيق "كما هو". "انطلق" هي منصة وسيطة وليست مسؤولة عن تصرفات السائقين أو الركاب، أو الحوادث، أو التأخير، أو أي نزاعات بين المستخدمين. مسؤوليتنا محدودة إلى أقصى حد يسمح به القانون.</p>
<h2>6. تحديثات السياسة</h2>
<p>قد نقوم بتحديث هذه الشروط. في حال إجراء تغييرات جوهرية، سنقوم بإعلامك داخل التطبيق. سيُطلب منك مراجعة الشروط الجديدة وقبولها لمواصلة استخدام الخدمات، لضمان أن موافقتك فعالة ومبنية على معرفة.</p>
<h2>7. حذف الحساب والتواصل</h2>
<p>لديك الحق في طلب حذف حسابك وبياناتك الشخصية. للقيام بذلك، أو لأي استفسارات أخرى، يرجى التواصل معنا. سنرد على طلبات الحذف في غضون 30 يومًا.</p>
<p><strong>البريد الإلكتروني:</strong> <a href="mailto:support@intaleqapp.com">support@intaleqapp.com</a></p>
</body>
</html>
''';
}

View File

@@ -0,0 +1,423 @@
// import 'package:siro_driver/env/env.dart';
import '../main.dart';
import 'box_name.dart';
class AppLink {
static String serverPHP = box.read('serverPHP');
static String paymentServer = 'https://walletintaleq.intaleq.xyz/v1/main';
static String locationServer =
'https://location.intaleq.xyz/intaleq/ride/location';
static String locationServerSide =
'https://location.intaleq.xyz/intaleq/ride/location';
static String mapSaasRoute = 'https://map-saas.intaleqapp.com/api/maps/route';
static String mapSaasPlaces =
'https://map-saas.intaleqapp.com/api/geocoding/places';
static const String routeApiBaseUrl =
"https://routesjo.intaleq.xyz/route/v1/driving";
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v3';
static final String syria = 'https://syria.intaleq.xyz/intaleq';
static final String server = endPoint;
///=================ride==========================///
///https://api.intaleq.xyz/intaleq/ride
static String ride = '$server/ride';
static String rideServer = 'https://rides.intaleq.xyz/intaleq/ride';
///mapOSM = 'https://routesy.intaleq.xyz'
static String mapOSM = 'https://routesy.intaleq.xyz';
static String seferCairoServer = endPoint;
static String seferGizaServer =
box.read('Giza') ?? box.read(BoxName.serverChosen);
static String seferAlexandriaServer =
box.read('Alexandria') ?? box.read(BoxName.serverChosen);
// static final String server = Env.serverPHP;
static String loginJwtDriver = "$server/loginJwtDriver.php";
static String loginJwtWalletDriver = "$server/loginJwtWalletDriver.php";
static String loginFirstTimeDriver = "$server/loginFirstTimeDriver.php";
static String googleMapsLink = 'https://maps.googleapis.com/maps/api/';
static String llama = 'https://api.llama-api.com/chat/completions';
static String gemini =
'https://generativelanguage.googleapis.com/v1beta3/models/text-bison-001:generateText';
static String test = "$server/test.php";
//===============contact==========================
static String savePhones = "$ride/egyptPhones/add.php";
static String savePhonesSyria = "$ride/egyptPhones/syrianAdd.php";
static String getPhones = "$ride/egyptPhones/get.php";
////===============firebase==========================
static String getTokens = "$ride/firebase/get.php";
static String getDriverToken = "$ride/firebase/getDriverToken.php";
static String addTokens = "$ride/firebase/add.php";
static String addTokensDriver = "$ride/firebase/addDriver.php";
static String addTokensDriverWallet =
"$paymentServer/ride/firebase/addDriver.php";
//=======================Wallet===================
static String wallet = '$paymentServer/ride/passengerWallet';
static String walletDriver = '$paymentServer/ride/driverWallet';
static String getAllPassengerTransaction =
"$wallet/getAllPassengerTransaction.php";
static String payWithMTNConfirm =
"$paymentServer/ride/mtn/driver/confirm_payment.php";
static String payWithMTNStart =
"$paymentServer/ride/mtn/driver/mtn_start.php";
static String payWithSyriatelConfirm =
"$paymentServer/ride/syriatel/driver/confirm_payment.php";
static String payWithSyriatelStart =
"$paymentServer/ride/syriatel/driver/start_payment.php";
static String payWithEcashDriver =
"$paymentServer/ride/ecash/driver/payWithEcash.php";
static String payWithEcashPassenger =
"$paymentServer/ride/ecash/passenger/payWithEcash.php";
// wl.tripz-egypt.com/v1/main/ride/ecash/driver
static String getWalletByPassenger = "$wallet/getWalletByPassenger.php";
static String getPassengersWallet = "$wallet/get.php";
static String getPassengerWalletArchive =
"$wallet/getPassengerWalletArchive.php";
static String addPassengersWallet = "$wallet/add.php";
static String deletePassengersWallet = "$wallet/delete.php";
static String updatePassengersWallet = "$wallet/update.php";
static String getWalletByDriver = "$walletDriver/getWalletByDriver.php";
static String driverStatistic =
"$endPoint/ride/driverWallet/driverStatistic.php";
static String getDriverDetails =
"$seferCairoServer/ride/driverWallet/getDriverDetails.php";
// ================= Gamification Endpoints =================
static String getWeeklyAggregate = "$endPoint/ride/gamification/getWeeklyAggregate.php";
static String getLeaderboard = "$endPoint/ride/gamification/getLeaderboard.php";
static String claimChallengeReward = "$endPoint/ride/gamification/claimChallengeReward.php";
static String getReferralStats = "$endPoint/ride/gamification/getReferralStats.php";
static String getDriverBehavior = "$endPoint/ride/gamification/getDriverBehavior.php";
static String getDriverWeekPaymentMove =
"$walletDriver/getDriverWeekPaymentMove.php";
static String getDriversWallet = "$walletDriver/get.php";
static String addDriversWalletPoints = "$walletDriver/add.php";
static String addpromotionDriver = "$walletDriver/promotionDriver.php";
static String deleteDriversWallet = "$walletDriver/delete.php";
static String updateDriversWallet = "$walletDriver/update.php";
//=======================promo===================ride.mobile-app.store/ride/promo/get.php
static String promo = '$server/ride/promo';
static String getPassengersPromo = "$promo/get.php";
static String getPromoBytody = "$promo/getPromoBytody.php";
static String addPassengersPromo = "$promo/add.php";
static String deletePassengersPromo = "$promo/delete.php";
static String updatePassengersPromo = "$promo/update.php";
////=======================cancelRide===================
static String addCancelRideFromPassenger = "$rideServer/cancelRide/add.php";
static String addCancelTripFromDriverAfterApplied =
"$rideServer/cancelRide/addCancelTripFromDriverAfterApplied.php";
static String cancelRide = "$rideServer/cancelRide/get.php";
//-----------------ridessss------------------
static String addRides = "$rideServer/rides/add.php";
static String getRides = "$rideServer/rides/get.php";
static String getPlacesSyria = "$rideServer/places_syria/get.php";
static String getMishwari = "$rideServer/mishwari/get.php";
static String getMishwariDriver = "$rideServer/mishwari/getDriver.php";
static String sendChatMessage = "$server/ride/chat/send_message.php";
static String getTripCountByCaptain =
"$rideServer/rides/getTripCountByCaptain.php";
static String getRideOrderID = "$rideServer/rides/getRideOrderID.php";
static String getRideStatus = "$rideServer/rides/getRideStatus.php";
static String getOverLayStatus = "$ride/overLay/get.php";
static String getArgumentAfterAppliedFromBackground =
"$ride/overLay/getArgumentAfterAppliedFromBackground.php";
static String addOverLayStatus = "$ride/overLay/add.php";
static String getapiKey = "$ride/apiKey/get.php";
static String getapiKeySefer = "$ride/apiKey/get.php";
static String getRideStatusBegin = "$rideServer/rides/getRideStatusBegin.php";
static String getRideStatusFromStartApp =
"$rideServer/rides/getRideStatusFromStartApp.php";
static String updateRides = "$rideServer/rides/update.php";
static String updateRideAndCheckIfApplied =
"$rideServer/rides/updateRideAndCheckIfApplied.php";
static String updateStausFromSpeed =
"$rideServer/rides/updateStausFromSpeed.php";
static String deleteRides = "$rideServer/rides/delete.php";
//-----------------DriverPayment------------------
static String addDriverScam = "$ride/driver_scam/add.php";
static String getDriverScam = "$ride/driver_scam/get.php";
/////////---getKazanPercent===////////////
static String getKazanPercent = "$ride/kazan/get.php";
static String addKazanPercent = "$ride/kazan/add.php";
////-----------------DriverPayment------------------
static String addDrivePayment = "$paymentServer/ride/payment/add.php";
static String payWithPayMobCardDriver =
"$paymentServer/ride/payMob/paymob_driver/payWithCard.php";
static String payWithWallet =
"$paymentServer/ride/payMob/paymob_driver/payWithWallet.php";
static String paymetVerifyDriver =
"$paymentServer/ride/payMob/paymob_driver/paymet_verfy.php";
static String updatePaymetToPaid =
"$paymentServer/ride/payment/updatePaymetToPaid.php";
static String paymobPayoutDriverWallet =
"$paymentServer/ride/payMob/paymob_driver/paymob_payout.php'";
static String addSeferWallet = "$paymentServer/ride/seferWallet/add.php";
static String getSeferWallet = "$paymentServer/ride/seferWallet/get.php";
static String addDriverPaymentPoints =
"$paymentServer/ride/driverPayment/add.php";
static String addPaymentTokenDriver =
"$paymentServer/ride/driverWallet/addPaymentToken.php"; //driverWallet/addPaymentToken.php
static String addPaymentTokenPassenger =
"$paymentServer/ride/passengerWallet/addPaymentTokenPassenger.php";
static String getDriverPaymentPoints =
"$paymentServer/ride/driverWallet/get.php";
static String getDriverPaymentToday = "$paymentServer/ride/payment/get.php";
static String getCountRide = "$rideServer/payment/getCountRide.php";
static String getAllPaymentFromRide =
"$paymentServer/ride/payment/getAllPayment.php";
static String getAllPaymentVisa =
"$paymentServer/ride/payment/getAllPaymentVisa.php";
//-----------------Passenger NotificationCaptain------------------
static String addNotificationPassenger =
"$ride/notificationPassenger/add.php";
static String getNotificationPassenger =
"$ride/notificationPassenger/get.php";
static String updateNotificationPassenger =
"$ride/notificationPassenger/update.php";
//-----------------Driver NotificationCaptain------------------
static String addNotificationCaptain = "$ride/notificationCaptain/add.php";
static String addWaitingRide = "$ride/notificationCaptain/addWaitingRide.php";
static String deleteAvailableRide =
"$ride/notificationCaptain/deleteAvailableRide.php";
static String updateWaitingRide =
"$ride/notificationCaptain/updateWaitingTrip.php";
static String getRideWaiting =
"$endPoint/ride/notificationCaptain/getRideWaiting.php";
static String getNotificationCaptain = "$ride/notificationCaptain/get.php";
static String updateNotificationCaptain =
"$ride/notificationCaptain/update.php";
static String deleteNotificationCaptain =
"$ride/notificationCaptain/delete.php";
//-----------------Api Key------------------
static String addApiKey = "$ride/apiKey/add.php";
static String getApiKey = "$ride/apiKey/get.php";
static String getCnMap = "$server/auth/cnMap.php";
static String getPromptDriverDocumentsEgypt =
"$server/auth/captin/getPromptDriverDocumentsEgypt.php";
static String updateApiKey = "$ride/apiKey/update.php";
static String deleteApiKey = "$ride/apiKey/delete.php";
static String checkPhoneNumberISVerfiedDriver =
"$auth/checkPhoneNumberISVerfiedDriver.php";
static String getTesterApp = "$auth/Tester/getTesterApp.php";
static String updateTesterApp = "$auth/Tester/updateTesterApp.php";
//-----------------healthInsuranceProvider------------------
static String addHealthInsuranceProvider = "$server/driver_assurance/add.php";
static String getHealthInsuranceProvider = "$server/driver_assurance/get.php";
//-----------------Feed Back------------------
static String addFeedBack = "$ride/feedBack/add.php";
static String getFeedBack = "$ride/feedBack/get.php";
static String updateFeedBack = "$ride/feedBack/updateFeedBack.php";
static String add_solve_all = "$server/ride/feedBack/add_solve_all.php";
static String uploadAudio = "$server/upload_audio.php";
//-----------------Tips------------------
static String addTips = "$ride/tips/add.php";
static String getTips = "$ride/tips/get.php";
static String updateTips = "$ride/tips/update.php";
//-----------------Help Center------------------
static String addhelpCenter = "$ride/helpCenter/add.php";
static String gethelpCenter = "$ride/helpCenter/get.php";
static String getByIdhelpCenter = "$ride/helpCenter/getById.php";
static String updatehelpCenter = "$ride/helpCenter/update.php";
static String deletehelpCenter = "$ride/helpCenter/delete.php";
//-----------------license------------------
static String addLicense = "$ride/license/add.php";
static String getLicense = "$ride/license/get.php";
static String updateLicense = "$ride/license/updateFeedBack.php";
//-----------------RegisrationCar------------------
static String addRegisrationCar = "$ride/RegisrationCar/add.php";
static String getRegisrationCar = "$endPoint/ride/RegisrationCar/get.php";
static String updateRegisrationCar = "$ride/RegisrationCar/update.php";
static String makeDefaultCar = "$ride/RegisrationCar/makeDefaultCar.php";
//-----------------DriverOrder------------------
static String addDriverOrder = "$ride/driver_order/add.php";
static String getDriverOrder = "$ride/driver_order/get.php";
static String getOrderCancelStatus =
"$ride/driver_order/getOrderCancelStatus.php";
static String updateDriverOrder = "$ride/driver_order/update.php";
static String deleteDriverOrder = "$ride/driver_order/delete.php";
// =====================================
static String addRateToPassenger = "$ride/rate/add.php";
static String addRateToDriver = "$ride/rate/addRateToDriver.php";
static String addRateApp = "$ride/rate/add_rate_app.php";
static String sendEmailRateingApp = "$ride/rate/sendEmailRateingApp.php";
static String getDriverRate = "$ride/rate/getDriverRate.php";
static String getPassengerRate = "$ride/rate/getPassengerRate.php";
////////////////emails ============//
static String sendEmailToPassengerForTripDetails =
"$ride/rides/emailToPassengerTripDetail.php";
static String sendEmailToDrivertransaction =
"$server/Admin/sendEmailToDrivertransaction.php";
// ===========================================
static String pathImage = "$server/upload/types/";
static String uploadImage = "$server/uploadImage.php";
static String uploadImage1 = "$server/uploadImage1.php";
static String uploadImagePortrate = "$server/uploadImagePortrate.php";
static String uploadSyrianDocs = "$syria/auth/syria/uploadSyrianDocs.php";
static String uploadImageType = "$server/uploadImageType.php";
//=============egypt documents ==============
static String uploadEgyptidFront =
"$server/EgyptDocuments/uploadEgyptidFront.php";
static String uploadEgypt = "$server/uploadEgypt.php";
static String uploadEgypt1 = "$server/uploadEgypt1.php";
//==================certifcate==========
// static String location = '$endPoint/ride/location';
static String getCarsLocationByPassenger = "$locationServer/get.php";
static String addpassengerLocation =
"$locationServer/addpassengerLocation.php";
static String getLocationAreaLinks =
"$locationServer/get_location_area_links.php";
static String getLatestLocationPassenger =
"$locationServer/getLatestLocationPassenger.php";
static String getFemalDriverLocationByPassenger =
"$locationServer/getFemalDriver.php";
static String getDriverCarsLocationToPassengerAfterApplied =
"$locationServer/getDriverCarsLocationToPassengerAfterApplied.php";
static String addCarsLocationByPassenger = "$locationServer/add.php";
static String saveBehavior = "$locationServer/save_behavior.php";
static String addCarsLocationGizaEndpoint = "$locationServer/add.php";
static String addCarsLocationAlexandriaEndpoint = "$locationServer/add.php";
static String addCarsLocationCairoEndpoint = "$locationServer/add.php";
static String deleteCarsLocationByPassenger = "$locationServer/delete.php";
static String updateCarsLocationByPassenger = "$locationServer/update.php";
static String getTotalDriverDuration =
"$locationServer/getTotalDriverDuration.php";
static String getRidesDriverByDay = "$locationServer/getRidesDriverByDay.php";
static String getTotalDriverDurationToday =
"$locationServer/getTotalDriverDurationToday.php";
//==================get_driver_behavior.php=============
static String get_driver_behavior =
'$ride/driver_behavior/get_driver_behavior.php';
//==================cars new drivers=============
static String addNewCarsDrivers = '$ride/carDrivers/add.php';
static String getNewCarsDrivers = '$ride/carDrivers/get.php';
static String deleteNewCarsDrivers = '$ride/carDrivers/delete.php';
//==================Blog=============
static String profile = '$ride/profile';
static String getprofile = "$profile/get.php";
static String getCaptainProfile = "$profile/getCaptainProfile.php";
static String addprofile = "$profile/add.php";
static String deleteprofile = "$profile/delete.php";
static String updateprofile = "$profile/update.php";
static String updateDriverEmail = "$profile/updateDriverEmail.php";
//===================Auth============
static String addInviteDriver = "$ride/invitor/add.php";
static String addInvitationPassenger =
"$ride/invitor/addInvitationPassenger.php";
static String getInviteDriver = "$ride/invitor/get.php";
static String getDriverInvitationToPassengers =
"$ride/invitor/getDriverInvitationToPassengers.php";
static String updateInviteDriver = "$ride/invitor/update.php";
static String updatePassengerGift = "$ride/invitor/updatePassengerGift.php";
static String updateInvitationCodeFromRegister =
"$ride/invitor/updateInvitationCodeFromRegister.php";
static String register_driver_and_car =
"$auth/syria/driver/register_driver_and_car.php";
static String updateDriverInvitationDirectly =
"$ride/invitor/updateDriverInvitationDirectly.php";
static String updatePassengersInvitation =
"$ride/invitor/updatePassengersInvitation.php";
//===================Auth============
static String auth = '$server/auth';
static String login = "$auth/login.php";
static String signUp = "$auth/signup.php";
static String updateDriverClaim = "$auth/captin/updateDriverClaim.php";
static String updateShamCashDriver = "$auth/captin/updateShamCashDriver.php";
static String sendVerifyEmail = "$auth/sendVerifyEmail.php";
static String passengerRemovedAccountEmail =
"$auth/passengerRemovedAccountEmail.php";
static String verifyEmail = "$auth/verifyEmail.php";
//===================Auth Captin============
static String authCaptin = '$server/auth/captin';
static String loginCaptin = "$authCaptin/login.php";
static String loginFromGoogleCaptin = "$authCaptin/loginFromGoogle.php";
static String loginUsingCredentialsWithoutGoogle =
"$authCaptin/loginUsingCredentialsWithoutGoogle.php";
static String packageInfo = "$server/auth/packageInfo.php";
static String signUpCaptin = "$authCaptin/register.php";
static String addCriminalDocuments = "$authCaptin/addCriminalDocuments.php";
static String sendVerifyEmailCaptin = "$authCaptin/sendVerifyEmail.php";
static String sendVerifyOtpMessage =
"$server/auth/captin/sendOtpMessageDriver.php";
static String verifyOtpMessage = "$server/auth/verifyOtpMessage.php";
static String verifyOtpDriver = "$server/auth/captin/verifyOtpDriver.php";
static String verifyEmailCaptin = "$authCaptin/verifyEmail.php";
static String removeUser = "$authCaptin/removeAccount.php";
static String deletecaptainAccounr = "$authCaptin/deletecaptainAccounr.php";
static String updateAccountBank = "$authCaptin/updateAccountBank.php";
static String getAccount = "$authCaptin/getAccount.php";
static String uploadImageToAi = "$auth/document_syria/ai_document.php";
static String isPhoneVerified = "$auth/syria/driver/isPhoneVerified.php";
//===================Admin Captin============
static String getPassengerDetailsByPassengerID =
"$server/Admin/getPassengerDetailsByPassengerID.php";
static String getPassengerDetails = "$server/Admin/getPassengerDetails.php";
static String getPassengerbyEmail = "$server/Admin/getPassengerbyEmail.php";
static String addAdminUser = "$server/Admin/adminUser/add.php";
static String addError = "$server/Admin/errorApp.php";
static String getAdminUser = "$server/Admin/adminUser/get.php";
static String getCaptainDetailsByEmailOrIDOrPhone =
"$server/Admin/AdminCaptain/getCaptainDetailsByEmailOrIDOrPhone.php";
static String getCaptainDetails = "$server/Admin/AdminCaptain/get.php";
static String getRidesPerMonth =
"$server/Admin/AdminRide/getRidesPerMonth.php";
static String getRidesDetails = "$server/Admin/AdminRide/get.php";
//////////Sms egypt///////////
static String sendSms = "https://sms.kazumi.me/api/sms/send-sms";
static String senddlr = "https://sms.kazumi.me/api/sms/send-dlr";
static String sendvalidity = "https://sms.kazumi.me/api/sms/send-validity";
static String sendmany = "https://sms.kazumi.me/api/sms/send-many";
static String checkCredit = "https://sms.kazumi.me/api/sms/check-credit";
static String checkStatus = "https://sms.kazumi.me/api/sms/check-status";
static String getSender = "$server/auth/sms/getSender.php";
static String updatePhoneInvalidSMS =
"$server/auth/sms/updatePhoneInvalidSMS.php";
//////////////service///////////
static String serviceApp = "$server/serviceapp";
static String getComplaintAllData = "$serviceApp/getComplaintAllData.php";
static String getComplaintAllDataForDriver =
"$serviceApp/getComplaintAllDataForDriver.php";
}

View File

@@ -0,0 +1,31 @@
List<String> syrianDriverMessages = [
// --- أوقات الذروة والطلبات الكتيرة ---
"وقت الذروة: البلد مولّعة مشاوير! 🌃 افتح التطبيق بسرعة وبلّش أرباحك تدبّل.",
"طلبات كتير حواليك: ⚠️ خليك أول كابتن بيلتقط الطلب وخلّي يومك رابح.",
"الناس طالعة من الشغل: 🌇 الكل راجع عالبيت، خليك جاهز لرحلات كتيرة!",
"زحمة المساء: ⏳ الزباين عم تزيد بهالوقت، لا تضيّع الفرصة واشتغل معنا!",
// --- التركيز عالربح والمصاري ---
"ضاعف ربحك: 💰 كل مشوار بيقربك من هدفك اليومي. جاهز تنطلق؟",
"مصاري أكتر: جزدانك ناطر تعبك، كل دقيقة شغل هي ربح مضمون. 💪",
"خليك بالواجهة: الطلب الجاي ممكن يكون إلك، كبسة زر بتجيبلك رزقة اليوم.",
"رزقتك جاهزة: 💸 لا تخلّي الزباين يستنّوا، افتح التطبيق وخليك متأهّب.",
// --- التقدير والشكر ---
"شكراً إلك: 🙏 إنت مو بس سائق، إنت أساس نجاح تطبيق انطلق.",
"نفتخر فيك: 🌟 الكباتن متلك هنن يلي رافعين اسمنا بالعالي.",
"أداء ممتاز: 👍 استمر بنفس الروح الحلوة، زباينك مبسوطين منك.",
"نجاح مشترك: 🗺️ كل توصيلة بتعملها بتكبر فيها شركتنا وانت كمان.",
// --- التحفيز والتشجيع ---
"صباح النشاط: ☀️ بلّش نهارك بطاقة إيجابية وانطلق لتكسب أكتر.",
"كل مشوار فرصة: 🏁 لا توقف، الطريق لإلك والنجاح ناطرك.",
"يوم مربح: 💼 السوق ناشط اليوم، لا تفوّت الفرصة!",
"جاهز للطلب الجاي: 🔔 الزبون الجاي ممكن يكون أوفر ممتاز، خليك مستعد.",
// --- نصايح ومعلومات ---
"نصيحة اليوم: روح صوب الأسواق والمطاعم، الطلب هناك عالي هالفترة. 🏙️",
"حافظ على تقييمك: 😊 الزبون بينجذب للكابتن يلي عنده تقييم عالي وابتسامة.",
"جو ممطر: 🌧️ المطر يعني طلبات أكتر، خليك شغّال بهالوقت!",
"حدث اليوم: 🎆 في فعالية بالبلد، المشاوير كتيرة بهالمنطقة، استغلها!"
];

View File

@@ -0,0 +1,74 @@
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/main.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'colors.dart';
class AppStyle {
static TextStyle get headTitle => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 36,
color: AppColor.accentColor,
fontFamily: box.read(BoxName.lang) == 'ar'
// ?GoogleFonts.markaziText().fontFamily
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get headTitle2 => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get title => TextStyle(
fontWeight: FontWeight.normal,
fontSize: 16,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get subtitle => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle get number => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: AppColor.writeColor,
fontFamily: 'digit');
static BoxDecoration get boxDecoration => BoxDecoration(
boxShadow: [
BoxShadow(
color: AppColor.accentColor.withValues(alpha: 0.3),
blurRadius: 5,
offset: const Offset(2, 4)),
BoxShadow(
color: AppColor.accentColor.withValues(alpha: 0.1),
blurRadius: 5,
offset: const Offset(-2, -2))
],
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30),
));
static BoxDecoration get boxDecoration1 => BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 4)),
BoxShadow(
color: AppColor.primaryColor.withValues(alpha: 0.02),
blurRadius: 5,
offset: const Offset(-2, -2))
],
color: AppColor.secondaryColor,
borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30),
),
);
}

View File

@@ -0,0 +1,19 @@
class TableName {
static const String placesFavorite = "placesFavorite";
static const String recentLocations = "recentLocations";
static const String carLocations = "carLocations";
static const String driverOrdersRefuse = "driverOrdersRefuse";
static const String rideLocation = "rideLocation";
static const String faceDetectTimes = "faceDetectTimes";
static const String behavior = "behavior";
static const String captainNotification = "captainNotification";
static const String applyRideFromOverLay = "applyRideFromOverLay";
}
class Driver {
static const String driverPass = 'MG6DEJZSczBT6Rx0jOlehQ==';
static const String payMobApikey = 'payMobApikey';
static const String initializationVector = 'initializationVector';
static const String keyOfApp = 'keyOfApp';
static const String FCM_PRIVATE_KEY = 'FCM_PRIVATE_KEY';
}

View File

@@ -0,0 +1,34 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class AuthController extends GetxController {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<User?> signInWithApple() async {
try {
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
final oAuthProvider = OAuthProvider('apple.com');
final credential = oAuthProvider.credential(
idToken: appleCredential.identityToken,
accessToken: appleCredential.authorizationCode,
);
UserCredential userCredential =
await _auth.signInWithCredential(credential);
return userCredential.user;
} catch (error) {
return null;
}
}
void signOut() async {
await _auth.signOut();
}
}

View File

@@ -0,0 +1,60 @@
import 'dart:convert';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart';
import '../../../views/home/Captin/history/history_details_page.dart';
import '../../functions/crud.dart';
class HistoryCaptainController extends GetxController {
bool isloading = false;
Map historyData = {};
Map historyDetailsData = {};
late String orderID;
getOrderId(String orderId) {
orderID = orderId;
update();
}
getHistory() async {
isloading = true;
var res = await CRUD().get(
link: AppLink.getDriverOrder,
payload: {'driver_id': box.read(BoxName.driverID)});
if (res != 'failure') {
historyData = jsonDecode(res);
isloading = false;
update();
} else {
Get.defaultDialog(
title: 'No ride yet'.tr,
middleText: '',
barrierDismissible: false,
confirm: MyElevatedButton(
title: 'Back'.tr,
onPressed: () {
Get.back();
Get.back();
}));
}
}
getHistoryDetails(String orderId) async {
isloading = true;
var res = await CRUD()
.get(link: AppLink.getRideOrderID, payload: {'id': (orderId)});
historyDetailsData = jsonDecode(res);
isloading = false;
update();
Get.to(() => HistoryDetailsPage());
}
@override
void onInit() {
getHistory();
super.onInit();
}
}

View File

@@ -0,0 +1,609 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_contacts/contact.dart';
import 'package:flutter_contacts/flutter_contacts.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/controller/home/payment/captain_wallet_controller.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import 'package:share_plus/share_plus.dart';
import '../../firebase/local_notification.dart';
import '../../functions/launch.dart';
import '../../notification/notification_captain_controller.dart';
class InviteController extends GetxController {
final TextEditingController invitePhoneController = TextEditingController();
List driverInvitationData = [];
List driverInvitationDataToPassengers = [];
String? couponCode;
String? driverCouponCode;
// **FIX**: Added the missing 'contacts' and 'contactMaps' definitions.
List<Contact> contacts = [];
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
int selectedTab = 0;
PassengerStats passengerStats = PassengerStats();
void updateSelectedTab(int index) {
selectedTab = index;
update();
}
Future<void> shareDriverCode() async {
if (driverCouponCode != null) {
final String shareText =
'''Join Intaleq as a driver using my referral code!
Use code: $driverCouponCode
Download the Intaleq Driver app now and earn rewards!
''';
await Share.share(shareText);
}
}
Future<void> sharePassengerCode() async {
if (couponCode != null) {
final String shareText = '''Get a discount on your first Intaleq ride!
Use my referral code: $couponCode
Download the Intaleq app now and enjoy your ride!
''';
await Share.share(shareText);
}
}
@override
void onInit() {
super.onInit();
// **MODIFIED**: Sync contacts automatically on controller initialization.
syncContactsToServerOnce();
// fetchDriverStats();
}
// --- NEW LOGIC: ONE-TIME CONTACTS SYNC ---
/// **NEW**: Syncs all phone contacts to the server, but only runs once per user.
Future<void> syncContactsToServerOnce() async {
final String syncFlagKey = 'contactsSynced_${box.read(BoxName.driverID)}';
// 1. Check if contacts have already been synced for this user.
if (box.read(syncFlagKey) == true) {
print("Contacts have already been synced for this user.");
return;
}
try {
// 2. Request permission and fetch all contacts.
if (await FlutterContacts.requestPermission(readonly: true)) {
// mySnackbarSuccess('Starting contacts sync in background...'.tr);
final List<Contact> allContacts =
await FlutterContacts.getContacts(withProperties: true);
// **FIX**: Assign fetched contacts to the class variable.
contacts = allContacts;
contactMaps.value = contacts.map((contact) {
return {
'name': contact.displayName,
'phones':
contact.phones.map((phone) => phone.normalizedNumber).toList(),
'emails': contact.emails.map((email) => email.address).toList(),
};
}).toList();
update();
// 3. Loop through contacts and save them to the server.
for (var contact in allContacts) {
if (contact.phones.isNotEmpty) {
// Use the normalized phone number for consistency.
var phone = contact.phones.first.normalizedNumber;
if (phone.isNotEmpty) {
CRUD().post(link: AppLink.savePhonesSyria, payload: {
"driverId": box.read(BoxName.driverID), // Associate with driver
"name": contact.displayName ?? 'No Name',
"phone": phone,
});
}
}
}
// 4. After a successful sync, set the flag to prevent future syncs.
await box.write(syncFlagKey, true);
// mySnackbarSuccess('Contacts sync completed successfully!'.tr);
}
} catch (e) {
// mySnackeBarError('An error occurred during contact sync: $e'.tr);
}
}
// --- NEW LOGIC: NATIVE CONTACT PICKER ---
/// **MODIFIED**: This function now opens the phone's native contact picker.
Future<void> pickContactFromNativeApp() async {
try {
log('=== START: FETCHING ALL CONTACTS FOR BOTTOM SHEET ===',
name: 'ContactPicker');
if (await FlutterContacts.requestPermission(readonly: true)) {
// عرض شاشة تحميل بسيطة ريثما يتم جلب الأسماء
Get.dialog(const Center(child: CircularProgressIndicator()),
barrierDismissible: false);
log('Permission granted. Calling FlutterContacts.getContacts()...',
name: 'ContactPicker');
// جلب جميع جهات الاتصال إجبارياً من الصفر مع خصائصها
List<Contact> allContacts =
await FlutterContacts.getContacts(withProperties: true);
log('Total Contacts Fetched from Device: ${allContacts.length}',
name: 'ContactPicker');
// فصل الأسماء لمعرفة الخلل
List<Contact> validContacts = [];
List<Contact> invalidContacts = [];
for (var c in allContacts) {
if (c.phones.isNotEmpty) {
validContacts.add(c);
} else {
invalidContacts.add(c);
}
}
log('Contacts WITH phone numbers: ${validContacts.length}',
name: 'ContactPicker');
log('Contacts WITHOUT phone numbers: ${invalidContacts.length}',
name: 'ContactPicker');
// طباعة أول 20 اسم صالح
log('--- Sample of VALID Contacts ---', name: 'ContactPicker');
for (int i = 0; i < validContacts.length && i < 20; i++) {
log('[$i] Name: ${validContacts[i].displayName}, Phone: ${validContacts[i].phones.first.number}',
name: 'ContactPicker');
}
// طباعة أول 20 اسم غير صالح (بدون أرقام) لفحص المشكلة
log('--- Sample of INVALID Contacts (No Phone) ---',
name: 'ContactPicker');
for (int i = 0; i < invalidContacts.length && i < 20; i++) {
log('[$i] Name: ${invalidContacts[i].displayName}',
name: 'ContactPicker');
}
Get.back(); // إغلاق شاشة التحميل
if (validContacts.isEmpty) {
mySnackeBarError('No contacts with phone numbers found'.tr);
return;
}
// متغيرات للبحث داخل القائمة المنسدلة
RxList<Contact> filteredContacts = validContacts.obs;
TextEditingController searchController = TextEditingController();
// دالة لتنظيف النصوص من أي رموز معطوبة
String sanitizeText(String input) {
if (input.isEmpty) return '';
return input
.replaceAll(
RegExp(r'[^\x00-\x7F\u0600-\u06FF\u08A0-\u08FF\p{L}\p{N}\s]'),
'')
.trim();
}
// فتح دليل هاتف مخصص داخل التطبيق
Get.bottomSheet(
Container(
height: Get.height * 0.85,
decoration: BoxDecoration(
color: Theme.of(Get.context!).scaffoldBackgroundColor,
borderRadius:
const BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(top: 10, bottom: 10),
width: 50,
height: 5,
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(10)),
),
Text("Select a Contact".tr,
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold)),
Padding(
padding: const EdgeInsets.all(12.0),
child: TextField(
controller: searchController,
decoration: InputDecoration(
hintText: "Search name or number...".tr,
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)),
contentPadding: const EdgeInsets.symmetric(vertical: 0),
),
onChanged: (value) {
filteredContacts.value = validContacts.where((c) {
final nameMatch = c.displayName
.toLowerCase()
.contains(value.toLowerCase());
final phoneMatch =
c.phones.first.number.contains(value);
return nameMatch || phoneMatch;
}).toList();
},
),
),
Expanded(
child: Obx(() => ListView.builder(
itemCount: filteredContacts.length,
itemBuilder: (context, index) {
Contact c = filteredContacts[index];
var firstPhone = c.phones.first;
String selectedPhone =
firstPhone.normalizedNumber.isNotEmpty
? firstPhone.normalizedNumber
: firstPhone.number;
String safeName = sanitizeText(c.displayName);
if (safeName.isEmpty) safeName = 'Unknown'.tr;
String safePhone = sanitizeText(selectedPhone);
String initial = safeName.isNotEmpty
? safeName[0].toUpperCase()
: '?';
return ListTile(
leading: CircleAvatar(
backgroundColor:
Colors.blueAccent.withOpacity(0.1),
child: Text(initial,
style: const TextStyle(
color: Colors.blueAccent)),
),
title: Text(safeName),
subtitle: Text(safePhone,
textDirection: TextDirection.ltr),
onTap: () {
selectPhone(selectedPhone);
},
);
},
)),
),
],
),
),
isScrollControlled: true,
);
} else {
log('Permission DENIED', name: 'ContactPicker');
mySnackeBarError('Contact permission is required to pick contacts'.tr);
}
} catch (e) {
if (Get.isDialogOpen ?? false) Get.back();
log('CRITICAL ERROR: $e', name: 'ContactPicker');
mySnackeBarError('An error occurred while loading contacts: $e'.tr);
}
log('=== END: FETCHING CONTACTS ===', name: 'ContactPicker');
}
/// **FIX**: Added the missing 'selectPhone' method.
void selectPhone(String phone) {
// Format the selected phone number and update the text field.
invitePhoneController.text = _formatSyrianPhoneNumber(phone);
update();
Get.back(); // Close the contacts dialog after selection.
}
void fetchDriverStats() async {
try {
var response = await CRUD().get(link: AppLink.getInviteDriver, payload: {
"driverId": box.read(BoxName.driverID),
});
if (response != 'failure') {
var data = jsonDecode(response);
driverInvitationData = data['message'];
update();
}
} catch (e) {
// Handle error gracefully
}
}
void fetchDriverStatsPassengers() async {
try {
var response = await CRUD()
.get(link: AppLink.getDriverInvitationToPassengers, payload: {
"driverId": box.read(BoxName.driverID),
});
if (response != 'failure') {
var data = jsonDecode(response);
driverInvitationDataToPassengers = data['message'];
update();
}
} catch (e) {
// Handle error gracefully
}
}
void onSelectDriverInvitation(int index) async {
MyDialog().getDialog(
int.parse((driverInvitationData[index]['countOfInvitDriver'])) < 100
? '${'When'.tr} ${(driverInvitationData[index]['invitorName'])} ${"complete, you can claim your gift".tr} '
: 'You deserve the gift'.tr,
'${(driverInvitationData[index]['invitorName'])} ${(driverInvitationData[index]['countOfInvitDriver'])} / 100 ${'Trip'.tr}',
() async {
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (int.parse((driverInvitationData[index]['countOfInvitDriver'])) <
100) {
Get.back();
} else if (isAvailable) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if ((driverInvitationData[index]['isGiftToken']).toString() ==
'0') {
Get.back();
await CRUD().post(
link: AppLink.updateInviteDriver,
payload: {'id': (driverInvitationData[index]['id'])});
await Get.find<CaptainWalletController>().addDriverPayment(
'paymentMethod',
('500'),
'',
);
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor(
'paymentMethod',
(driverInvitationData[index]['driverInviterId']),
('500'),
);
NotificationCaptainController().addNotificationCaptain(
driverInvitationData[index]['driverInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have 500".tr} ${'SYP'.tr}',
false);
NotificationController().showNotification(
"You have got a gift for invitation".tr,
'${"You have 500".tr} ${'SYP'.tr}',
'tone1',
'');
} else {
Get.back();
MyDialog().getDialog("You have got a gift".tr,
"Share the app with another new driver".tr, () => Get.back());
}
} else {
MyDialog()
.getDialog('Authentication failed'.tr, '', () => Get.back());
}
} else {
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
);
}
void onSelectPassengerInvitation(int index) async {
bool isAvailable = await LocalAuthentication().isDeviceSupported();
MyDialog().getDialog(
int.parse(driverInvitationDataToPassengers[index]['countOfInvitDriver']
.toString()) <
3
? '${'When'.tr} ${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${"complete, you can claim your gift".tr} '
: 'You deserve the gift'.tr,
'${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${driverInvitationDataToPassengers[index]['countOfInvitDriver']} / 3 ${'Trip'.tr}',
() async {
if (int.parse(driverInvitationDataToPassengers[index]
['countOfInvitDriver']
.toString()) <
3) {
Get.back();
} else if (isAvailable) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if (driverInvitationDataToPassengers[index]['isGiftToken']
.toString() ==
'0') {
Get.back();
await Get.find<CaptainWalletController>()
.addDriverWallet('paymentMethod', '200', '200');
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor('paymentMethod',
driverInvitationData[index]['driverInviterId'], '200');
await CRUD().post(
link: AppLink.updatePassengerGift,
payload: {'id': driverInvitationDataToPassengers[index]['id']},
);
NotificationCaptainController().addNotificationCaptain(
driverInvitationDataToPassengers[index]['passengerInviterId']
.toString(),
"You have got a gift for invitation".tr,
'${"You have 200".tr} ${'SYP'.tr}',
false,
);
} else {
Get.back();
MyDialog().getDialog(
"You have got a gift".tr,
"Share the app with another new passenger".tr,
() => Get.back(),
);
}
} else {
MyDialog()
.getDialog('Authentication failed'.tr, '', () => Get.back());
}
} else {
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
);
}
/// Formats a phone number to the standard Syrian international format (+963...).
String _formatSyrianPhoneNumber(String input) {
String digitsOnly = input.replaceAll(RegExp(r'\D'), '');
if (digitsOnly.startsWith('09') && digitsOnly.length == 10) {
return '963${digitsOnly.substring(1)}';
}
if (digitsOnly.length == 9 && digitsOnly.startsWith('9')) {
return '963$digitsOnly';
}
return input; // Fallback for unrecognized formats
}
String normalizeSyrianPhone(String input) {
String phone = input.trim();
// احذف كل شيء غير أرقام
phone = phone.replaceAll(RegExp(r'[^0-9]'), '');
// إذا يبدأ بـ 0 → احذفها
if (phone.startsWith('0')) {
phone = phone.substring(1);
}
// إذا يبدأ بـ 963 مكررة → احذف التكرار
while (phone.startsWith('963963')) {
phone = phone.substring(3);
}
// إذا يبدأ بـ 963 ولكن داخله كمان 963 → خليه مرة واحدة فقط
if (phone.startsWith('963') && phone.length > 12) {
phone = phone.substring(phone.length - 9); // آخر 9 أرقام
}
// الآن إذا كان بلا 963 → أضفها
if (!phone.startsWith('963')) {
phone = '963' + phone;
}
return phone;
}
/// Sends an invitation to a potential new driver.
void sendInvite() async {
if (invitePhoneController.text.isEmpty) {
mySnackeBarError('Please enter a phone number'.tr);
return;
}
// Format Syrian phone number: remove leading 0 and add +963
String formattedPhoneNumber =
normalizeSyrianPhone(invitePhoneController.text);
if (formattedPhoneNumber.length != 12) {
mySnackeBarError('Please enter a correct phone'.tr);
return;
}
var response = await CRUD().post(link: AppLink.addInviteDriver, payload: {
"driverId": box.read(BoxName.driverID),
"inviterDriverPhone": formattedPhoneNumber,
});
if (response != 'failure') {
var d = (response);
mySnackbarSuccess('Invite sent successfully'.tr);
String message = '${'*Intaleq DRIVER CODE*'.tr}\n\n'
'${"Use this code in registration".tr}\n'
'${"To get a gift for both".tr}\n\n'
'${"The period of this code is 24 hours".tr}\n\n'
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
'_*${d['message']['inviteCode'].toString()}*_\n\n'
'${"Install our app:".tr}\n'
'*Android:* https://play.google.com/store/apps/details?id=com.intaleq_driver \n\n\n'
'*iOS:* https://apps.apple.com/st/app/intaleq-driver/id6482995159';
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
} else {
mySnackeBarError("Invite code already used".tr);
}
}
/// Sends an invitation to a potential new passenger.
void sendInviteToPassenger() async {
if (invitePhoneController.text.isEmpty) {
mySnackeBarError('Please enter a phone number'.tr);
return;
}
// Format Syrian phone number: remove leading 0 and add +963
String formattedPhoneNumber = invitePhoneController.text.trim();
if (formattedPhoneNumber.startsWith('0')) {
formattedPhoneNumber = formattedPhoneNumber.substring(1);
}
formattedPhoneNumber = '+963$formattedPhoneNumber';
if (formattedPhoneNumber.length < 12) {
// +963 + 9 digits = 12+
mySnackeBarError('Please enter a correct phone'.tr);
return;
}
var response = await CRUD().post(
link: AppLink.addInvitationPassenger,
payload: {
"driverId": box.read(BoxName.driverID),
"inviterPassengerPhone": formattedPhoneNumber,
},
);
if (response != 'failure') {
var d = response;
mySnackbarSuccess('Invite sent successfully'.tr);
String message = '${'*Intaleq APP CODE*'.tr}\n\n'
'${"Use this code in registration".tr}\n\n'
'${"To get a gift for both".tr}\n\n'
'${"The period of this code is 24 hours".tr}\n\n'
'${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n'
'_*${d['message']['inviteCode'].toString()}*_\n\n'
'${"Install our app:".tr}\n'
'*Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n\n\n'
'*iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179';
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
} else {
mySnackeBarError("Invite code already used".tr);
}
}
}
class PassengerStats {
final int totalInvites;
final int activeUsers;
final double totalEarnings;
PassengerStats({
this.totalInvites = 0,
this.activeUsers = 0,
this.totalEarnings = 0.0,
});
}

View File

@@ -0,0 +1,696 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:permission_handler/permission_handler.dart';
import 'package:siro_driver/views/auth/captin/cards/sms_signup.dart';
import 'package:siro_driver/views/auth/syria/registration_view.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/home/Captin/home_captain/home_captin.dart';
import 'package:location/location.dart';
import '../../../constant/api_key.dart';
import '../../../constant/info.dart';
import '../../../print.dart';
import '../../../views/auth/captin/otp_page.dart';
import '../../../views/auth/captin/otp_token_page.dart';
import '../../../views/auth/syria/pending_driver_page.dart';
import '../../firebase/firbase_messge.dart';
import '../../firebase/local_notification.dart';
import '../../firebase/notification_service.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
import '../../functions/secure_storage.dart';
import '../../functions/security_checks.dart';
class LoginDriverController extends GetxController {
final formKey = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController passwordController2 = TextEditingController();
bool isAgreeTerms = false;
bool isGoogleDashOpen = false;
bool isGoogleLogin = false;
bool isloading = false;
late int isTest = 1;
final FlutterSecureStorage _storage = const FlutterSecureStorage();
final location = Location();
void changeAgreeTerm() {
isAgreeTerms = !isAgreeTerms;
update();
}
bool isPasswordHidden = true;
void togglePasswordVisibility() {
isPasswordHidden = !isPasswordHidden;
update([
'passwordVisibility'
]); // Use a unique ID to only update the password field
}
void changeGoogleDashOpen() {
isGoogleDashOpen = !isGoogleDashOpen;
update();
}
@override
void onInit() async {
box.write(BoxName.countryCode, 'Syria');
// box.write(BoxName.driverID, '34feffd3fa72d6bee56b');
// await getAppTester();
getJWT();
super.onInit();
}
getAppTester() async {
var res = await CRUD().get(
link: AppLink.getTesterApp,
payload: {'appPlatform': AppInformation.appName});
// Log.print('res: ${res}');
if (res != 'failure') {
var d = jsonDecode(res);
isTest = d['message'][0]['isTest'];
// Log.print('isTest: ${isTest}');
box.write(BoxName.isTest, isTest);
// Log.print('isTest: ${box.read(BoxName.isTest)}');
update();
} else {
isTest = 0;
box.write(BoxName.isTest, isTest);
update();
return false;
}
}
updateAppTester(String appPlatform) async {
await CRUD().post(
link: AppLink.updateTesterApp, payload: {'appPlatform': appPlatform});
}
isPhoneVerified() async {
var res = await CRUD().post(
link: AppLink.isPhoneVerified,
payload: {'phone_number': box.read(BoxName.phoneDriver)});
if (res != 'failure') {
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
// isloading = false;
// update();
} else {
Get.offAll(() => PhoneNumberScreen());
}
}
void saveAgreementTerms() {
box.write(BoxName.agreeTerms, 'agreed');
update();
}
void saveCountryCode(String countryCode) {
box.write(BoxName.countryCode, countryCode);
update();
}
String shortHash(String password) {
var bytes = utf8.encode(password);
var digest = sha256.convert(bytes);
return base64UrlEncode(digest.bytes);
}
var dev = '';
getJwtWallet() async {
if (box.read(BoxName.security_check).toString() != 'passed') {
Log.print('Security check failed');
return;
}
Log.print('Security check passed');
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
dev = Platform.isAndroid ? 'android' : 'ios';
var payload = {
'id': box.read(BoxName.driverID),
'password': AK.passnpassenger,
'aud': '${AK.allowedWallet}$dev',
'fingerPrint': fingerPrint
};
var response1 = await http.post(
Uri.parse(AppLink.loginJwtWalletDriver),
body: payload,
);
Log.print('response.request: ${response1.request}');
Log.print('response.body: ${response1.body}');
var decoded = jsonDecode(response1.body);
var jwt = decoded['message'] is Map && decoded['message']['jwt'] != null ? decoded['message']['jwt'] : decoded['jwt'];
var hmac = decoded['message'] is Map && decoded['message']['hmac'] != null ? decoded['message']['hmac'] : decoded['hmac'];
Log.print('payment["jwt"]: $jwt');
await box.write(BoxName.hmac, hmac);
return jwt.toString();
}
getJWT() async {
await EncryptionHelper.initialize();
dev = Platform.isAndroid ? 'android' : 'ios';
Log.print(
'box.read(BoxName.firstTimeLoadKey): ${box.read(BoxName.firstTimeLoadKey)}');
if (box.read(BoxName.firstTimeLoadKey).toString() != 'false') {
var payload = {
'id': box.read(BoxName.driverID) ?? AK.newId,
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
'fingerPrint': box.read(BoxName.deviceFingerprint) ??
await DeviceHelper.getDeviceFingerprint(),
};
// Log.print('payload: ${payload}');
var response0 = await http.post(
Uri.parse(AppLink.loginFirstTimeDriver),
body: payload,
);
Log.print('response0: ${response0.body}');
Log.print('request: ${response0.request}');
if (response0.statusCode == 200) {
final decodedResponse1 = jsonDecode(response0.body);
Log.print('decodedResponse1: ${decodedResponse1}');
String? jwt;
if (decodedResponse1['message'] is Map && decodedResponse1['message']['jwt'] != null) {
jwt = decodedResponse1['message']['jwt'];
} else {
jwt = decodedResponse1['jwt'];
}
if (jwt != null) {
box.write(BoxName.jwt, c(jwt));
}
// ✅ بعد التأكد أن كل المفاتيح موجودة
await EncryptionHelper.initialize();
// await AppInitializer().getKey();
} else {}
} else {
await EncryptionHelper.initialize();
var payload = {
'id': box.read(BoxName.driverID),
'password': box.read(BoxName.emailDriver),
'aud': '${AK.allowed}$dev',
'fingerPrint': box.read(BoxName.deviceFingerprint) ??
await DeviceHelper.getDeviceFingerprint(),
};
// print(payload);
var response1 = await http.post(
Uri.parse(AppLink.loginJwtDriver),
body: payload,
);
Log.print('response1.request: ${response1.request}');
Log.print('response1.body: ${response1.body}');
if (response1.statusCode == 200) {
final decodedResponse1 = jsonDecode(response1.body);
// Log.print('decodedResponse1: ${decodedResponse1}');
String? jwt;
if (decodedResponse1['message'] is Map && decodedResponse1['message']['jwt'] != null) {
jwt = decodedResponse1['message']['jwt'];
} else {
jwt = decodedResponse1['jwt'];
}
if (jwt != null) {
await box.write(BoxName.jwt, c(jwt));
}
// await AppInitializer().getKey();
}
}
}
Future<void> getLocationPermission() async {
var status = await Permission.locationAlways.status;
if (!status.isGranted) {
await Permission.locationAlways.request();
}
update();
}
String generateUniqueIdFromEmail(String email) {
// Step 1: Extract the local part of the email
String localPart = email.split('@')[0];
// Step 2: Replace invalid characters (if any)
String cleanLocalPart = localPart.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '');
// Step 3: Ensure it does not exceed 24 characters
if (cleanLocalPart.length > 24) {
cleanLocalPart = cleanLocalPart.substring(0, 24);
}
// Step 4: Generate a random suffix if needed
String suffix = generateRandomSuffix(24 - cleanLocalPart.length);
return cleanLocalPart + suffix;
}
String generateRandomSuffix(int length) {
const String chars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
Random random = Random();
return List.generate(length, (index) => chars[random.nextInt(chars.length)])
.join('');
}
bool isInviteDriverFound = false;
Future updateInvitationCodeFromRegister() async {
var res = await CRUD().post(
link: AppLink.updateDriverInvitationDirectly,
payload: {
"inviterDriverPhone": box.read(BoxName.phoneDriver).toString(),
},
);
Log.print('invite: ${res}');
// حماية من النوع — res قد يكون String ('failure'/'token_expired') بدل Map
if (res is! Map) return;
if (res['status'] != 'failure') {
isInviteDriverFound = true;
update();
box.write(BoxName.isInstall, '1');
NotificationController().showNotification(
"Code approved".tr, "Code approved".tr, 'tone2', '');
try {
NotificationService.sendNotification(
target: (res)['message'][0]['token'].toString(),
title: 'You have received a gift token!'.tr,
body: 'for '.tr + box.read(BoxName.phoneDriver).toString(),
isTopic: false,
tone: 'tone2',
driverList: [], category: 'You have received a gift token!',
);
} catch (e) {
Log.print('invite notification error: $e');
}
}
}
loginWithGoogleCredential(String driverID, email) async {
isloading = true;
update();
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
// await getJWT();
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
// 'email': email ?? 'yet',
'id': driverID,
});
Log.print('loginWithGoogleCredential: ${res}');
if (res == 'failure') {
await isPhoneVerified();
isloading = false; // <--- أضفت هذا أيضاً
update();
return false;
// Get.snackbar('Failure', '', backgroundColor: Colors.red);
} else {
var jsonDecoeded = jsonDecode(res);
var d = jsonDecoeded['data'][0];
if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success' &&
d['is_verified'].toString() == '1') {
box.write(BoxName.emailDriver, d['email']);
box.write(BoxName.firstTimeLoadKey, 'false');
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');
box.write(BoxName.gender, (d['gender']));
box.write(BoxName.phoneVerified, d['is_verified'].toString());
box.write(BoxName.phoneDriver, (d['phone']));
box.write(BoxName.is_claimed, d['is_claimed']);
box.write(BoxName.isInstall, d['isInstall']);
// box.write(
// BoxName.isGiftToken, d['isGiftToken']);
box.write(BoxName.nameArabic, (d['name_arabic']));
box.write(BoxName.carYear, d['year']);
box.write(BoxName.bankCodeDriver, (d['bankCode']));
box.write(BoxName.accountBankNumberDriver, (d['accountBank']));
box.write(
BoxName.nameDriver,
'${(d['first_name'])}'
' ${(d['last_name'])}');
if (((d['model']).toString().contains('دراجه') ||
d['make'].toString().contains('دراجه '))) {
if ((d['gender']).toString() == 'Male') {
box.write(BoxName.carTypeOfDriver, 'Scooter');
} else {
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
}
} else if (int.parse(d['year'].toString()) > 2016) {
if (d['gender'].toString() != 'Male') {
box.write(BoxName.carTypeOfDriver, 'Lady');
} else {
box.write(BoxName.carTypeOfDriver, 'Comfort');
}
} else if (int.parse(d['year'].toString()) > 2002 &&
int.parse(d['year'].toString()) < 2016) {
box.write(BoxName.carTypeOfDriver, 'Speed');
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
// ✅ الحصول على توكن access بدل registration قبل أي طلبات بعد تسجيل الدخول
Log.print('🔑 Getting access token after login...');
await getJWT();
Log.print('🔑 Access token obtained.');
// add invitations
if (box.read(BoxName.isInstall) == null ||
box.read(BoxName.isInstall).toString() == '0') {
updateInvitationCodeFromRegister();
}
// updateAppTester(AppInformation.appName);
if (d['status'].toString() != 'yet') {
var token = await CRUD().get(
link: AppLink.getDriverToken,
payload: {
'captain_id': (box.read(BoxName.driverID)).toString()
});
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(
key: BoxName.fingerPrint, value: fingerPrint.toString());
// print(jsonDecode(token)['data'][0]['token'].toString());
// print(box.read(BoxName.tokenDriver).toString());
// if (box.read(BoxName.emailDriver).toString() !=
// '963992952235@intaleqapp.com') {
if (token != 'failure') {
var serverData = jsonDecode(token);
if ((serverData['data'][0]['token'].toString()) !=
box.read(BoxName.tokenDriver).toString() ||
serverData['data'][0]['fingerPrint'].toString() !=
fingerPrint.toString()) {
Get.defaultDialog(
barrierDismissible: false,
title: 'Device Change Detected'.tr,
middleText: 'Please verify your identity'.tr,
textConfirm: 'Verify'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
// نغلق الـ Dialog أولاً بشكل صريح
if (Get.isDialogOpen ?? false) {
Get.back();
}
// ثم ننتقل لصفحة OTP
Get.offAll(
() => OtpVerificationPage(
phone: d['phone'].toString(),
deviceToken: fingerPrint.toString(),
token: token.toString(),
ptoken:
jsonDecode(token)['data'][0]['token'].toString(),
),
);
},
);
isloading = false;
update();
return true; // نخرج من الدالة هنا لنسمح لـ OTP بالتعامل مع الأمر
}
// }
}
Get.offAll(() => HomeCaptain()); // افترض أن هذا الكلاس موجود
isloading = false; // <--- أضفت هذا
update(); // <--- أضفت هذا
return true;
} else {
Get.offAll(
() => DriverVerificationScreen()); // افترض أن هذا الكلاس موجود
isloading = false; // <--- أضفت هذا
update(); // <--- أضفت هذا
return false;
}
// Get.off(() => HomeCaptain());
} else {
Get.offAll(() => PhoneNumberScreen());
isloading = false;
update();
return false; // <--- ✅ وهذا السطر موجود للحالات الأخرى
}
} else {
mySnackbarSuccess('');
isloading = false;
update();
}
}
}
logintest(String driverID, email) async {
isloading = true;
update();
// await SecurityHelper.performSecurityChecks();
// Log.print('(BoxName.emailDriver): ${box.read(BoxName.emailDriver)}');
var res = await CRUD().get(link: AppLink.loginFromGoogleCaptin, payload: {
'email': email ?? 'yet',
'id': driverID,
});
// print('res is $res');
// if (res == 'failure') {
// await isPhoneVerified();
// // Get.snackbar('Failure', '', backgroundColor: Colors.red);
// } else
// {
var jsonDecoeded = jsonDecode(res);
var d = jsonDecoeded['data'][0];
if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success')
// &&
// d['is_verified'].toString() == '1')
{
box.write(BoxName.emailDriver, d['email']);
box.write(BoxName.firstTimeLoadKey, 'false');
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');
box.write(BoxName.gender, (d['gender']));
box.write(BoxName.phoneVerified, d['is_verified'].toString());
box.write(BoxName.phoneDriver, (d['phone']));
box.write(BoxName.is_claimed, d['is_claimed']);
box.write(BoxName.isInstall, d['isInstall']);
// box.write(
// BoxName.isGiftToken, d['isGiftToken']);
box.write(BoxName.nameArabic, (d['name_arabic']));
box.write(BoxName.carYear, d['year']);
box.write(BoxName.bankCodeDriver, (d['bankCode']));
box.write(BoxName.accountBankNumberDriver, (d['accountBank']));
box.write(
BoxName.nameDriver,
'${(d['first_name'])}'
' ${(d['last_name'])}');
if (((d['model']).toString().contains('دراجه') ||
d['make'].toString().contains('دراجه '))) {
if ((d['gender']).toString() == 'Male') {
box.write(BoxName.carTypeOfDriver, 'Scooter');
} else {
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
}
} else if (int.parse(d['year'].toString()) > 2016) {
if (d['gender'].toString() != 'Male') {
box.write(BoxName.carTypeOfDriver, 'Lady');
} else {
box.write(BoxName.carTypeOfDriver, 'Comfort');
}
} else if (int.parse(d['year'].toString()) > 2002 &&
int.parse(d['year'].toString()) < 2016) {
box.write(BoxName.carTypeOfDriver, 'Speed');
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
// updateAppTester(AppInformation.appName);
// var token = await CRUD().get(
// link: AppLink.getDriverToken,
// payload: {'captain_id': (box.read(BoxName.driverID)).toString()});
// String fingerPrint = await DeviceHelper.getDeviceFingerprint();
// await storage.write(
// key: BoxName.fingerPrint, value: fingerPrint.toString());
Get.off(() => HomeCaptain());
// } else {
// Get.offAll(() => PhoneNumberScreen());
// isloading = false;
// update();
// }
// }
// else {
// mySnackbarSuccess('');
// isloading = false;
// update();
}
}
}
loginUsingCredentialsWithoutGoogle(String password, email) async {
isloading = true;
isGoogleLogin = true;
update();
var res = await CRUD()
.get(link: AppLink.loginUsingCredentialsWithoutGoogle, payload: {
'email': (email),
'password': password,
});
box.write(BoxName.emailDriver, (email).toString());
// print(res);
if (res == 'failure') {
//Failure
if (box.read(BoxName.phoneVerified).toString() == '1') {
// Get.offAll(() => SyrianCardAI());
Get.offAll(() => RegistrationView());
} else {
Get.offAll(() => SmsSignupEgypt());
}
isloading = false;
update();
} else {
var jsonDecoeded = jsonDecode(res);
var d = jsonDecoeded['data'][0];
if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success' &&
d['is_verified'].toString() == '1') {
box.write(BoxName.emailDriver, (d['email']));
box.write(BoxName.driverID, (d['id']));
box.write(BoxName.isTest, '1');
box.write(BoxName.gender, (d['gender']));
box.write(BoxName.phoneVerified, d['is_verified'].toString());
box.write(BoxName.phoneDriver, (d['phone']));
box.write(BoxName.nameArabic, (d['name_arabic']));
box.write(BoxName.bankCodeDriver, (d['bankCode']));
box.write(BoxName.accountBankNumberDriver, d['accountBank']);
box.write(
BoxName.nameDriver,
'${(d['first_name'])}'
' ${(d['last_name'])}');
if ((d['model'].toString().contains('دراجه') ||
d['make'].toString().contains('دراجه '))) {
if ((d['gender']).toString() == 'Male') {
box.write(BoxName.carTypeOfDriver, 'Scooter');
} else {
box.write(BoxName.carTypeOfDriver, 'Pink Bike');
}
} else if (int.parse(d['year'].toString()) > 2017) {
if ((d['gender']).toString() != 'Male') {
box.write(BoxName.carTypeOfDriver, 'Lady');
} else {
box.write(BoxName.carTypeOfDriver, 'Comfort');
}
} else if (int.parse(d['year'].toString()) > 2002 &&
int.parse(d['year'].toString()) < 2017) {
box.write(BoxName.carTypeOfDriver, 'Speed');
} else if (int.parse(d['year'].toString()) < 2002) {
box.write(BoxName.carTypeOfDriver, 'Awfar Car');
}
updateAppTester(AppInformation.appName);
var fingerPrint = await DeviceHelper.getDeviceFingerprint();
await storage.write(key: BoxName.fingerPrint, value: fingerPrint);
var token = await CRUD().get(
link: AppLink.getDriverToken,
payload: {'captain_id': box.read(BoxName.driverID).toString()});
if (token != 'failure') {
if ((jsonDecode(token)['data'][0]['token']) !=
(box.read(BoxName.tokenDriver))) {
// Get.put(FirebaseMessagesController()).sendNotificationToDriverMAP(
// 'token change'.tr,
// 'change device'.tr,
// (jsonDecode(token)['data'][0]['token']).toString(),
// [],
// 'ding.wav');
NotificationService.sendNotification(
target: (jsonDecode(token)['data'][0]['token']).toString(),
title: 'token change'.tr,
body: 'token change'.tr,
isTopic: false, // Important: this is a token
tone: 'cancel',
driverList: [], category: 'token change',
);
Get.defaultDialog(
title: 'you will use this device?'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () async {
await CRUD()
.post(link: AppLink.addTokensDriver, payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id': box.read(BoxName.driverID).toString(),
'fingerPrint': (fingerPrint).toString()
});
Get.back();
}));
}
}
Get.off(() => HomeCaptain());
// Get.off(() => LoginCaptin());
} else {
Get.offAll(() => SmsSignupEgypt());
isloading = false;
update();
}
} else {
mySnackeBarError('');
isloading = false;
update();
}
}
}
void loginByBoxData() async {
Get.to(() => HomeCaptain());
await CRUD().post(link: AppLink.addTokensDriver, payload: {
'token': box.read(BoxName.tokenDriver).toString(),
'captain_id': box.read(BoxName.driverID).toString()
});
CRUD().post(
link: "${AppLink.seferAlexandriaServer}/ride/firebase/addDriver.php",
payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id': box.read(BoxName.driverID).toString()
});
CRUD().post(
link: "${AppLink.seferGizaServer}/ride/firebase/addDriver.php",
payload: {
'token': box.read(BoxName.tokenDriver),
'captain_id': box.read(BoxName.driverID).toString()
});
}
}

View File

@@ -0,0 +1,93 @@
import 'dart:io';
import 'package:get/get.dart';
// import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/controller/functions/llama_ai.dart';
// class CarRegistrationRecognizerController extends GetxController {
// @override
// void onInit() {
// // scanText();
// super.onInit();
// }
// // The ImagePicker instance
// final ImagePicker _imagePicker = ImagePicker();
// // The GoogleMlKit TextRecognizer instance
// // final TextRecognizer _textRecognizer = TextRecognizer();
// // The scanned text
// String? scannedText;
// String? jsonOutput;
// final List<Map<String, dynamic>> lines = [];
// Map extracted = {};
// XFile? image;
// CroppedFile? croppedFile;
// // Picks an image from the camera or gallery and extracts the text
// final List<Map<String, dynamic>> extractedTextWithCoordinates = [];
// Future<void> scanText() async {
// // Pick an image from the camera or gallery
// image = await _imagePicker.pickImage(source: ImageSource.gallery);
// update();
// // If no image was picked, return
// if (image == null) {
// return;
// }
// // Crop the image
// croppedFile = await ImageCropper().cropImage(
// sourcePath: image!.path,
// //
// uiSettings: [
// AndroidUiSettings(
// toolbarTitle: 'Cropper'.tr,
// toolbarColor: AppColor.blueColor,
// toolbarWidgetColor: AppColor.yellowColor,
// initAspectRatio: CropAspectRatioPreset.original,
// lockAspectRatio: false),
// IOSUiSettings(
// title: 'Cropper'.tr,
// ),
// ],
// );
// // If no cropped image was obtained, return
// if (croppedFile == null) {
// return;
// }
// // Convert the cropped file to an InputImage object
// final InputImage inputImage = InputImage.fromFile(File(croppedFile!.path));
// // Recognize the text in the image
// final RecognizedText recognizedText =
// await _textRecognizer.processImage(inputImage);
// scannedText = recognizedText.text;
// // Extract the scanned text line by line
// final List<Map<String, dynamic>> lines = [];
// for (var i = 0; i < recognizedText.blocks.length; i++) {
// lines.add({
// i.toString(): recognizedText.blocks[i].text,
// });
// }
// String result = lines.map((map) => map.values.first.toString()).join(' ');
// if (result.length > 2200) {
// result = result.substring(0, 2200);
// }
// Map result2 = await LlamaAi().getCarRegistrationData(result,
// 'vin,make,made,year,expiration_date,color,owner,registration_date'); //
// // Assign the result to the extracted variable
// extracted = result2;
// update();
// }
// }

View File

@@ -0,0 +1,126 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:siro_driver/print.dart';
import 'package:siro_driver/views/home/Captin/home_captain/home_captin.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../firebase/firbase_messge.dart';
import '../../firebase/notification_service.dart';
import '../../functions/crud.dart';
class OtpVerificationController extends GetxController {
final String phone;
final String deviceToken;
final String token;
final otpCode = ''.obs;
final isLoading = false.obs;
final isVerifying = false.obs;
var canResend = false.obs;
var countdown = 120.obs;
Timer? _timer;
OtpVerificationController({
required this.phone,
required this.deviceToken,
required this.token,
});
@override
void onInit() {
super.onInit();
sendOtp(); // ترسل تلقائيًا عند فتح الصفحة
startCountdown();
}
void startCountdown() {
canResend.value = false;
countdown.value = 120;
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (countdown.value > 0) {
countdown.value--;
} else {
canResend.value = true;
timer.cancel();
}
});
}
Future<void> sendOtp() async {
isLoading.value = true;
try {
final response = await CRUD().post(
link:
'${AppLink.server}/auth/token_passenger/driver/send_otp_driver.php',
payload: {
'receiver': phone,
// 'device_token': deviceToken,
},
);
if (response != 'failure') {
// بإمكانك عرض رسالة نجاح هنا
} else {
// Get.snackbar('Error', 'Failed to send OTP');
}
} catch (e) {
Get.snackbar('Error', e.toString());
} finally {
isLoading.value = false;
}
}
Future<void> verifyOtp(String ptoken) async {
isVerifying.value = true;
var finger = box.read(BoxName.deviceFingerprint);
try {
final response = await CRUD().post(
link:
'${AppLink.server}/auth/token_passenger/driver/verify_otp_driver.php',
payload: {
'phone_number': phone,
'otp': otpCode.value,
'token': box.read(BoxName.tokenDriver).toString(),
'fingerPrint': finger.toString(),
},
);
if (response != 'failure' &&
response != 'token_expired' &&
response != 'no_internet') {
Log.print('response (already decoded): ${response}');
// توجه إلى الصفحة التالية
await CRUD().post(
link: '${AppLink.paymentServer}/auth/token/update_driver_auth.php',
payload: {
'token': box.read(BoxName.tokenDriver).toString(),
'fingerPrint': finger.toString(),
'captain_id': box.read(BoxName.driverID).toString(),
});
await NotificationService.sendNotification(
target: ptoken.toString(),
title: 'token change'.tr,
body: 'token change'.tr,
isTopic: false,
tone: 'cancel',
driverList: [],
category: 'token change',
);
Get.offAll(() => HomeCaptain());
} else {
mySnackeBarError('OTP is incorrect or expired'.tr);
}
} catch (e) {
mySnackeBarError(e.toString());
} finally {
isVerifying.value = false;
}
}
}

View File

@@ -0,0 +1,204 @@
import 'package:get/get.dart';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/print.dart';
import 'package:siro_driver/views/home/on_boarding_page.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../main.dart';
import '../../../views/auth/syria/registration_view.dart';
// --- Helper Class for Phone Authentication ---
class PhoneAuthHelper {
// Define your server URLs
static final String _baseUrl = '${AppLink.server}/auth/syria/driver/';
static final String _sendOtpUrl = '${_baseUrl}sendWhatsAppDriver.php';
static final String _verifyOtpUrl = '${_baseUrl}verifyOtp.php';
static final String _registerUrl = '${_baseUrl}register_driver.php';
static String formatSyrianPhone(String phone) {
// Remove spaces, symbols, +, -, ()
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
// Normalize 00963 → 963
if (phone.startsWith('00963')) {
phone = phone.replaceFirst('00963', '963');
}
// Normalize 0963 → 963
if (phone.startsWith('0963')) {
phone = phone.replaceFirst('0963', '963');
}
if (phone.startsWith('096309')) {
phone = phone.replaceFirst('096309', '963');
}
// NEW: Fix 96309xxxx → 9639xxxx
if (phone.startsWith('96309')) {
phone = '9639' + phone.substring(5); // remove the "0" after 963
}
// If starts with 9630 → correct to 9639
if (phone.startsWith('9630')) {
phone = '9639' + phone.substring(4);
}
// If already in correct format: 9639xxxxxxxx
if (phone.startsWith('9639') && phone.length == 12) {
return phone;
}
// If starts with 963 but missing the 9
if (phone.startsWith('963') && phone.length > 3) {
// Ensure it begins with 9639
if (!phone.startsWith('9639')) {
phone = '9639' + phone.substring(3);
}
return phone;
}
// If starts with 09xxxxxxxx → 9639xxxxxxxx
if (phone.startsWith('09')) {
return '963' + phone.substring(1);
}
// If 9xxxxxxxx (9 digits)
if (phone.startsWith('9') && phone.length == 9) {
return '963' + phone;
}
// If starts with incorrect 0xxxxxxx → assume Syrian and fix
if (phone.startsWith('0') && phone.length == 10) {
return '963' + phone.substring(1);
}
return phone;
}
/// Sends an OTP to the provided phone number.
static Future<bool> sendOtp(String phoneNumber) async {
try {
final fixedPhone = formatSyrianPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post(
link: _sendOtpUrl,
payload: {'receiver': fixedPhone},
);
Log.print('fixedPhone: ${fixedPhone}');
if (response != 'failure') {
final data = (response);
Log.print('data: ${data}');
// if (data['status'] == 'success') {
mySnackbarSuccess('An OTP has been sent to your number.'.tr);
return true;
// } else {
// mySnackeBarError(data['message'] ?? 'Failed to send OTP.');
// return false;
// }
} else {
mySnackeBarError('Server error. Please try again.'.tr);
return false;
}
} catch (e) {
// mySnackeBarError('An error occurred: $e');
return false;
}
}
/// Verifies the OTP and logs the user in.
static Future<void> verifyOtp(String phoneNumber, String otpCode) async {
try {
final fixedPhone = formatSyrianPhone(phoneNumber);
Log.print('fixedPhone: $fixedPhone');
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {
'phone_number': fixedPhone,
'otp': otpCode,
},
);
if (response != 'failure') {
final data = response;
if (data['status'] == 'success') {
final isRegistered = data['message']['isRegistered'] ?? false;
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phoneDriver, phoneNumber);
box.write(BoxName.driverID, data['message']['driverID']);
if (isRegistered) {
// ✅ السائق مسجل مسبقًا - سجل دخوله واذهب إلى الصفحة الرئيسية
final driver = data['message']['driver'];
// mySnackbarSuccess('Welcome back, ${driver['first_name']}!');
// حفظ بيانات السائق إذا أردت:
box.write(BoxName.driverID, driver['id']);
box.write(BoxName.emailDriver, driver['email']);
await Get.find<LoginDriverController>().loginWithGoogleCredential(
driver['id'].toString(), driver['email'].toString());
} else {
// ✅ رقم الهاتف تم التحقق منه لكن السائق غير مسجل
// mySnackbarSuccess('Phone verified. Please complete registration.');
// Get.offAll(() => SyrianCardAI());
Get.to(() => RegistrationView());
}
} else {
mySnackeBarError(data['message'] ?? 'Verification failed.');
}
} else {
mySnackeBarError('Server error. Please try again.');
}
} catch (e) {
mySnackeBarError('An error occurred: $e');
}
}
static Future<void> registerUser({
required String phoneNumber,
required String firstName,
required String lastName,
String? email,
}) async {
try {
final response = await CRUD().post(
link: _registerUrl,
payload: {
'phone_number': phoneNumber,
'first_name': firstName,
'last_name': lastName,
'email': email ?? '', // Send empty string if null
},
);
final data = (response);
if (data != 'failure') {
// Registration successful, log user in
await _handleSuccessfulLogin(data['message']);
} else {
mySnackeBarError(
"User with this phone number or email already exists.".tr);
}
} catch (e) {
mySnackeBarError('An error occurred: $e');
}
}
static Future<void> _handleSuccessfulLogin(
Map<String, dynamic> userData) async {
mySnackbarSuccess('Welcome, ${userData['first_name']}!');
// Save user data to local storage (Hive box) using new keys
box.write(BoxName.passengerID, userData['id']);
box.write(BoxName.nameDriver, userData['first_name']);
box.write(BoxName.lastNameDriver, userData['last_name']);
box.write(BoxName.emailDriver, userData['email']);
box.write(BoxName.phoneDriver, userData['phone']);
Get.offAll(() => OnBoardingPage()); // Navigate to home
}
}

View File

@@ -0,0 +1,418 @@
import 'dart:convert';
import 'dart:math';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/controller/functions/ocr_controller.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/auth/captin/login_captin.dart';
import 'package:siro_driver/views/auth/captin/verify_email_captain.dart';
import '../../../constant/colors.dart';
import '../../../views/auth/captin/ai_page.dart';
import '../../../views/auth/syria/registration_view.dart';
import '../../../views/home/Captin/home_captain/home_captin.dart';
import '../../functions/sms_egypt_controller.dart';
class RegisterCaptainController extends GetxController {
final formKey = GlobalKey<FormState>();
final formKey3 = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController verifyCode = TextEditingController();
String birthDate = 'Birth Date'.tr;
String gender = 'Male'.tr;
bool isLoading = false;
bool isSent = false;
late String name;
late String licenseClass;
late String documentNo;
late String address;
late String height;
late String postalCode;
late String sex;
late String stateCode;
late String expireDate;
late String dob;
getBirthDate() {
Get.defaultDialog(
title: 'Select Date'.tr,
content: SizedBox(
width: 300,
child: CalendarDatePicker(
initialDate: DateTime.now().subtract(const Duration(days: 18 * 365)),
firstDate: DateTime.parse('1940-06-01'),
lastDate: DateTime.now().subtract(const Duration(days: 18 * 365)),
onDateChanged: (date) {
// Get the selected date and convert it to a DateTime object
DateTime dateTime = date;
// Call the getOrders() function from the controller
birthDate = dateTime.toString().split(' ')[0];
update();
Get.back();
},
// onDateChanged: (DateTime value) {},
),
),
);
}
@override
void onInit() {
// Get.put(SmsEgyptController());
super.onInit();
}
void changeGender(String value) {
gender = value;
update();
}
bool isValidEgyptianPhoneNumber(String phoneNumber) {
// Remove any non-digit characters (spaces, dashes, etc.)
phoneNumber = phoneNumber.replaceAll(RegExp(r'\D+'), '');
// Check if the phone number has exactly 11 digits
if (phoneNumber.length != 11) {
return false;
}
// Check if the phone number starts with 010, 011, 012, or 015
RegExp validPrefixes = RegExp(r'^01[0125]\d{8}$');
return validPrefixes.hasMatch(phoneNumber);
}
sendOtpMessage() async {
SmsEgyptController smsEgyptController = Get.put(SmsEgyptController());
isLoading = true;
update();
isLoading = true;
update();
if (formKey3.currentState!.validate()) {
if (box.read(BoxName.countryCode) == 'Egypt') {
if (isValidEgyptianPhoneNumber(phoneController.text)) {
var responseCheker = await CRUD()
.post(link: AppLink.checkPhoneNumberISVerfiedDriver, payload: {
'phone_number': ('+2${phoneController.text}'),
});
if (responseCheker != 'failure') {
var d = jsonDecode(responseCheker);
if (d['message'][0]['is_verified'].toString() == '1') {
Get.snackbar('Phone number is verified before'.tr, '',
backgroundColor: AppColor.greenColor);
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phone, ('+2${phoneController.text}'));
await Get.put(LoginDriverController()).loginWithGoogleCredential(
box.read(BoxName.driverID).toString(),
(box.read(BoxName.emailDriver).toString()),
);
} else {
await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: {
'phone_number': ('+2${phoneController.text}'),
"driverId": box.read(BoxName.driverID),
"email": (box.read(BoxName.emailDriver)),
});
isSent = true;
isLoading = false;
update();
}
} else {
await CRUD().post(link: AppLink.sendVerifyOtpMessage, payload: {
'phone_number': ('+2${phoneController.text}'),
"driverId": box.read(BoxName.driverID),
"email": box.read(BoxName.emailDriver),
});
isSent = true;
isLoading = false;
update();
}
} else {
mySnackeBarError(
'Phone Number wrong'.tr,
);
}
}
}
isLoading = false;
update();
}
DateTime? lastOtpSentTime; // Store the last OTP sent time
int otpResendInterval = 300; // 5 minutes in seconds
// Main function to handle OTP sending
// sendOtpMessage() async {
// if (_isOtpResendAllowed()) {
// isLoading = true;
// update();
// if (formKey3.currentState!.validate()) {
// String countryCode = box.read(BoxName.countryCode);
// String phoneNumber = phoneController.text;
// if (countryCode == 'Egypt' && isValidEgyptianPhoneNumber(phoneNumber)) {
// await _checkAndSendOtp(phoneNumber);
// } else {
// _showErrorMessage('Phone Number is not Egypt phone '.tr);
// }
// }
// isLoading = false;
// update();
// } else {
// _showCooldownMessage();
// }
// }
// Check if the resend OTP request is allowed (5 minutes cooldown)
// bool _isOtpResendAllowed() {
// if (lastOtpSentTime == null) return true;
// final int elapsedTime =
// DateTime.now().difference(lastOtpSentTime!).inSeconds;
// return elapsedTime >= otpResendInterval;
// }
// // Show message when user tries to resend OTP too soon
// void _showCooldownMessage() {
// int remainingTime = otpResendInterval -
// DateTime.now().difference(lastOtpSentTime!).inSeconds;
// Get.snackbar(
// 'Please wait ${remainingTime ~/ 60}:${(remainingTime % 60).toString().padLeft(2, '0')} minutes before requesting again',
// '',
// backgroundColor: AppColor.redColor,
// );
// }
// // Check if the phone number has been verified, and send OTP if not verified
// _checkAndSendOtp(String phoneNumber) async {
// var responseChecker = await CRUD().post(
// link: AppLink.checkPhoneNumberISVerfiedDriver,
// payload: {
// 'phone_number': '+2$phoneNumber',
// },
// );
// if (responseChecker != 'failure') {
// var responseData = jsonDecode(responseChecker);
// if (_isPhoneVerified(responseData)) {
// _handleAlreadyVerified();
// } else {
// await _sendOtpAndSms(phoneNumber);
// }
// } else {
// await _sendOtpAndSms(phoneNumber);
// }
// }
// Check if the phone number is already verified
bool _isPhoneVerified(dynamic responseData) {
return responseData['message'][0]['is_verified'].toString() == '1';
}
// Handle case where phone number is already verified
_handleAlreadyVerified() {
mySnackbarSuccess('Phone number is already verified'.tr);
box.write(BoxName.phoneVerified, '1');
box.write(BoxName.phone, ('+2${phoneController.text}'));
Get.put(LoginDriverController()).loginWithGoogleCredential(
box.read(BoxName.driverID).toString(),
box.read(BoxName.emailDriver).toString(),
);
}
// Send OTP and SMS
_sendOtpAndSms(String phoneNumber) async {
SmsEgyptController smsEgyptController = Get.put(SmsEgyptController());
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(
link: AppLink.sendVerifyOtpMessage,
payload: {
'phone_number': ('+2$phoneNumber'),
'token_code': (randomNumber.toString()),
'driverId': box.read(BoxName.driverID),
'email': box.read(BoxName.emailDriver),
},
);
await smsEgyptController.sendSmsEgypt(phoneNumber);
lastOtpSentTime = DateTime.now(); // Update the last OTP sent time
isSent = true;
isLoading = false;
update();
}
verifySMSCode() async {
// var loginDriverController = Get.put(LoginDriverController());
if (formKey3.currentState!.validate()) {
var res = await CRUD().post(link: AppLink.verifyOtpDriver, payload: {
'phone_number': ('+2${phoneController.text}'),
'token_code': (verifyCode.text.toString()),
});
if (res != 'failure') {
// var dec = jsonDecode(res);
box.write(BoxName.phoneDriver, ('+2${phoneController.text}'));
box.write(BoxName.phoneVerified, '1');
// loginDriverController.isGoogleLogin == true
// ? await loginDriverController.loginUsingCredentialsWithoutGoogle(
// loginDriverController.passwordController.text.toString(),
// box.read(BoxName.emailDriver).toString(),
// )
// : await loginDriverController.loginUsingCredentials(
// box.read(BoxName.driverID).toString(),
// box.read(BoxName.emailDriver).toString(),
// );
// Get.offAll(() => SyrianCardAI());
Get.to(() => RegistrationView());
// } else {
// Get.snackbar('title', 'message');
// }
}
} else {
mySnackeBarError('you must insert token code '.tr);
}
}
sendVerifications() async {
var res = await CRUD().post(link: AppLink.verifyEmail, payload: {
'email': emailController.text.isEmpty
? (Get.find<LoginDriverController>().emailController.text.toString())
: (emailController.text),
'token': (verifyCode.text),
});
if (res != 'failure') {
if (Get.find<LoginDriverController>().emailController.text.toString() !=
'') {
Get.offAll(() => HomeCaptain());
} else {
// Get.to(() => CarLicensePage());
}
}
}
void nextToAIDetection() async {
//Todo dont forget this
if (formKey.currentState!.validate()) {
isLoading = true;
update();
Get.to(() => AiPage());
}
}
Map<String, dynamic> payloadLisence = {};
void getFromController() {
name = Get.find<ScanDocumentsByApi>().name;
licenseClass = Get.find<ScanDocumentsByApi>().licenseClass.toString();
documentNo = Get.find<ScanDocumentsByApi>().documentNo.toString();
address = Get.find<ScanDocumentsByApi>().address.toString();
height = Get.find<ScanDocumentsByApi>().height.toString();
postalCode = Get.find<ScanDocumentsByApi>().address.toString();
sex = Get.find<ScanDocumentsByApi>().sex.toString();
stateCode = Get.find<ScanDocumentsByApi>().postalCode.toString();
expireDate = Get.find<ScanDocumentsByApi>().expireDate.toString();
dob = Get.find<ScanDocumentsByApi>().dob.toString();
update();
}
Future addLisence() async {
getFromController();
var res = await CRUD().post(link: AppLink.addLicense, payload: {
'name': name,
'licenseClass': licenseClass,
'documentNo': documentNo,
'address': address,
'height': height,
'postalCode': postalCode,
'sex': sex,
'stateCode': stateCode,
'expireDate': expireDate,
'dateOfBirth': dob,
});
isLoading = false;
update();
if (jsonDecode(res)['status'] == 'success') {
// Get.to(() => AiPage()); //todo rplace this
}
}
void addRegisrationCarForDriver(String vin, make, model, year, color, owner,
expirationDate, registrationDate) async {
getFromController();
var res = await CRUD().post(link: AppLink.addRegisrationCar, payload: {
'vin': vin,
'make': make,
'model': model,
'year': year,
'expirationDate': expirationDate,
'color': color,
'owner': owner,
'registrationDate': registrationDate,
});
box.write(BoxName.vin, vin);
box.write(BoxName.make, make);
box.write(BoxName.model, model);
box.write(BoxName.year, year);
box.write(BoxName.expirationDate, expirationDate);
box.write(BoxName.color, color);
box.write(BoxName.owner, owner);
box.write(BoxName.registrationDate, registrationDate);
isLoading = false;
update();
if (jsonDecode(res)['status'] == 'success') {
Get.offAll(() => LoginCaptin()); //todo replace this
}
}
Future register() async {
getFromController();
if (formKey.currentState!.validate()) {
isLoading = true;
update();
var res = await CRUD().post(link: AppLink.signUpCaptin, payload: {
'first_name': name.split(' ')[1],
'last_name': name.split(' ')[0],
'email': emailController.text,
'phone': phoneController.text,
'password': passwordController.text,
'gender': sex,
'site': address,
'birthdate': dob,
});
isLoading = false;
update();
if (jsonDecode(res)['status'] == 'success') {
box.write(BoxName.driverID, jsonDecode(res)['message']);
box.write(BoxName.dobDriver, dob);
box.write(BoxName.sexDriver, sex);
box.write(BoxName.phoneDriver, phoneController.text);
box.write(BoxName.lastNameDriver, name.split(' ')[0]);
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
'email': emailController.text,
'token': randomNumber.toString(),
});
Get.to(() => VerifyEmailCaptainPage());
}
}
}
}

View File

@@ -0,0 +1,30 @@
// import 'package:firebase_auth/firebase_auth.dart';
// import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
// class FacebookSignIn {
// Future<UserCredential?> signInWithFacebook() async {
// final LoginResult result = await FacebookAuth.instance.login();
// if (result.status == LoginStatus.success) {
// // Create a credential from the access token
// final OAuthCredential credential =
// FacebookAuthProvider.credential(result.accessToken!.tokenString);
// // Once signed in, return the UserCredential
// return await FirebaseAuth.instance.signInWithCredential(credential);
// }
// return null;
// }
// Future<void> signOut() async {
// try {
// await FacebookAuth.instance.logOut();
// print('Facebook Sign Out Successful');
// } catch (e) {
// print('Error during Facebook Sign Out: $e');
// }
// }
// Future<bool> isSignedIn() async {
// final accessToken = await FacebookAuth.instance.accessToken;
// return accessToken != null;
// }
// }

View File

@@ -0,0 +1,98 @@
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/auth/captin/cards/sms_signup.dart';
import 'package:siro_driver/views/home/on_boarding_page.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:get/get.dart';
import 'package:google_sign_in/google_sign_in.dart';
import '../../views/auth/captin/ai_page.dart';
import '../functions/add_error.dart';
import '../functions/encrypt_decrypt.dart';
class GoogleSignInHelper {
static final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
// متغير ثابت لحفظ حالة المستخدم محلياً كبديل لخاصية currentUser المحذوفة
static GoogleSignInAccount? _cachedUser;
static Future<GoogleSignInAccount?> signIn() async {
try {
final GoogleSignInAccount? googleUser =
await _googleSignIn.authenticate();
if (googleUser != null) {
_cachedUser = googleUser; // حفظ الجلسة في الكاش المحلي
await _handleSignUp(googleUser);
if (box.read(BoxName.countryCode) == 'Egypt') {
Get.to(() => SmsSignupEgypt());
} else if (box.read(BoxName.countryCode) == 'Jordan') {
Get.to(() => AiPage());
}
}
return googleUser;
} catch (error) {
return null;
}
}
Future<GoogleSignInAccount?> signInFromLogin() async {
try {
final GoogleSignInAccount? googleUser =
await _googleSignIn.authenticate();
if (googleUser != null) {
_cachedUser = googleUser; // حفظ الجلسة في الكاش المحلي
await _handleSignUp(googleUser);
final driverID =
(box.read(BoxName.driverID)?.toString()) ?? 'Unknown ID';
final emailDriver =
(box.read(BoxName.emailDriver)?.toString()) ?? 'Unknown Email';
print('Driver ID: $driverID');
print('Driver Email: $emailDriver');
await Get.find<LoginDriverController>()
.loginWithGoogleCredential(driverID, emailDriver);
}
return googleUser;
} catch (error, stackTrace) {
mySnackeBarError('$error');
CRUD.addError(error.toString(), stackTrace.toString(),
'GoogleSignInAccount?> signInFromLogin()');
return null;
}
}
static Future<void> _handleSignUp(GoogleSignInAccount user) async {
box.write(BoxName.driverID, (user.id) ?? 'Unknown ID');
box.write(BoxName.emailDriver, (user.email) ?? 'Unknown Email');
}
static Future<void> signOut() async {
try {
await _googleSignIn.signOut();
} catch (error) {
// التعامل مع الخطأ بصمت إذا كانت جلسة جوجل فارغة مسبقاً
} finally {
_cachedUser = null; // مسح الكاش المحلي بشكل إلزامي
await _handleSignOut();
}
}
static Future<void> _handleSignOut() async {
box.erase();
storage.deleteAll();
Get.offAll(OnBoardingPage());
}
// استخدام الكاش المحلي بدلاً من استدعاء المكتبة
static GoogleSignInAccount? getCurrentUser() {
return _cachedUser;
}
}

View File

@@ -0,0 +1,117 @@
import 'dart:convert';
import 'dart:math';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/controller/functions/secure_storage.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/auth/verify_email_page.dart';
import '../functions/encrypt_decrypt.dart';
class LoginController extends GetxController {
final formKey = GlobalKey<FormState>();
final formKeyAdmin = GlobalKey<FormState>();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController adminPasswordController = TextEditingController();
TextEditingController adminNameController = TextEditingController();
bool isAgreeTerms = false;
bool isloading = false;
final FlutterSecureStorage _storage = const FlutterSecureStorage();
void changeAgreeTerm() {
isAgreeTerms = !isAgreeTerms;
update();
}
void saveAgreementTerms() {
box.write(BoxName.agreeTerms, 'agreed');
update();
}
void saveCountryCode(String countryCode) {
box.write(BoxName.countryCode, countryCode);
update();
}
void login() async {
isloading = true;
update();
var res = await CRUD().get(link: AppLink.login, payload: {
'email': emailController.text,
'phone': phoneController.text,
'password': passwordController.text
});
isloading = false;
update();
if (res == 'failure') {
//Failure
mySnackeBarError('');
} else {
var jsonDecoeded = jsonDecode(res);
if (jsonDecoeded.isNotEmpty) {
if (jsonDecoeded['status'] == 'success') {
if (jsonDecoeded['data'][0]['verified'] == 1) {
box.write(BoxName.driverID, jsonDecoeded['data'][0]['id']);
box.write(BoxName.emailDriver, (jsonDecoeded['data'][0]['email']));
box.write(
BoxName.nameDriver,
jsonDecoeded['data'][0]['first_name'] +
' ' +
jsonDecoeded['data'][0]['last_name']);
box.write(BoxName.phone, jsonDecoeded['data'][0]['phone']);
SecureStorage().saveData(BoxName.password, passwordController.text);
// Get.offAll(() => const MapPagePassenger());
isloading = false;
update();
await CRUD().post(link: AppLink.addTokens, payload: {
'token': box.read(BoxName.tokenFCM),
'passengerID': box.read(BoxName.passengerID).toString()
});
} else {
isloading = false;
update();
Get.defaultDialog(
title: 'You must Verify email !.'.tr,
middleText: '',
backgroundColor: Colors.yellow[300],
onConfirm: () async {
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
'email': emailController.text,
'token': randomNumber.toString(),
});
Get.to(() => const VerifyEmailPage());
},
);
}
} else if (jsonDecoeded['status'] == 'Failure') {
mySnackeBarError(jsonDecoeded['data']);
isloading = false;
update();
}
} else {
isloading = false;
update();
}
}
}
goToMapPage() {
if (box.read(BoxName.email) != null) {
// Get.offAll(() => const MapPagePassenger());
}
}
@override
void onInit() {
super.onInit();
}
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/main.dart';
import '../../models/model/onboarding_model.dart';
import '../../views/auth/captin/login_captin.dart';
abstract class OnBoardingController extends GetxController {
next();
onPageChanged(int index);
}
class OnBoardingControllerImp extends OnBoardingController {
late PageController pageController;
int currentPage = 0;
@override
next() {
currentPage++;
if (currentPage > onBoardingList.length - 1) {
box.write(BoxName.onBoarding, 'yes');
Get.offAll(() => LoginCaptin());
} else {
pageController.animateToPage(currentPage,
duration: const Duration(milliseconds: 900), curve: Curves.easeInOut);
}
}
@override
onPageChanged(int index) {
currentPage = index;
update();
}
@override
void onInit() {
pageController = PageController();
super.onInit();
}
}

View File

@@ -0,0 +1,95 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import '../../views/auth/captin/login_captin.dart';
import '../../views/auth/verify_email_page.dart';
class RegisterController extends GetxController {
final formKey = GlobalKey<FormState>();
TextEditingController firstNameController = TextEditingController();
TextEditingController lastNameController = TextEditingController();
TextEditingController emailController = TextEditingController();
TextEditingController phoneController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController siteController = TextEditingController();
TextEditingController verfyCode = TextEditingController();
String birthDate = 'Birth Date'.tr;
String gender = 'Male'.tr;
@override
void onInit() {
super.onInit();
}
getBirthDate() {
Get.defaultDialog(
title: 'Select Date'.tr,
titleStyle: AppStyle.title,
content: SizedBox(
width: 300,
child: CalendarDatePicker(
initialDate:
DateTime.now().subtract(const Duration(days: 14 * 365)),
firstDate: DateTime.parse('1940-06-01'),
lastDate: DateTime.now().subtract(const Duration(days: 14 * 365)),
onDateChanged: (date) {
// Get the selected date and convert it to a DateTime object
DateTime dateTime = date;
// Call the getOrders() function from the controller
birthDate = dateTime.toString().split(' ')[0];
update();
},
// onDateChanged: (DateTime value) {},
),
),
confirm: MyElevatedButton(title: 'Ok'.tr, onPressed: () => Get.back()));
}
void changeGender(String value) {
gender = value;
update();
}
sendVerifications() async {
var res = await CRUD().post(link: AppLink.verifyEmail, payload: {
'email': emailController.text,
'token': verfyCode.text,
});
var dec = jsonDecode(res);
if (dec['status'] == 'success') {
Get.offAll(() => LoginCaptin());
}
}
void register() async {
if (formKey.currentState!.validate()) {
var res = await CRUD().post(link: AppLink.signUp, payload: {
'first_name': firstNameController.text.toString(),
'last_name': lastNameController.text.toString(),
'email': emailController.text.toString(),
'phone': phoneController.text.toString(),
'password': passwordController.text.toString(),
'gender': 'yet',
'site': siteController.text,
'birthdate': birthDate,
});
if (jsonDecode(res)['status'] == 'success') {
int randomNumber = Random().nextInt(100000) + 1;
await CRUD().post(link: AppLink.sendVerifyEmail, payload: {
'email': emailController.text,
'token': randomNumber.toString(),
});
Get.to(() => const VerifyEmailPage());
}
}
}
}

View File

@@ -0,0 +1,799 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image/image.dart' as img;
import 'package:path/path.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/firebase/notification_service.dart';
import '../../../constant/box_name.dart';
import 'package:path_provider/path_provider.dart';
// --- Final Submission ---
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
import '../../../constant/colors.dart';
import '../../../constant/info.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../functions/crud.dart';
import '../../functions/encrypt_decrypt.dart';
import '../../functions/package_info.dart';
import '../captin/login_captin_controller.dart';
// You can create a simple enum to manage image types
enum ImageType {
driverLicenseFront,
driverLicenseBack,
carLicenseFront,
carLicenseBack,
}
class RegistrationController extends GetxController {
// Page Controller for managing steps
late PageController pageController;
var currentPage =
0.obs; // Use .obs for reactive updates on the step indicator
// Loading state
var isLoading = false.obs;
var isloading = false;
CroppedFile? croppedFile;
final picker = ImagePicker();
var image;
File? myImage;
String? colorHex; // سيُملى من الدروب داون
// Form Keys for validation
final driverInfoFormKey = GlobalKey<FormState>();
final carInfoFormKey = GlobalKey<FormState>();
// STEP 1: Driver Information Controllers
final firstNameController = TextEditingController();
final lastNameController = TextEditingController();
final nationalIdController = TextEditingController();
final bithdateController = TextEditingController();
final phoneController = TextEditingController(); // You can pre-fill this
final driverLicenseExpiryController = TextEditingController();
DateTime? driverLicenseExpiryDate;
// STEP 2: Car Information Controllers
final carPlateController = TextEditingController();
final carMakeController = TextEditingController();
final carModelController = TextEditingController();
final carYearController = TextEditingController();
final carColorController = TextEditingController();
final carVinController = TextEditingController(); // Chassis number
final carRegistrationExpiryController = TextEditingController();
DateTime? carRegistrationExpiryDate;
// داخل RegistrationController
// المتغيرات لتخزين القيم المختارة (لإرسالها للـ API لاحقاً)
int? selectedVehicleCategoryId; // سيخزن 1 أو 2 أو 3
int? selectedFuelTypeId; // سيخزن 1 أو 2 أو 3 أو 4
// قائمة أنواع المركبات (مطابقة لقاعدة البيانات)
final List<Map<String, dynamic>> vehicleCategoryOptions = [
{'id': 1, 'name': 'Car'.tr}, // ترجمة: سيارة
{'id': 2, 'name': 'Motorcycle'.tr}, // ترجمة: دراجة نارية
{'id': 3, 'name': 'Van / Bus'.tr}, // ترجمة: فان / باص
];
// قائمة أنواع الوقود
final List<Map<String, dynamic>> fuelTypeOptions = [
{'id': 1, 'name': 'Petrol'.tr}, // ترجمة: بنزين
{'id': 2, 'name': 'Diesel'.tr}, // ترجمة: ديزل
{'id': 3, 'name': 'Electric'.tr}, // ترجمة: كهربائي
{'id': 4, 'name': 'Hybrid'.tr}, // ترجمة: هايبرد
];
// STEP 3: Document Uploads
File? driverLicenseFrontImage;
File? driverLicenseBackImage;
File? carLicenseFrontImage;
File? carLicenseBackImage;
@override
void onInit() {
super.onInit();
pageController = PageController();
// Pre-fill phone number if it exists in storage
// phoneController.text = box.read(BoxName.phoneDriver) ?? '';
}
@override
void onClose() {
pageController.dispose();
// Dispose all other text controllers
super.onClose();
}
// --- Page Navigation ---
void goToNextStep() {
bool isValid = false;
if (currentPage.value == 0) {
// Validate Step 1
isValid = driverInfoFormKey.currentState!.validate();
if (isValid) {
// Optional: Check if license is expired
// if (driverLicenseExpiryDate != null &&
// driverLicenseExpiryDate!.isBefore(DateTime.now())) {
// Get.snackbar('Expired License', 'Your drivers license has expired.'.tr
// ,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// return; // Stop progression
// }
}
} else if (currentPage.value == 1) {
// Validate Step 2
isValid = carInfoFormKey.currentState!.validate();
}
if (isValid) {
pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
}
void goToPreviousStep() {
pageController.previousPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeIn,
);
}
// --- Image Picking ---
Future<void> pickImage(ImageType type) async {
try {
final picker = ImagePicker();
final picked = await picker.pickImage(
source: ImageSource.camera,
imageQuality: 95, // جودة أولية من الكاميرا
maxWidth: 3000, // نسمح بصورة كبيرة ثم نصغّر نحن
);
if (picked == null) return;
// قصّ الصورة
final cropped = await ImageCropper().cropImage(
sourcePath: picked.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.accentColor,
toolbarWidgetColor: AppColor.redColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(title: 'Cropper'.tr),
],
);
if (cropped == null) return; // المستخدم ألغى
// قراءة bytes + التصحيح حسب EXIF ثم التصغير
final rawBytes = await File(cropped.path).readAsBytes();
final decoded = img.decodeImage(rawBytes);
if (decoded == null) throw Exception('Decode image failed');
// تصحيح اتجاه الصورة (EXIF)
final fixed = img.bakeOrientation(decoded);
// تصغير لعرض 800px (عدّل عند الحاجة)
final resized = img.copyResize(fixed, width: 800);
// حفظ مؤقت بصيغة JPG
final tmpDir = await getTemporaryDirectory();
final outPath =
'${tmpDir.path}/doc_${DateTime.now().millisecondsSinceEpoch}.jpg';
final outFile = File(outPath);
await outFile.writeAsBytes(img.encodeJpg(resized, quality: 85));
// عيّن الملف في المتغير الصحيح حسب النوع
if (outFile != null) {
switch (type) {
case ImageType.driverLicenseFront:
driverLicenseFrontImage = File(outFile.path);
break;
case ImageType.driverLicenseBack:
driverLicenseBackImage = File(outFile.path);
break;
case ImageType.carLicenseFront:
carLicenseFrontImage = File(outFile.path);
break;
case ImageType.carLicenseBack:
carLicenseBackImage = File(outFile.path);
break;
}
update(); // Use update() to refresh the GetBuilder UI
}
update(); // لتحديث الـ UI
// // الإرسال للذكاء الاصطناعي
// await sendToAI(type, imageFile: outFile);
} catch (e) {
mySnackeBarError('${'An unexpected error occurred:'.tr} $e');
}
}
// ثابت: 20 لون سيارة شائع
static const List<Map<String, String>> kCarColorOptions = [
{'key': 'color.white', 'hex': '#FFFFFF'},
{'key': 'color.black', 'hex': '#000000'},
{'key': 'color.silver', 'hex': '#C0C0C0'},
{'key': 'color.gray', 'hex': '#808080'},
{'key': 'color.gunmetal', 'hex': '#2A3439'},
{'key': 'color.red', 'hex': '#C62828'},
{'key': 'color.blue', 'hex': '#1565C0'},
{'key': 'color.navy', 'hex': '#0D47A1'},
{'key': 'color.green', 'hex': '#2E7D32'},
{'key': 'color.darkGreen', 'hex': '#1B5E20'},
{'key': 'color.beige', 'hex': '#D7CCC8'},
{'key': 'color.brown', 'hex': '#5D4037'},
{'key': 'color.maroon', 'hex': '#800000'},
{'key': 'color.burgundy', 'hex': '#800020'},
{'key': 'color.yellow', 'hex': '#F9A825'},
{'key': 'color.orange', 'hex': '#EF6C00'},
{'key': 'color.gold', 'hex': '#D4AF37'},
{'key': 'color.bronze', 'hex': '#CD7F32'},
{'key': 'color.champagne', 'hex': '#EFE1C6'},
{'key': 'color.purple', 'hex': '#6A1B9A'},
];
Color hexToColor(String hex) {
var v = hex.replaceAll('#', '');
if (v.length == 6) v = 'FF$v';
return Color(int.parse(v, radix: 16));
}
//uploadSyrianDocs
// دالة مساعدة: تضيف الحقل إذا كان له قيمة
void _addField(Map<String, String> fields, String key, String? value) {
if (value != null && value.toString().isNotEmpty) {
fields[key] = value.toString();
}
}
/// خريطة لتخزين روابط المستندات بعد الرفع
final Map<String, String> docUrls = {
'driver_license_front': '',
'driver_license_back': '',
'car_license_front': '',
'car_license_back': '',
};
/// التصرّف العام لاختيار/قص/ضغط/رفع الصورة حسب type
Future<void> choosImage(String link, String imageType) async {
try {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) return;
image = File(pickedImage.path);
final croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(title: 'Cropper'.tr),
],
);
if (croppedFile == null) return;
// صورة للمعاينة داخل التطبيق
myImage = File(croppedFile.path);
isloading = true;
update();
// ضغط (وأيضاً يمكنك إضافة rotateImageIfNeeded قبل/بعد الضغط إن رغبت)
final File compressedImage = await compressImage(File(croppedFile.path));
// تجهيز الحقول
final driverId = box.read(BoxName.driverID);
final payload = <String, String>{
'driverID': driverId,
'imageType': imageType, // مثال: driver_license_front
};
// الرفع وإرجاع الرابط
final String imageUrl = await uploadImage(compressedImage, payload, link);
// حفظ الرابط محلياً حسب النوع
docUrls[imageType] = imageUrl;
Log.print('✅ Uploaded $imageType => $imageUrl');
} catch (e, st) {
Log.print('❌ Error in choosImage: $e\n$st');
mySnackeBarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
/// ترفع الملف وترجع رابط الصورة النهائي كـ String
Future<String> uploadImage(
File file, Map<String, String> data, String link) async {
final uri = Uri.parse(link);
final request = http.MultipartRequest('POST', uri);
// الهيدرز (كما عندك)
final headers = <String, String>{
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
};
request.headers.addAll(headers);
// اسم الملف: driverID.jpg (اختياري)
final forcedName = '${box.read(BoxName.driverID) ?? 'image'}.jpg';
// إضافة الملف (من المسار مباشرة أسلم من الـ stream)
request.files.add(
await http.MultipartFile.fromPath(
'image', // تأكد أنه نفس اسم الحقل على السيرفر
file.path,
filename: forcedName,
),
);
// الحقول الإضافية
data.forEach((k, v) => request.fields[k] = v);
// الإرسال
final streamed = await request.send();
final res = await http.Response.fromStream(streamed);
if (res.statusCode != 200) {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
// نحاول استخراج رابط الصورة من أكثر من مفتاح محتمل
final body = jsonDecode(res.body);
final String? url = body['url'] ??
body['file_link'] ??
body['image_url'] ??
(body['data'] is Map ? body['data']['url'] : null);
if (url == null || url.isEmpty) {
// لو السيرفر يرجع هيكل مختلف، عدّل هنا المفتاح حسب استجابتك الفعلية
throw Exception(
'Upload succeeded but no image URL found in response: ${res.body}');
}
return url;
}
Future<File> compressImage(File file) async {
final dir = await path_provider.getTemporaryDirectory();
final targetPath = "${dir.absolute.path}/temp.jpg";
var result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
targetPath,
quality: 70,
minWidth: 1024,
minHeight: 1024,
);
return File(result!.path);
}
// دالة رفع إلى السيرفر السوري: ترجع file_url (Signed URL)
Future<String> uploadToSyria({
required String docType,
required File file,
required Uri syrianUploadUri,
required String authHeader,
required String hmacHeader,
required String driverId,
Duration timeout = const Duration(seconds: 60),
http.Client? clientOverride,
}) async {
final client = clientOverride ?? http.Client();
try {
final mime = lookupMimeType(file.path) ?? 'image/jpeg';
final parts = mime.split('/');
final req = http.MultipartRequest('POST', syrianUploadUri);
req.headers.addAll({
'Authorization': authHeader,
'X-HMAC-Auth': hmacHeader,
});
req.fields['driver_id'] = driverId;
req.fields['doc_type'] = docType;
req.files.add(
await http.MultipartFile.fromPath(
'file',
file.path,
filename: p.basename(file.path),
contentType: MediaType(parts.first, parts.last),
),
);
// ====== الطباعة قبل الإرسال ======
// Log.print('--- Syrian Upload Request ---');
// Log.print('URL: $syrianUploadUri');
// // Log.print('Method: POST');
// // Log.print('Headers: ${req.headers}');
// Log.print('Fields: ${req.fields}');
// // Log.print(
// // 'File: ${file.path} (${await file.length()} bytes, mime: $mime)');
// Log.print('-----------------------------');
// الإرسال
final streamed = await client.send(req).timeout(timeout);
final resp = await http.Response.fromStream(streamed);
// ====== الطباعة بعد الاستجابة ======
// Log.print('--- Syrian Upload Response ---');
Log.print('Status: ${resp.statusCode}');
// Log.print('Headers: ${resp.headers}');
// Log.print('Body: ${resp.body}');
// Log.print('-------------------------------');
Map<String, dynamic> j = {};
try {
j = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (e) {
Log.print('⚠️ Failed to parse JSON: $e');
}
// التحمّل لشكلين من الـ JSON:
final statusOk = j['status'] == 'success';
final fileUrl = (j['file_url'] ?? j['message']?['file_url'])?.toString();
final fileName =
(j['file_name'] ?? j['message']?['file_name'])?.toString();
if (resp.statusCode == 200 &&
statusOk &&
(fileUrl?.isNotEmpty ?? false)) {
// Log.print(
// '✅ Syrian upload success: $fileUrl (file: ${fileName ?? "-"})');
return fileUrl!;
}
throw Exception(
'❌ Syrian upload failed ($docType): ${j['message'] ?? resp.body}');
} finally {
if (clientOverride == null) client.close();
}
}
Future<void> submitRegistration() async {
// 0) دوال/مساعدات محلية
void _addField(Map<String, String> fields, String key, String? value) {
if (value != null && value.isNotEmpty) {
fields[key] = value;
}
}
// 1) تحقق من وجود الروابط
final driverFrontUrl = docUrls['driver_license_front'];
final driverBackUrl = docUrls['driver_license_back'];
final carFrontUrl = docUrls['car_license_front'];
final carBackUrl = docUrls['car_license_back'];
Log.print(driverFrontUrl.toString());
Log.print(driverBackUrl.toString());
Log.print(carFrontUrl.toString());
Log.print(carBackUrl.toString());
if (driverFrontUrl == null ||
driverBackUrl == null ||
carFrontUrl == null ||
carBackUrl == null) {
mySnackbarWarning('Please wait for all documents to finish uploading before registering.'.tr);
return;
}
isLoading.value = true;
update();
final registerUri = Uri.parse(AppLink.register_driver_and_car);
final client = http.Client();
try {
// ترويسات مشتركة
final bearer =
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}';
final hmac = '${box.read(BoxName.hmac)}';
String fingerPrint =
box.read(BoxName.deviceFingerprint)?.toString() ?? '';
String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
String nonce = timestamp; // Simple nonce for now
final req = http.MultipartRequest('POST', registerUri);
req.headers.addAll({
'Authorization': bearer,
// 'X-HMAC-Auth': hmac, // Removed to bypass "Invalid HMAC signature" check
'X-Device-FP': fingerPrint,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
});
final fields = <String, String>{};
// --- Driver Data ---
_addField(fields, 'id', box.read(BoxName.driverID)?.toString());
_addField(fields, 'first_name', firstNameController.text);
_addField(fields, 'last_name', lastNameController.text);
_addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
_addField(fields, 'national_number', nationalIdController.text);
_addField(fields, 'birthdate', bithdateController.text);
_addField(fields, 'expiry_date', driverLicenseExpiryController.text);
_addField(fields, 'password', 'generated_password_or_token');
_addField(fields, 'status', 'yet');
_addField(fields, 'email', 'Not specified');
_addField(fields, 'gender', 'Male'); // يفضل ربطها بـ Dropdown أيضاً
// --- Car Data ---
_addField(fields, 'vin', 'yet');
_addField(fields, 'car_plate', carPlateController.text);
_addField(fields, 'make', carMakeController.text);
_addField(fields, 'model', carModelController.text);
_addField(fields, 'year', carYearController.text);
_addField(
fields,
'expiration_date',
driverLicenseExpiryController
.text); // تأكد من أن هذا تاريخ انتهاء السيارة وليس الرخصة
_addField(
fields,
'color',
carColorController.text.isNotEmpty
? carColorController.text
: 'White');
_addField(fields, 'color_hex',
(colorHex != null && colorHex!.isNotEmpty) ? colorHex! : '#FFFFFF');
_addField(
fields,
'owner',
'${firstNameController.text} ${lastNameController.text}'
.trim()
.isNotEmpty
? '${firstNameController.text} ${lastNameController.text}'
: 'Driver Owner');
// ============================================================
// 🔥 التعديل الجديد: إرسال الأرقام (IDs) لتصنيف المركبة والوقود
// ============================================================
// 1. إرسال رقم تصنيف المركبة (1=سيارة, 2=دراجة...)
if (selectedVehicleCategoryId != null) {
_addField(fields, 'vehicle_category_id',
selectedVehicleCategoryId.toString());
} else {
_addField(fields, 'vehicle_category_id', '1'); // قيمة افتراضية (سيارة)
}
// 2. إرسال رقم ونوع الوقود
if (selectedFuelTypeId != null) {
// إرسال الرقم (للبحث السريع)
_addField(fields, 'fuel_type_id', selectedFuelTypeId.toString());
// إرسال الاسم نصاً (للتوافق مع العمود القديم 'fuel' إذا لزم الأمر)
// نبحث عن الاسم داخل القائمة بناءً على الرقم المختار
final fuelObj = fuelTypeOptions.firstWhere(
(e) => e['id'] == selectedFuelTypeId,
orElse: () => {'name': 'Petrol'});
_addField(fields, 'fuel', fuelObj['name'].toString());
} else {
_addField(fields, 'fuel_type_id', '1');
_addField(fields, 'fuel', 'Petrol');
}
// --- روابط الصور ---
_addField(fields, 'driver_license_front', driverFrontUrl!);
_addField(fields, 'driver_license_back', driverBackUrl!);
_addField(fields, 'car_license_front', carFrontUrl!);
_addField(fields, 'car_license_back', carBackUrl!);
req.fields.addAll(fields);
// 3) الإرسال
final streamed =
await client.send(req).timeout(const Duration(seconds: 60));
final resp = await http.Response.fromStream(streamed);
// 4) معالجة الاستجابة
Map<String, dynamic>? json;
try {
Log.print('--- Registration Response: ${resp.body} ---');
json = jsonDecode(resp.body) as Map<String, dynamic>;
} catch (_) {}
if (resp.statusCode == 200 && json?['status'] == 'success') {
mySnackbarSuccess('Registration completed successfully!'.tr);
// منطق التوكن والإشعارات وتسجيل الدخول...
final email = box.read(BoxName.emailDriver);
final driverID = box.read(BoxName.driverID);
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
await CRUD().post(link: AppLink.addTokensDriver, payload: {
'captain_id': (box.read(BoxName.driverID)).toString(),
'token': (box.read(BoxName.tokenDriver)).toString(),
'fingerPrint': fingerPrint.toString(),
});
NotificationService.sendNotification(
target: 'service',
title: 'New Driver Registration',
body: 'Driver $driverID has submitted registration.',
isTopic: true,
category: 'new_service_request',
);
final c = Get.isRegistered<LoginDriverController>()
? Get.find<LoginDriverController>()
: Get.put(LoginDriverController());
c.loginWithGoogleCredential(driverID, email);
} else {
final msg = (json?['message'] ?? 'Registration failed.').toString();
mySnackeBarError(msg);
}
} catch (e) {
mySnackeBarError('Error: $e');
} finally {
client.close();
isLoading.value = false;
update();
}
}
// // 1) تحقق من الصور
// if (driverLicenseFrontImage == null ||
// driverLicenseBackImage == null ||
// carLicenseFrontImage == null ||
// carLicenseBackImage == null) {
// Get.snackbar(
// 'Missing Documents'.tr, 'Please upload all 4 required documents.'.tr,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.orange,
// colorText: Colors.white);
// return;
// }
// isLoading.value = true;
// final uri = Uri.parse(
// 'https://intaleq.xyz/intaleq/auth/syria/driver/register_driver_and_car.php',
// );
// final client = http.Client();
// try {
// final req = http.MultipartRequest('POST', uri);
// // مهم: لا تضع Content-Type يدويًا، الـ MultipartRequest يتكفّل فيه ببناء boundary.
// final headers = {
// 'Authorization':
// 'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
// 'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
// };
// // 2) الحقول النصية
// final fields = <String, String>{};
// // --- Driver Data ---
// _addField(fields, 'id', box.read(BoxName.driverID)?.toString());
// _addField(fields, 'first_name', firstNameController.text);
// _addField(fields, 'last_name', lastNameController.text);
// _addField(fields, 'phone', box.read(BoxName.phoneDriver) ?? '');
// _addField(fields, 'national_number', nationalIdController.text);
// _addField(fields, 'expiry_date', driverLicenseExpiryController.text);
// _addField(
// fields, 'password', 'generate_your_password_here'); // عدّل حسب منطقك
// _addField(fields, 'status', 'yet');
// _addField(fields, 'email',
// 'Not specified'); // سكربت السيرفر سيحوّلها null ويبني ايميل افتراضي
// _addField(fields, 'gender', 'Male');
// // --- Car Data (مطابقة لما يتوقّعه السكربت) ---
// _addField(fields, 'vin', 'carVinController.text);');
// _addField(fields, 'car_plate', carPlateController.text);
// _addField(fields, 'make', carMakeController.text);
// _addField(fields, 'model', carModelController.text);
// _addField(fields, 'year', carYearController.text);
// _addField(fields, 'expiration_date', 'carRegistrationExpiryController');
// _addField(fields, 'color', carColorController.text);
// _addField(fields, 'fuel', 'Gasoline'); // أو حسب اختيارك
// _addField(fields, 'color_hex', colorHex); // مهم
// // لو عندك حقول إضافية مطلوبة بالسكربت (مالك المركبة / الكود اللوني / الوقود) مرّرها:
// _addField(fields, 'owner',
// firstNameController.text + ' ' + lastNameController.text);
// // if (colorHex != null) _addField(fields, 'color_hex', colorHex);
// // if (fuelType != null) _addField(fields, 'fuel', fuelType);
// req.headers.addAll(headers);
// req.fields.addAll(fields);
// // 3) الملفات (4 صور) — مفاتيحها مطابقة للسكربت
// Future<void> addFile(String field, File file) async {
// final mime = lookupMimeType(file.path) ?? 'image/jpeg';
// final parts = mime.split('/');
// final mediaType = MediaType(parts.first, parts.last);
// req.files.add(
// await http.MultipartFile.fromPath(
// field,
// file.path,
// filename: p.basename(file.path),
// contentType: mediaType,
// ),
// );
// }
// await addFile('driver_license_front', driverLicenseFrontImage!);
// await addFile('driver_license_back', driverLicenseBackImage!);
// await addFile('car_license_front', carLicenseFrontImage!);
// await addFile('car_license_back', carLicenseBackImage!);
// // 4) الإرسال
// final streamed =
// await client.send(req).timeout(const Duration(seconds: 60));
// final resp = await http.Response.fromStream(streamed);
// // 5) فحص النتيجة
// Map<String, dynamic>? json;
// try {
// json = jsonDecode(resp.body) as Map<String, dynamic>;
// } catch (_) {}
// if (resp.statusCode == 200 &&
// json != null &&
// json['status'] == 'success') {
// // ممكن يرجّع driverID, carRegID, documents
// final driverID =
// (json['data']?['driverID'] ?? json['driverID'])?.toString();
// if (driverID != null && driverID.isNotEmpty) {
// box.write(BoxName.driverID, driverID);
// }
// Get.snackbar('Success'.tr, 'Registration completed successfully!'.tr,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.green,
// colorText: Colors.white);
// // TODO: انتقل للصفحة التالية أو حدّث الحالة…
// } else {
// final msg =
// (json?['message'] ?? 'Registration failed. Please try again.')
// .toString();
// Get.snackbar('Error'.tr, msg,
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// }
// } catch (e) {
// Get.snackbar('Error'.tr, '${'An unexpected error occurred:'.tr} $e',
// snackPosition: SnackPosition.BOTTOM,
// backgroundColor: Colors.red,
// colorText: Colors.white);
// } finally {
// client.close();
// isLoading.value = false;
// }
// }
// Helpers
}

View File

@@ -0,0 +1,38 @@
import 'dart:convert';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
class TokenController extends GetxController {
bool isloading = false;
Future addToken() async {
String? basicAuthCredentials =
await storage.read(key: BoxName.basicAuthCredentials);
isloading = true;
update();
var res = await http.post(
Uri.parse(AppLink.addTokens),
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode(basicAuthCredentials.toString()))}',
},
body: {
'token': box.read(BoxName.tokenFCM.toString()),
'passengerID': box.read(BoxName.passengerID).toString()
},
);
isloading = false;
update();
var jsonToken = jsonDecode(res.body);
if (jsonToken['status'] == 'The token has been updated successfully.') {
mySnackbarSuccess('token updated'.tr);
}
}
}

View File

@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
class VerifyEmailController extends GetxController {
TextEditingController verfyCode = TextEditingController();
@override
void onInit() async {
super.onInit();
}
sendverfications() async {
await CRUD().post(link: AppLink.sendVerifyEmail);
}
}

View File

@@ -0,0 +1,53 @@
import 'dart:convert';
import 'package:googleapis_auth/auth_io.dart';
import '../../print.dart';
class AccessTokenManager {
static final AccessTokenManager _instance = AccessTokenManager._internal();
late final String serviceAccountJsonKey;
AccessToken? _accessToken;
DateTime? _expiryDate;
AccessTokenManager._internal();
factory AccessTokenManager(String jsonKey) {
if (_instance._isServiceAccountKeyInitialized()) {
// Prevent re-initialization
return _instance;
}
_instance.serviceAccountJsonKey = jsonKey;
return _instance;
}
bool _isServiceAccountKeyInitialized() {
try {
serviceAccountJsonKey; // Access to check if initialized
return true;
} catch (e) {
return false;
}
}
Future<String> getAccessToken() async {
if (_accessToken != null && DateTime.now().isBefore(_expiryDate!)) {
return _accessToken!.data;
}
try {
final serviceAccountCredentials = ServiceAccountCredentials.fromJson(
json.decode(serviceAccountJsonKey));
final client = await clientViaServiceAccount(
serviceAccountCredentials,
['https://www.googleapis.com/auth/firebase.messaging'],
);
_accessToken = client.credentials.accessToken;
_expiryDate = client.credentials.accessToken.expiry;
client.close();
// Log.print('_accessToken!.data: ${_accessToken!.data}');
return _accessToken!.data;
} catch (e) {
throw Exception('Failed to obtain access token');
}
}
}

View File

@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AppLifecycleManager {
static const platform = MethodChannel('com.sefer_driver/app_lifecycle');
static Future<void> bringAppToForeground() async {
try {
debugPrint('Attempting to bring app to foreground');
await platform.invokeMethod('bringAppToForeground');
debugPrint('Method invocation completed');
} on PlatformException catch (e) {
debugPrint("Failed to bring app to foreground: '${e.message}'.");
} catch (e) {
debugPrint("Unexpected error: $e");
}
}
}

View File

@@ -0,0 +1,373 @@
import 'dart:convert';
import 'dart:io';
import 'package:siro_driver/controller/home/captin/home_captain_controller.dart';
import 'package:siro_driver/views/home/Captin/orderCaptin/order_speed_request.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import 'package:siro_driver/controller/voice_call_controller.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../../print.dart';
import '../../views/auth/captin/criminal_documents_page.dart';
import '../../views/home/Captin/home_captain/home_captin.dart';
import '../../views/home/Captin/orderCaptin/order_request_page.dart';
import '../../views/home/Captin/orderCaptin/vip_order_page.dart';
import '../auth/google_sign.dart';
import '../functions/face_detect.dart';
import '../home/captin/map_driver_controller.dart';
import 'local_notification.dart';
class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance;
List<String> tokens = [];
List dataTokens = [];
late String driverID;
late String driverToken;
NotificationSettings? notificationSettings;
NotificationController notificationController =
Get.put(NotificationController());
Future<void> getNotificationSettings() async {
// Get the current notification settings
NotificationSettings? notificationSettings =
await FirebaseMessaging.instance.getNotificationSettings();
'Notification authorization status: ${notificationSettings.authorizationStatus}';
// Call the update function if needed
update();
}
Future<void> requestFirebaseMessagingPermission() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Check if the platform is Android
if (Platform.isAndroid) {
// Request permission for Android
await messaging.requestPermission();
} else if (Platform.isIOS) {
// Request permission for iOS
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: true,
criticalAlert: true,
provisional: false,
sound: true,
);
messaging.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
}
}
Future getToken() async {
fcmToken.getToken().then((token) {
Log.print('token fcm driver: ${token}');
box.write(BoxName.tokenDriver, (token!));
});
// 🔹 الاشتراك في topic
await fcmToken.subscribeToTopic("drivers"); // أو "users" حسب نوع المستخدم
print("Subscribed to 'drivers' topic ✅");
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) async {
if (message != null && message.data.isNotEmpty) {
Log.print("🔔 FCM getInitialMessage payload: ${message.data}");
String? category = message.data['category'] ?? message.data['type'];
if (category == 'ORDER' || category == 'Order' || category == 'OrderVIP' || message.data.containsKey('DriverList')) {
String? myListString = message.data['DriverList'];
if (myListString != null && myListString.isNotEmpty) {
await storage.write(key: 'pending_driver_list', value: myListString);
Log.print("💾 Saved pending driver list to secure storage from getInitialMessage");
}
} else {
Future.delayed(const Duration(milliseconds: 1500), () {
fireBaseTitles(message);
});
}
}
});
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) {
fireBaseTitles(message);
}
// if (message.data.isNotEmpty && message.notification != null) {
// fireBaseTitles(message);
// }
});
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty) {
fireBaseTitles(message);
}
});
}
Future<void> fireBaseTitles(RemoteMessage message) async {
// [!! تعديل جوهري !!]
// اقرأ "النوع" من حمولة البيانات، وليس من العنوان
String category = message.data['category'] ?? '';
// اقرأ العنوان والنص (للعرض)
String title = message.notification?.title ?? '';
String body = message.notification?.body ?? '';
// استخدم switch لسهولة القراءة والصيانة
switch (category) {
case 'ORDER':
case 'Order': // Handle both cases for backward compatibility
// if (Platform.isAndroid) {
// notificationController.showNotification(title, body, 'order', '');
// }
// 🔥 [Fix FCM-Guard] منع إعاقة الرحلة النشطة بطلبات جديدة عبر FCM
String currentRideStatus = box.read(BoxName.rideStatus) ?? '';
if (currentRideStatus == 'Begin' ||
currentRideStatus == 'Apply' ||
currentRideStatus == 'Arrived') {
Log.print(
"⛔ [FCM] Ignoring ORDER notification — driver has active ride ($currentRideStatus)");
break;
}
var myListString = message.data['DriverList'];
if (myListString != null) {
var myList = jsonDecode(myListString) as List<dynamic>;
driverToken = myList[14].toString();
Get.put(HomeCaptainController(), permanent: true).changeRideId();
update();
Get.toNamed('/OrderRequestPage', arguments: {
'myListString': myListString,
'DriverList': myList,
'body': body
});
}
break;
case 'OrderVIP':
var myListString = message.data['DriverList'];
if (myListString != null) {
var myList = jsonDecode(myListString) as List<dynamic>;
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'order', '');
}
Get.to(VipOrderPage(), arguments: {
'myListString': myListString,
'DriverList': myList,
'body': body
});
}
break;
case 'Cancel Trip':
case 'TRIP_CANCELLED':
if (Platform.isAndroid) {
notificationController.showNotification(
title, 'Passenger Cancel Trip'.tr, 'cancel', '');
}
Log.print("🔔 FCM: Ride Cancelled by Passenger received.");
// 1. استخراج السبب (أرسلناه من PHP باسم 'reason')
String reason = message.data['reason'] ?? 'No reason provided';
// 2. توجيه الأمر للكنترولر
if (Get.isRegistered<MapDriverController>()) {
// استدعاء الحارس (سيتجاهل الأمر إذا كان السوكيت قد سبقه)
Get.find<MapDriverController>()
.processRideCancelledByPassenger(reason, source: "FCM");
}
break;
case 'VIP Order Accepted':
// This seems to be a notification for the passenger, but if the driver needs to see it:
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'order', '');
}
// Maybe show a simple snackbar confirmation
mySnackbarSuccess('You accepted the VIP order.'.tr);
break;
case 'message From passenger':
case 'MSG_FROM_PASSENGER':
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'ding', '');
}
MyDialog().getDialog(title, body, () {
// Empty callback, MyDialog already closes itself using pop().
});
break;
case 'token change':
case 'TOKEN_CHANGE':
GoogleSignInHelper.signOut();
break;
case 'face detect':
case 'FACE_DETECT':
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone2', '');
}
String result0 = await faceDetector();
var result = jsonDecode(result0);
MyDialogContent().getDialog(
'Face Detection Result'.tr,
Text(
result['similar'].toString() == 'true'
? 'similar'.tr
: 'not similar'.tr,
style: AppStyle.title,
),
() {
// Navigator.pop(Get.context!);
},
);
update();
break;
case 'Hi ,I will go now':
case 'PASSENGER_COMING':
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone2', '');
}
update();
break;
case 'Criminal Document Required':
case 'DOC_REQUIRED':
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'tone2', '');
}
MyDialog().getDialog(title, 'You should have upload it .'.tr, () {
Get.to(() => const CriminalDocumemtPage());
});
break;
case 'Order Applied':
case 'ORDER_TAKEN':
mySnackbarSuccess("The order has been accepted by another driver.".tr);
break;
case 'incoming_call':
case 'INCOMING_CALL':
final sessionId = message.data['session_id'];
final callerName = message.data['caller_name'];
final rideId = message.data['ride_id'];
if (sessionId != null && callerName != null && rideId != null) {
Get.find<VoiceCallController>().receiveCall(
sessionIdVal: sessionId.toString(),
remoteNameVal: callerName.toString(),
rideIdVal: rideId.toString(),
);
}
break;
default:
Log.print('Received unhandled notification category: $category');
// Optionally show a generic notification
if (Platform.isAndroid) {
notificationController.showNotification(title, body, 'default', '');
}
break;
}
}
SnackbarController driverAppliedTripSnakBar() {
return Get.snackbar(
'Driver Applied the Ride for You'.tr,
'',
colorText: AppColor.greenColor,
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
titleText: Text(
'Applied'.tr,
style: const TextStyle(color: AppColor.redColor),
),
messageText: Text(
'Driver Applied the Ride for You'.tr,
style: AppStyle.title,
),
icon: const Icon(Icons.approval),
shouldIconPulse: true,
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
);
}
Future<dynamic> cancelTripDialog() {
return Get.defaultDialog(
barrierDismissible: false,
title: 'Passenger Cancel Trip'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () {
box.write(BoxName.rideStatus, 'Cancel');
box.write(BoxName.statusDriverLocation, 'off');
Log.print(
'rideStatus from 347 : ${box.read(BoxName.rideStatus)}');
Get.offAll(HomeCaptain());
}));
}
Future<dynamic> cancelTripDialog1() {
return Get.defaultDialog(
barrierDismissible: false,
title: 'Passenger Cancel Trip'.tr,
middleText:
'Trip Cancelled. The cost of the trip will be added to your wallet.'
.tr,
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () {
box.write(BoxName.rideStatus, 'Cancel');
Log.print(
'rideStatus from 364 : ${box.read(BoxName.rideStatus)}');
Get.offAll(HomeCaptain());
}));
}
}
class OverlayContent extends StatelessWidget {
final String title;
final String body;
OverlayContent(this.title, this.body);
@override
Widget build(BuildContext context) {
return Material(
child: Container(
padding: const EdgeInsets.all(16.0),
color: Colors.white,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8.0),
Text(
body,
style: const TextStyle(fontSize: 16),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,548 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:ui'; // للألوان
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart'; // للوصول لـ box
import '../../print.dart';
import '../../views/home/Captin/driver_map_page.dart';
import '../../views/home/Captin/orderCaptin/order_request_page.dart';
import '../functions/crud.dart';
import '../home/captin/home_captain_controller.dart';
class NotificationController extends GetxController {
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
// ==============================================================================
// 1. تهيئة الإشعارات (إعداد القنوات والأزرار للآيفون والأندرويد)
// ==============================================================================
Future<void> initNotifications() async {
// إعدادات الأندرويد
const AndroidInitializationSettings android =
AndroidInitializationSettings('@mipmap/launcher_icon');
// إعدادات أزرار الآيفون (Categories)
// هذا الجزء ضروري لظهور الأزرار في iOS
final List<DarwinNotificationCategory> darwinNotificationCategories = [
DarwinNotificationCategory(
'ORDER_CATEGORY', // المعرف المستخدم لربط الإشعار بالأزرار
actions: [
DarwinNotificationAction.plain('ACCEPT_ORDER', '✅ قبول'),
DarwinNotificationAction.plain('SHOW_DETAILS', '📄 تفاصيل'),
DarwinNotificationAction.plain(
'REJECT_ORDER',
'❌ رفض',
options: {
DarwinNotificationActionOption.destructive
}, // يظهر باللون الأحمر
),
],
)
];
// إعدادات الآيفون العامة
final DarwinInitializationSettings ios = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
notificationCategories: darwinNotificationCategories, // تسجيل الأزرار
);
InitializationSettings initializationSettings =
InitializationSettings(android: android, iOS: ios);
tz.initializeTimeZones();
print('✅ Notifications initialized with Action Buttons Support');
await _flutterLocalNotificationsPlugin.initialize(
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
settings: initializationSettings,
);
// إنشاء قناة الأندرويد ذات الأهمية القصوى
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for important notifications.',
importance: Importance.max, // أقصى أهمية
playSound: true,
);
await _flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
}
// ==============================================================================
// 2. دالة عرض الإشعار المطور (شكل واضح + أزرار + صوت مخصص)
// ==============================================================================
void showOrderNotification(
String title, String body, String tone, String myListString) async {
// أ) تنسيق النص والبيانات بشكل جميل
String formattedBigText = body;
String summaryText = 'طلب جديد';
String price = '';
try {
List<dynamic> data = jsonDecode(myListString);
// استخراج البيانات (تأكد أن الاندكسات مطابقة للباك إند عندك)
price = _getVal(data, 26);
String distance = _getVal(data, 5);
String startLoc = _getVal(data, 29);
String endLoc = _getVal(data, 30);
String paxName = _getVal(data, 8);
// String rating = _getVal(data, 33);
String isHaveSteps = _getVal(data, 20);
// تنسيق النص ليكون 4 أسطر واضحة
formattedBigText = "👤 $paxName\n"
"💰 $price ${'SYP'.tr} | 🛣️ $distance كم\n"
"🟢 من: $startLoc\n"
"🏁 إلى: $endLoc";
if (isHaveSteps == 'true') {
formattedBigText += "\n🛑 هذه الرحلة تحتوي على نقاط توقف!";
}
summaryText = 'سعر الرحلة: $price';
} catch (e) {
print("Error formatting notification text: $e");
}
// ب) نمط النص الكبير (BigText) للأندرويد
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
formattedBigText,
contentTitle: '🚖 $title',
summaryText: summaryText,
htmlFormatContent: true,
htmlFormatContentTitle: true,
);
// ج) معالجة اسم الصوت (أندرويد بدون امتداد، آيفون مع امتداد)
String soundNameAndroid = tone.contains('.') ? tone.split('.').first : tone;
String soundNameIOS = tone.contains('.') ? tone : "$tone.wav";
// د) إعدادات الأندرويد (الأزرار + Full Screen)
final androidDetails = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.max,
fullScreenIntent: true, // يفتح الشاشة وتظهر التفاصيل
category: AndroidNotificationCategory.call, // يعامل كمكالمة (رنين مستمر)
visibility: NotificationVisibility.public,
ongoing: true, // يمنع الحذف بالسحب
sound: RawResourceAndroidNotificationSound(soundNameAndroid),
audioAttributesUsage: AudioAttributesUsage.alarm, // صوت عالٍ كالمنبه
styleInformation: bigTextStyleInformation,
color: const Color(0xFF1A252F),
// الأزرار الثلاثة
actions: <AndroidNotificationAction>[
const AndroidNotificationAction(
'ACCEPT_ORDER',
'✅ قبول فوري',
showsUserInterface: true,
titleColor: Color(0xFF4CAF50), // أخضر
),
const AndroidNotificationAction(
'SHOW_DETAILS',
'📄 التفاصيل',
showsUserInterface: true,
titleColor: Color(0xFF2196F3), // أزرق
),
const AndroidNotificationAction(
'REJECT_ORDER',
'❌ رفض',
showsUserInterface: false, // لا يفتح التطبيق
cancelNotification: true,
titleColor: Color(0xFFE53935), // أحمر
),
],
);
// هـ) إعدادات الآيفون
final iosDetails = DarwinNotificationDetails(
sound: soundNameIOS,
presentAlert: true,
presentBadge: true,
presentSound: true,
categoryIdentifier: 'ORDER_CATEGORY', // ربط الأزرار
interruptionLevel: InterruptionLevel.critical, // محاولة لكسر الصامت
);
final details =
NotificationDetails(android: androidDetails, iOS: iosDetails);
String briefBody = "$price - مسافة $formattedBigText";
if (_getVal(jsonDecode(myListString), 20) == 'true') {
briefBody = "🛑 (متعددة التوقفات) $price - مسافة $formattedBigText";
}
// عرض الإشعار
await _flutterLocalNotificationsPlugin.show(
id: 1001, // ID ثابت لاستبدال الإشعار القديم
title: title,
body: briefBody, // نص مختصر يظهر في البار العلوي
notificationDetails: details,
payload: jsonEncode({
'type': 'Order',
'data': myListString,
}),
);
}
// ==============================================================================
// 3. معالجة الاستجابة (عند الضغط على الأزرار)
// ==============================================================================
Future<void> handleNotificationResponse(NotificationResponse response) async {
final payload = response.payload;
if (payload == null) return;
final payloadData = jsonDecode(payload) as Map<String, dynamic>;
final rawData = payloadData['data'];
List<dynamic> listData = [];
if (rawData is String) {
listData = jsonDecode(rawData);
} else if (rawData is List) {
listData = rawData;
}
print("🔔 Notification Action: ${response.actionId}");
// أ) زر القبول
if (response.actionId == 'ACCEPT_ORDER') {
await _flutterLocalNotificationsPlugin.cancel(id: 1001); // حذف الإشعار
_processAcceptOrder(listData);
}
// ب) زر التفاصيل
else if (response.actionId == 'SHOW_DETAILS') {
// await _flutterLocalNotificationsPlugin.cancel(1001); // اختياري: حذف الإشعار
Get.to(() => OrderRequestPage(), arguments: {'myListString': rawData});
}
// ج) زر الرفض
else if (response.actionId == 'REJECT_ORDER') {
await _flutterLocalNotificationsPlugin.cancel(id: 1001); // حذف الإشعار
_processRejectOrder(listData);
}
// د) الضغط على الإشعار نفسه (بدون أزرار)
else {
Get.to(() => OrderRequestPage(), arguments: {'myListString': rawData});
}
}
// ==============================================================================
// 4. منطق القبول الآمن (Safe Accept Logic)
// ==============================================================================
Future<void> _processAcceptOrder(List<dynamic> data) async {
// إظهار Loading
Get.dialog(
WillPopScope(
onWillPop: () async => false,
child: const Center(
child: CircularProgressIndicator(color: Colors.white),
),
),
barrierDismissible: false,
);
try {
final driverId = box.read(BoxName.driverID);
String orderId = _getVal(data, 16);
String passengerToken = _getVal(data, 9);
print("🚀 Sending Accept Request for Order: $orderId");
var res = await CRUD().post(
link: "${AppLink.ride}/rides/acceptRide.php",
payload: {
'id': orderId,
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'passengerToken': passengerToken,
'driver_id': driverId,
},
);
print("📥 Server Response: $res");
if (Get.isDialogOpen == true) Get.back(); // إغلاق اللودينج
// 🔴 فحص النتيجة بدقة (Map أو String)
bool isFailure = false;
if (res is Map && res['status'] == 'failure') {
isFailure = true;
} else if (res == 'failure') {
isFailure = true;
}
if (isFailure) {
Get.defaultDialog(
title: "تنبيه",
middleText: "عذراً، الطلب أخذه سائق آخر.",
confirmTextColor: Colors.white,
onConfirm: () => Get.back(),
textConfirm: "حسناً",
);
return; // توقف هنا ولا تكمل
}
// ✅ نجاح -> تجهيز الانتقال
// حماية من الكراش: التأكد من وجود HomeCaptainController
if (!Get.isRegistered<HomeCaptainController>()) {
print("♻️ Reviving HomeCaptainController...");
Get.put(HomeCaptainController(), permanent: true);
} else {
Get.find<HomeCaptainController>().changeRideId();
}
box.write(BoxName.statusDriverLocation, 'on');
box.write(BoxName.rideStatus, 'Apply');
var rideArgs = _buildRideArgs(data);
box.write(BoxName.rideArguments, rideArgs);
// استخدام offAll لمنع الرجوع لصفحة الطلب
Get.offAll(() => PassengerLocationMapPage(), arguments: rideArgs);
} catch (e) {
if (Get.isDialogOpen == true) Get.back();
print("❌ Error in accept process: $e");
Get.snackbar("خطأ", "حدث خطأ غير متوقع");
}
}
// ==============================================================================
// 5. منطق الرفض (يعمل في الخلفية بدون فتح صفحات)
// ==============================================================================
Future<void> _processRejectOrder(List<dynamic> data) async {
try {
final driverId = box.read(BoxName.driverID);
String orderId = _getVal(data, 16);
if (driverId != null && orderId.isNotEmpty) {
print("📤 Rejecting Order: $orderId");
await CRUD().post(link: AppLink.addDriverOrder, payload: {
'driver_id': driverId,
'order_id': orderId,
'status': 'Refused'
});
print("✅ Order Rejected Successfully");
}
} catch (e) {
print("❌ Error rejecting order: $e");
}
}
// ==============================================================================
// 6. دوال مساعدة (Helpers)
// ==============================================================================
Map<String, dynamic> _buildRideArgs(List<dynamic> data) {
return {
'passengerLocation': '${_getVal(data, 0)},${_getVal(data, 1)}',
'passengerDestination': '${_getVal(data, 3)},${_getVal(data, 4)}',
'Duration': _getVal(data, 4), // انتبه: تأكد من الإندكس الصحيح للوقت
'totalCost': _getVal(data, 26),
'Distance': _getVal(data, 5),
'name': _getVal(data, 8),
'phone': _getVal(data, 10),
'email': _getVal(data, 28),
'WalletChecked': _getVal(data, 13),
'tokenPassenger': _getVal(data, 9),
'direction':
'https://www.google.com/maps/dir/${_getVal(data, 0)}/${_getVal(data, 1)}/',
'DurationToPassenger': _getVal(data, 15),
'rideId': _getVal(data, 16),
'passengerId': _getVal(data, 7),
'driverId': _getVal(data, 18),
'durationOfRideValue': _getVal(data, 19),
'paymentAmount': _getVal(data, 2),
'paymentMethod': _getVal(data, 13) == 'true' ? 'visa' : 'cash',
'isHaveSteps': _getVal(data, 20),
'step0': _getVal(data, 21),
'step1': _getVal(data, 22),
'step2': _getVal(data, 23),
'step3': _getVal(data, 24),
'step4': _getVal(data, 25),
'passengerWalletBurc': _getVal(data, 26),
'timeOfOrder': DateTime.now().toString(),
'totalPassenger': _getVal(data, 2),
'carType': _getVal(data, 31),
'kazan': _getVal(data, 32),
'startNameLocation': _getVal(data, 29),
'endNameLocation': _getVal(data, 30),
};
}
String _getVal(List<dynamic> data, int index) {
if (data.length > index && data[index] != null) {
return data[index].toString();
}
return '';
}
// Callbacks
void onDidReceiveNotificationResponse(NotificationResponse response) {
handleNotificationResponse(response);
}
void onDidReceiveBackgroundNotificationResponse(
NotificationResponse response) {
handleNotificationResponse(response);
}
// ==============================================================================
// 7. الدوال القديمة (Old Scheduled Notifications) - لم يتم تغييرها
// ==============================================================================
void showNotification(
String title, String message, String tone, String payLoad) async {
// هذه الدالة القديمة للإشعارات البسيطة (ليس الطلبات)
BigTextStyleInformation bigTextStyleInformation = BigTextStyleInformation(
message,
contentTitle: title.tr,
htmlFormatContent: true,
htmlFormatContentTitle: true,
);
AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
);
DarwinNotificationDetails ios = const DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
await _flutterLocalNotificationsPlugin.show(
id: 0,
title: title,
body: message,
notificationDetails: details,
payload: jsonEncode({'title': title, 'data': payLoad}));
}
void scheduleNotificationsForSevenDays(
String title, String message, String tone) async {
final AndroidNotificationDetails android = AndroidNotificationDetails(
'high_importance_channel',
'High Importance Notifications',
importance: Importance.max,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound(tone.split('.').first),
);
const DarwinNotificationDetails ios = DarwinNotificationDetails(
sound: 'default',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final NotificationDetails details =
NotificationDetails(android: android, iOS: ios);
if (Platform.isAndroid) {
if (await Permission.scheduleExactAlarm.isDenied) {
await Permission.scheduleExactAlarm.request();
}
}
for (int day = 0; day < 7; day++) {
final notificationTimes = [
{'hour': 8, 'minute': 0, 'id': day * 1000 + 1},
{'hour': 15, 'minute': 0, 'id': day * 1000 + 2},
{'hour': 20, 'minute': 0, 'id': day * 1000 + 3},
];
for (var time in notificationTimes) {
final notificationId = time['id'] as int;
bool isScheduled = box.read('notification_$notificationId') ?? false;
if (!isScheduled) {
await _scheduleNotificationForTime(
day,
time['hour'] as int,
time['minute'] as int,
title,
message,
details,
notificationId,
);
box.write('notification_$notificationId', true);
}
}
}
}
// ==============================================================================
// دالة حذف إشعار الطلب (تستدعى عند أخذ الطلب من سائق آخر)
// ==============================================================================
Future<void> cancelOrderNotification() async {
// 1001 هو نفس الآيدي الذي استخدمناه عند عرض الإشعار
await _flutterLocalNotificationsPlugin.cancel(id: 1001);
print("🗑️ Order Notification Cancelled (Taken by another driver)");
}
Future<void> _scheduleNotificationForTime(
int dayOffset,
int hour,
int minute,
String title,
String message,
NotificationDetails details,
int notificationId,
) async {
tz.initializeTimeZones();
var cairoLocation =
tz.getLocation('Africa/Cairo'); // تأكد من المنطقة الزمنية
final now = tz.TZDateTime.now(cairoLocation);
tz.TZDateTime scheduledDate = tz.TZDateTime(
cairoLocation,
now.year,
now.month,
now.day + dayOffset,
hour,
minute,
);
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
await _flutterLocalNotificationsPlugin.zonedSchedule(
id: notificationId,
title: title,
body: message,
scheduledDate: scheduledDate,
notificationDetails: details,
androidScheduleMode: AndroidScheduleMode.exact, // أو exactAllowWhileIdle
matchDateTimeComponents: null,
);
}
}

View File

@@ -0,0 +1,63 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:get/get.dart'; // للترجمة .tr
class NotificationService {
static const String _serverUrl =
'https://api.intaleq.xyz/intaleq/ride/firebase/send_fcm.php';
static Future<void> sendNotification({
required String target,
required String title,
required String body,
required String category, // إلزامي للتصنيف
String? tone,
List<String>? driverList,
bool isTopic = false,
}) async {
try {
// 1. تجهيز البيانات المخصصة (Data Payload)
Map<String, dynamic> customData = {};
customData['category'] = category;
// إذا كان هناك قائمة سائقين/ركاب، نضعها هنا
if (driverList != null && driverList.isNotEmpty) {
// نرسلها كـ JSON String لأن FCM v1 يدعم String Values فقط في الـ data
customData['driverList'] = jsonEncode(driverList);
}
// 2. تجهيز الطلب الرئيسي للسيرفر
final Map<String, dynamic> requestPayload = {
'target': target,
'title': title,
'body': body,
'isTopic': isTopic,
'data':
customData, // 🔥🔥 التغيير الجوهري: وضعنا البيانات داخل "data" 🔥🔥
};
if (tone != null) {
requestPayload['tone'] = tone;
}
final response = await http.post(
Uri.parse(_serverUrl),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(requestPayload),
);
if (response.statusCode == 200) {
print('✅ Notification sent successfully.');
// print('Response: ${response.body}');
} else {
print('❌ Failed to send notification. Code: ${response.statusCode}');
print('Error Body: ${response.body}');
}
} catch (e) {
print('❌ Error sending notification: $e');
}
}
}

View File

@@ -0,0 +1,35 @@
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/views/home/Captin/home_captain/home_captin.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:flutter/material.dart';
class OverlayContent1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: Container(
padding: EdgeInsets.all(16.0),
color: Colors.white,
child: MyElevatedButton(
title: 'go to order',
onPressed: () async {
var res = await CRUD().post(
link: AppLink.addFeedBack,
payload: {
"passengerId": 'dddddd',
"feedBack": "eeeee",
},
);
print(res);
if (res != 'failure') {
Navigator.push(
context, MaterialPageRoute(builder: (cont) => HomeCaptain()));
// Get.to(OrderRequestPage());
}
},
),
),
);
}
}

View File

@@ -0,0 +1,32 @@
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import 'crud.dart';
addError1(String error, String details, String where) async {
try {
// Get user information for the error log
final userId = box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
final userType =
box.read(BoxName.driverID) != null ? 'Driver' : 'passenger';
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
// Send the error data to the server
// Note: This is a fire-and-forget call. We don't await it or handle its response
// to prevent an infinite loop if the addError endpoint itself is failing.
CRUD().post(
link: AppLink.addError,
payload: {
'error': error.toString(),
'userId': userId.toString(),
'userType': userType,
'phone': phone.toString(),
'device': where, // The location of the error
'details': details, // The detailed stack trace or context
},
);
} catch (e) {
// If logging the error itself fails, print to the console to avoid infinite loops.
print("Failed to log error to server: $e");
}
}

View File

@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
import 'dart:convert';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../constant/colors.dart';
import '../../print.dart';
import 'crud.dart';
class AppUpdateController extends GetxController {
@override
void onInit() {
super.onInit();
// الفحص التلقائي عند التشغيل لتحديثات المتجر
checkSmartUpdate();
}
// ======================================================================
// الدالة الذكية المدمجة (الآن تفحص المتجر فقط لأن Shorebird يعمل تلقائياً بالخلفية)
// ======================================================================
Future<void> checkSmartUpdate() async {
Log.print("🔄 بدء فحص تحديثات المتجر...");
// 1. فحص تحديث المتجر (Native Update)
await _checkStoreUpdate();
}
// ======================================================================
// 1. تحديث المتجر الأساسي
// ======================================================================
Future<bool> _checkStoreUpdate() async {
try {
final packageInfo = await PackageInfo.fromPlatform();
final currentBuildNumber = packageInfo.buildNumber;
// استخدام نفس الـ Endpoint والمعايير الموجودة في التطبيق
var response = await CRUD().get(link: AppLink.packageInfo, payload: {
"platform": Platform.isAndroid ? 'android' : 'ios',
"appName": AppInformation.appVersion,
});
if (response != 'failure') {
var decoded = jsonDecode(response);
if (decoded['status'] == 'success' && decoded['message'] != null && decoded['message'].isNotEmpty) {
String latestBuildNumber = decoded['message'][0]['version'].toString();
// مقارنة الـ Build Number
if (latestBuildNumber != currentBuildNumber) {
_showStoreUpdateDialog();
return true;
}
}
}
} catch (e) {
Log.print("❌ Store update check error: $e");
}
return false;
}
// ======================================================================
// دوال مساعدة
// ======================================================================
void _showStoreUpdateDialog() {
final String storeUrl = Platform.isAndroid
? 'https://play.google.com/store/apps/details?id=com.intaleq_driver'
: 'https://apps.apple.com/jo/app/intaleq-driver/id6482995159';
Get.defaultDialog(
title: "تحديث جديد متوفر".tr,
middleText: "يوجد إصدار جديد من التطبيق في المتجر، يرجى التحديث للحصول على الميزات الجديدة.".tr,
barrierDismissible: false,
onWillPop: () async => false,
confirm: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))
),
onPressed: () async {
if (await canLaunchUrl(Uri.parse(storeUrl))) {
await launchUrl(Uri.parse(storeUrl), mode: LaunchMode.externalApplication);
}
},
child: Text("تحديث الآن".tr, style: const TextStyle(color: Colors.white)),
),
);
}
}

View File

@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:get/get.dart';
class AudioController extends GetxController {
final AudioPlayer _audioPlayer = AudioPlayer();
Future<void> playAudio() async {
// Check if the platform is Android
if (Theme.of(Get.context!).platform == TargetPlatform.android) {
try {
// Load the audio file from the raw resources
await _audioPlayer.setAsset(
'assets/order1.wav'); // Adjust the path based on your project structure
_audioPlayer.play();
} catch (e) {
// Handle errors, such as file not found
print('Error playing audio: $e');
}
}
}
Future<void> playAudio1(String path) async {
// Check if the platform is Android
// if (Theme.of(Get.context!).platform == TargetPlatform.android) {
try {
// Load the audio file from the raw resources
await _audioPlayer
.setAsset(path); // Adjust the path based on your project structure
_audioPlayer.play();
} catch (e) {
// Handle errors, such as file not found
print('Error playing audio: $e');
}
// }
}
@override
void onClose() {
// Release resources when done
_audioPlayer.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,80 @@
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
import 'package:record/record.dart';
class AudioRecorderController extends GetxController {
AudioPlayer audioPlayer = AudioPlayer();
AudioRecorder recorder = AudioRecorder();
bool isRecording = false;
bool isPlaying = false;
bool isPaused = false;
String filePath = '';
String? selectedFilePath;
double currentPosition = 0;
double totalDuration = 0;
// Start recording
Future<void> startRecording({String? rideId}) async {
final bool isPermissionGranted = await recorder.hasPermission();
if (!isPermissionGranted) {
return;
}
final directory = await getApplicationDocumentsDirectory();
final String dateStr =
'${DateTime.now().year}-${DateTime.now().month.toString().padLeft(2, '0')}-${DateTime.now().day.toString().padLeft(2, '0')}';
// Generate a unique file name
String fileName = (rideId != null && rideId.isNotEmpty && rideId != 'yet' && rideId != 'null')
? '${dateStr}_$rideId.m4a'
: '$dateStr.m4a';
filePath = '${directory.path}/$fileName';
const config = RecordConfig(
encoder: AudioEncoder.aacLc,
sampleRate: 44100,
bitRate: 128000,
);
await recorder.start(config, path: filePath);
isRecording = true;
update();
}
// Stop recording
Future<void> stopRecording() async {
await recorder.stop();
isRecording = false;
isPaused = false;
update();
}
// Get a list of recorded files
Future<List<String>> getRecordedFiles() async {
final directory = await getApplicationDocumentsDirectory();
final files = await directory.list().toList();
return files
.map((file) => file.path)
.where((path) => path.endsWith('.m4a'))
.toList();
}
// Delete a specific recorded file
Future<void> deleteRecordedFile(String filePath) async {
final file = File(filePath);
if (await file.exists()) {
await file.delete();
update();
}
}
@override
void onClose() {
audioPlayer.dispose();
recorder.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,225 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ui';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay;
import 'package:get_storage/get_storage.dart';
import 'package:geolocator/geolocator.dart' as geo;
import '../../constant/box_name.dart';
import '../firebase/local_notification.dart';
const String notificationChannelId = 'driver_service_channel';
const int notificationId = 888;
const String notificationIcon = '@mipmap/launcher_icon';
@pragma('vm:entry-point')
Future<bool> onStart(ServiceInstance service) async {
DartPluginRegistrant.ensureInitialized();
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
await GetStorage.init();
final box = GetStorage();
IO.Socket? socket;
String driverId = box.read(BoxName.driverID) ?? '';
String token = box.read(BoxName.tokenDriver) ?? '';
if (driverId.isNotEmpty) {
socket = IO.io(
'https://location.intaleq.xyz',
IO.OptionBuilder()
.setTransports(['websocket'])
.disableAutoConnect()
.setQuery({
'driver_id': driverId,
'token': token,
'EIO': '3', // توافقية مع Workerman
})
.setReconnectionAttempts(double.infinity)
.build());
socket.connect();
socket.onConnect((_) {
print("✅ Background Service: Socket Connected! ID: ${socket?.id}");
if (service is AndroidServiceInstance) {
flutterLocalNotificationsPlugin.show(
id: notificationId,
title: 'أنت متصل الآن',
body: 'بانتظار الطلبات...',
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId,
'خدمة السائق',
icon: notificationIcon,
ongoing: true,
importance: Importance.low,
priority: Priority.low,
),
),
);
}
});
socket.on('new_ride_request', (data) async {
print("🔔 Background Service: Received new_ride_request");
// 🔥 قراءة حالة التطبيق مباشرة قبل العرض
await GetStorage.init(); // تأكد من تحديث البيانات
final box = GetStorage();
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
// 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟ (للأندرويد فقط)
bool overlayActive = false;
if (Platform.isAndroid) {
overlayActive = await Overlay.FlutterOverlayWindow.isActive();
}
if (isAppInForeground || overlayActive) {
print("🛑 App is FOREGROUND or Overlay already shown. Skipping.");
return;
}
// عرض الـ Overlay (للأندرويد فقط)
if (Platform.isAndroid) {
print("🚀 App is BACKGROUND. Showing Overlay...");
try {
await Overlay.FlutterOverlayWindow.showOverlay(
enableDrag: true,
overlayTitle: "طلب جديد",
overlayContent: "لديك طلب جديد وصل للتو!",
flag: OverlayFlag.focusPointer,
positionGravity: PositionGravity.auto,
height: WindowSize.matchParent,
width: WindowSize.matchParent,
startPosition: const OverlayPosition(0, -30),
);
await Overlay.FlutterOverlayWindow.shareData(data);
} catch (e) {
print("Overlay Error: $e");
}
} else if (Platform.isIOS) {
// على iOS، نظهر إشعاراً عادياً لأن الـ Overlay غير موجود
flutterLocalNotificationsPlugin.show(
id: 1002,
title: "طلب رحلة جديد 🚖",
body: "لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه",
notificationDetails: const NotificationDetails(
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
),
),
payload: jsonEncode(data));
}
});
}
service.on('stopService').listen((event) {
socket?.clearListeners();
socket?.dispose();
service.stopSelf();
});
// 🔥 Location management in background isolate (Using Geolocator)
geo.Position? latestPos;
// Listen to location changes continuously in the background
geo.Geolocator.getPositionStream(
locationSettings: geo.AndroidSettings(
accuracy: geo.LocationAccuracy.high,
distanceFilter: 10,
intervalDuration: const Duration(seconds: 10),
),
).listen((pos) {
latestPos = pos;
});
// 🔥 MERCY HEARTBEAT: Send location every 2 minutes to keep driver active in 'raids'
Timer.periodic(const Duration(minutes: 2), (timer) async {
if (socket != null && socket.connected && latestPos != null) {
try {
socket.emit('update_location', {
'driver_id': driverId,
'lat': latestPos!.latitude,
'lng': latestPos!.longitude,
'heading': latestPos!.heading,
'speed': latestPos!.speed * 3.6,
'status': box.read(BoxName.statusDriverLocation) ?? 'on',
'source': 'background_heartbeat'
});
print(
"💓 Background Mercy Heartbeat Sent: ${latestPos!.latitude}, ${latestPos!.longitude}");
} catch (e) {
print("❌ Background Heartbeat Error: $e");
}
}
});
Timer.periodic(const Duration(seconds: 30), (timer) async {
if (service is AndroidServiceInstance) {
if (await service.isForegroundService()) {
flutterLocalNotificationsPlugin.show(
id: notificationId,
title: 'خدمة السائق نشطة',
body: 'بانتظار الطلبات...',
notificationDetails: const NotificationDetails(
android: AndroidNotificationDetails(
notificationChannelId,
'خدمة السائق',
icon: notificationIcon,
ongoing: true,
importance: Importance.low,
priority: Priority.low,
),
),
);
}
}
});
return true;
}
class BackgroundServiceHelper {
static Future<void> initialize() async {
final service = FlutterBackgroundService();
await service.configure(
androidConfiguration: AndroidConfiguration(
onStart: onStart,
autoStart: false,
isForegroundMode: true,
notificationChannelId: notificationChannelId,
initialNotificationTitle: 'تطبيق السائق',
initialNotificationContent: 'تجهيز الخدمة...',
foregroundServiceNotificationId: notificationId,
),
iosConfiguration: IosConfiguration(
autoStart: false,
onForeground: onStart,
onBackground: onStart,
),
);
}
static Future<void> startService() async {
final service = FlutterBackgroundService();
if (!await service.isRunning()) {
await service.startService();
}
}
static Future<void> stopService() async {
final service = FlutterBackgroundService();
service.invoke("stopService");
}
}

View File

@@ -0,0 +1,39 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class BatteryNotifier {
static final Battery _battery = Battery();
static int? _lastNotifiedLevel;
static Future<void> checkBatteryAndNotify() async {
try {
final int batteryLevel = await _battery.batteryLevel;
// ✅ لا تكرر الإشعار إذا الفرق قليل
if (_lastNotifiedLevel != null &&
(batteryLevel >= _lastNotifiedLevel! - 2)) return;
if (batteryLevel <= 30) {
Color backgroundColor = Colors.yellow;
if (batteryLevel <= 20) {
backgroundColor = Colors.red;
}
Get.snackbar(
"⚠️ تنبيه البطارية", // العنوان
"مستوى البطارية: $batteryLevel٪", // النص
snackPosition: SnackPosition.TOP,
backgroundColor: backgroundColor,
colorText: Colors.white,
duration: const Duration(seconds: 10), // مدة الظهور
margin: const EdgeInsets.all(10),
);
_lastNotifiedLevel = batteryLevel;
}
} catch (e) {
print('Battery check error: $e');
}
}
}

View File

@@ -0,0 +1,129 @@
// import 'package:SEFER/constant/api_key.dart';
// import 'package:SEFER/controller/functions/crud.dart';
// // import 'package:agora_rtc_engine/agora_rtc_engine.dart';
// import 'package:get/get.dart';
// import 'package:permission_handler/permission_handler.dart';
// import '../../constant/box_name.dart';
// import '../firebase/firbase_messge.dart';
// import '../home/captin/map_driver_controller.dart';
// import '../../main.dart';
// class CallController extends GetxController {
// String channelName = ''; // Get.find<MapDriverController>().rideId;
// String token = '';
// // int uid = int.parse(box.read(BoxName.phoneDriver)); // uid of the local user
// int uid = 0;
// int? remoteUid; // uid of the remote user
// bool _isJoined = false; // Indicates if the local user has joined the channel
// String status = '';
// // late RtcEngine agoraEngine; // Agora engine instance
// @override
// void onInit() {
// super.onInit();
// channelName = Get.find<MapDriverController>().rideId; // 'sefer300'; //
// remoteUid = int.parse(Get.find<MapDriverController>().passengerPhone);
// uid = int.parse(box.read(BoxName.phoneDriver));
// initAgoraFull();
// }
// initAgoraFull() async {
// await fetchToken();
// // Set up an instance of Agora engine
// setupVoiceSDKEngine();
// // join();
// FirebaseMessagesController().sendNotificationToPassengerTokenCALL(
// 'Call Income',
// '${'You have call from driver'.tr} ${box.read(BoxName.nameDriver)}',
// Get.find<MapDriverController>().tokenPassenger,
// [
// token,
// channelName,
// uid.toString(),
// remoteUid.toString(),
// ],
// );
// join();
// }
// @override
// void onClose() {
// // agoraEngine.leaveChannel();
// super.onClose();
// }
// // Future<void> setupVoiceSDKEngine() async {
// // // retrieve or request microphone permission
// // await [Permission.microphone].request();
// // //create an instance of the Agora engine
// // agoraEngine = createAgoraRtcEngine();
// // await agoraEngine.initialize(RtcEngineContext(appId: AK.agoraAppId));
// // // Register the event handler
// // agoraEngine.registerEventHandler(
// // RtcEngineEventHandler(
// // onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
// // // Get.snackbar(
// // // "Local user uid:${connection.localUid} joined the channel", '');
// // status = 'joined'.tr;
// // _isJoined = true;
// // update();
// // },
// // onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
// // // Get.snackbar("Remote user uid:$remoteUid joined the channel", '');
// // status = '${Get.find<MapDriverController>().passengerName} '
// // 'joined'
// // .tr;
// // remoteUid = remoteUid;
// // update();
// // },
// // onUserOffline: (RtcConnection connection, int? remoteUid,
// // UserOfflineReasonType reason) {
// // // Get.snackbar("Remote user uid:$remoteUid left the channel", '');
// // status = 'Call Left'.tr;
// // remoteUid = null;
// // update();
// // },
// // ),
// // );
// // }
// // void join() async {
// // // Set channel options including the client role and channel profile
// // ChannelMediaOptions options = const ChannelMediaOptions(
// // clientRoleType: ClientRoleType.clientRoleBroadcaster,
// // channelProfile: ChannelProfileType.channelProfileCommunication,
// // );
// // await agoraEngine.joinChannel(
// // token: token,
// // channelId: channelName,
// // options: options,
// // uid: uid,
// // );
// // }
// // void leave() {
// // _isJoined = false;
// // remoteUid = null;
// // update();
// // agoraEngine.leaveChannel();
// // }
// // // Clean up the resources when you leave
// // @override
// // void dispose() async {
// // await agoraEngine.leaveChannel();
// // super.dispose();
// // }
// fetchToken() async {
// var res = await CRUD()
// .getAgoraToken(channelName: channelName, uid: uid.toString());
// token = res;
// update();
// }
// }

View File

@@ -0,0 +1,238 @@
/*
import 'dart:convert';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:get/get.dart';
// import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:path/path.dart' as path;
import 'package:http/http.dart' as http;
import '../../main.dart';
class CameraClassController extends GetxController {
late CameraController cameraController;
late List<CameraDescription> cameras;
bool isCameraInitialized = false;
// final TextRecognizer _textRecognizer = TextRecognizer();
String? scannedText;
bool isloading = false;
@override
void onInit() {
super.onInit();
initializeCamera();
}
Future<void> initializeCamera() async {
try {
cameras = await availableCameras();
//update();
cameraController = CameraController(
cameras[0],
ResolutionPreset.medium,
enableAudio: false,
);
await cameraController.initialize();
isCameraInitialized = true;
update();
} catch (e) {
if (e is CameraException) {
switch (e.code) {
case 'CameraAccessDenied':
Get.defaultDialog(
title: 'Camera Access Denied.'.tr,
middleText: '',
confirm:
MyElevatedButton(title: 'Open Settings'.tr, onPressed: () {}),
);
break;
default:
// Handle other errors here.
break;
}
}
}
}
var imgUrl = '';
Future extractCardId() async {
// Construct the path for the image file
final directory = await path_provider.getTemporaryDirectory();
final imagePath =
path.join(directory.path, '${box.read(BoxName.driverID)}.png');
// Capture the image and save it to the specified path
final XFile capturedImage = await cameraController.takePicture();
// Move the captured image to the desired path
await capturedImage.saveTo(imagePath);
await uploadImage(File(capturedImage.path));
extractByAPI('${AppLink.server}/card_image/' + box.read(BoxName.driverID));
}
Future extractByAPI(String imgUrl) async {
var headers = {'apikey': 'K89368168788957'};
var request = http.MultipartRequest(
'POST', Uri.parse('https://api.ocr.space/parse/image'));
request.fields.addAll({
'language': 'ara',
'isOverlayRequired': 'false',
'url': imgUrl,
'iscreatesearchablepdf': 'false',
'issearchablepdfhidetextlayer': 'false'
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
} else {}
}
Future<String> uploadImage(File imageFile) async {
String? basicAuthCredentials =
await storage.read(key: BoxName.basicAuthCredentials);
var request = http.MultipartRequest(
'POST',
Uri.parse(AppLink.uploadImage),
);
// Attach the image file to the request
request.files.add(
await http.MultipartFile.fromPath('image', imageFile.path),
); // Add the headers to the request
request.headers.addAll({
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Basic ${base64Encode(utf8.encode(basicAuthCredentials.toString()))}',
});
// Add the driverID to the request
request.fields['driverID'] = box.read(BoxName.driverID);
// Send the request
var response = await request.send();
// Read the response
var responseData = await response.stream.toBytes();
var responseString = String.fromCharCodes(responseData);
scannedText = responseString;
update();
// Return the link received from the server
return responseString;
}
// Future<void> takePictureAndMLGoogleScan() async {
// try {
// // Construct the path for the image file
// final directory = await path_provider.getTemporaryDirectory();
// final imagePath =
// path.join(directory.path, '${box.read(BoxName.driverID)}.png');
// // Capture the image and save it to the specified path
// final XFile capturedImage = await cameraController.takePicture();
// // Move the captured image to the desired path
// await capturedImage.saveTo(imagePath);
// // Recognize the text in the image
// final InputImage inputImage =
// InputImage.fromFile(File(capturedImage.path));
// final RecognizedText recognizedText =
// await _textRecognizer.processImage(inputImage);
// scannedText = recognizedText.text;
// // Extract the scanned text line by line
// final List<Map<String, dynamic>> lines = [];
// for (var i = 0; i < recognizedText.blocks.length; i++) {
// lines.add({
// 'line_number': i,
// 'text': recognizedText.blocks[i].text,
// });
// }
// // Convert the list of lines to a JSON string
// final String jsonOutput = jsonEncode(lines);
// update();
// // Print the JSON output
// // Get.back();
// } catch (e) {}
// }
String getTextAsJSON(String text) {
final lines = text.split('\n');
final jsonList = lines.map((line) {
return {
'line_text': line,
'num_words': line.trim().split(' ').length,
};
}).toList();
final json = {
'lines': jsonList,
'num_lines': lines.length,
};
return jsonEncode(json);
}
List<String> getTextBlocks(String text) {
return text.split('\n');
}
// Future<void> takePictureAndTesseractScan() async {
// try {
// // Construct the path for the image file
// final directory = await path_provider.getTemporaryDirectory();
// final imagePath =
// path.join(directory.path, '${box.read(BoxName.driverID)}.png');
// // Capture the image and save it to the specified path
// final XFile capturedImage = await cameraController.takePicture();
// // Move the captured image to the desired path
// await capturedImage.saveTo(imagePath);
// // Recognize the text in the image
// final languages = [
// 'eng',
// 'ara'
// ]; // Specify the languages you want to use for text extraction
// final text = await FlutterTesseractOcr.extractText(imagePath,
// language: languages.join('+'), // Combine multiple languages with '+'
// args: {
// "psm": "4",
// "preserve_interword_spaces": "1",
// // "rectangle": const Rect.fromLTWH(100, 100, 200, 200),
// } // Additional options if needed
// );
// isloading = false;
// final jsonText = getTextAsJSON(text);
// final textBlocks = getTextBlocks(text);
// update();
// scannedText =
// textBlocks.toString(); // Convert the extracted text to JSON.
// // Print the JSON to the console.
// update();
// } catch (e) {
// scannedText = '';
// }
// }
@override
void onClose() {
cameraController.dispose();
super.onClose();
}
}
*/

View File

@@ -0,0 +1,736 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:siro_driver/controller/functions/encrypt_decrypt.dart';
import 'package:siro_driver/controller/functions/network/net_guard.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/main.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:siro_driver/env/env.dart';
import 'package:siro_driver/print.dart';
import '../../constant/api_key.dart';
import '../../views/widgets/error_snakbar.dart';
import 'gemeni.dart';
import 'upload_image.dart';
class CRUD {
final NetGuard _netGuard = NetGuard();
static bool _isRefreshingJWT = false;
static String _lastErrorSignature = '';
static DateTime _lastErrorTimestamp = DateTime(2000);
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
// ── فحص صلاحية JWT بدون مكتبات خارجية ──────────────────────
static bool _isJwtValid(String? token) {
if (token == null || token.isEmpty) return false;
try {
final parts = token.split('.');
if (parts.length != 3) return false;
// فك تشفير الـ payload (الجزء الثاني)
String payload = parts[1];
// إضافة padding للـ base64
switch (payload.length % 4) {
case 2: payload += '=='; break;
case 3: payload += '='; break;
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
final exp = decoded['exp'];
if (exp == null) return false;
// نعتبر التوكن منتهي قبل 30 ثانية من انتهاء الصلاحية (buffer)
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
} catch (_) {
return false;
}
}
static Future<void> addError(
String error, String details, String where) async {
try {
final currentErrorSignature = '$where-$error';
final now = DateTime.now();
if (currentErrorSignature == _lastErrorSignature &&
now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) {
return;
}
_lastErrorSignature = currentErrorSignature;
_lastErrorTimestamp = now;
final userId =
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
final userType =
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
CRUD().post(
link: AppLink.addError,
payload: {
'error': error.toString(),
'userId': userId.toString(),
'userType': userType,
'phone': phone.toString(),
'device': where,
'details': details,
},
);
} catch (e) {}
}
// ─────────────────────────────────────────────────────────────
// دالة مساعدة: يجيب البصمة المشفرة من GetStorage
// نفس القيمة المرسلة عند login وعُملها hash في JWT
// السيرفر يتحقق: sha256(X-Device-FP + FP_PEPPER) == JWT.fingerPrint
// ─────────────────────────────────────────────────────────────
String _getFpHeader() {
return box.read(BoxName.deviceFingerprint)?.toString() ?? '';
}
// ═══════════════════════════════════════════════════════════════
// _makeRequest — دالة مركزية لكل الطلبات
// ───────────────────────────────────────────────────────────────
// Retry logic للشبكات الضعيفة (سوريا):
// • 3 محاولات لأخطاء الشبكة (SocketException / TimeoutException)
// • انتظار 1 ثانية بين المحاولات لأخطاء SocketException
// • بدون انتظار لأخطاء Timeout (نعيد فوراً)
// ═══════════════════════════════════════════════════════════════
Future<dynamic> _makeRequest({
required String link,
Map<String, dynamic>? payload,
required Map<String, String> headers,
}) async {
// timeouts مرتفعة مناسبة للإنترنت الضعيف في سوريا
const totalTimeout = Duration(seconds: 60);
Future<http.Response> doPost() {
final url = Uri.parse(link);
return http
.post(url, body: payload, headers: headers)
.timeout(totalTimeout);
}
http.Response? response;
int attempts = 0;
final requestId = DateTime.now().millisecondsSinceEpoch.toString().substring(7);
Log.print('🚀 [REQ-$requestId] $link');
if (payload != null) Log.print('📦 [PAYLOAD-$requestId] $payload');
while (attempts < 3) {
try {
attempts++;
response = await doPost();
break; // نجح الاتصال — نخرج
} on SocketException catch (_) {
Log.print('⚠️ SocketException attempt $attempts$link');
if (attempts >= 3) {
_netGuard.notifyOnce((title, msg) => mySnackeBarError(msg));
return 'no_internet';
}
// انتظار قبل إعادة المحاولة — مهم للشبكات المتقطعة
await Future.delayed(const Duration(seconds: 1));
} on TimeoutException catch (_) {
Log.print('⚠️ TimeoutException attempt $attempts$link');
if (attempts >= 3) return 'failure';
// لا انتظار — نعيد فوراً
} catch (e) {
// errno = 9 (Bad file descriptor) — إعادة المحاولة
if (e.toString().contains('errno = 9') && attempts < 3) {
await Future.delayed(const Duration(milliseconds: 500));
continue;
}
addError(
'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
return 'failure';
}
}
// لو كل المحاولات فشلت بدون response
if (response == null) return 'failure';
final sc = response.statusCode;
final body = response.body;
Log.print('📥 [RES-$requestId] [$sc] $link');
Log.print('📄 [BODY-$requestId] $body');
// 2xx
if (sc >= 200 && sc < 300) {
try {
return jsonDecode(body);
} catch (e, st) {
addError(
'JSON Decode Error', 'Body: $body\n$st', 'CRUD._makeRequest $link');
return 'failure';
}
}
// 401 → تجديد التوكن (مع حماية من الحلقة اللانهائية)
if (sc == 401) {
// تخطي تجديد التوكن لـ endpoints غير حرجة (مثل تسجيل الأخطاء)
final isNonCritical = link.contains('errorApp.php');
if (!_isRefreshingJWT && !isNonCritical) {
_isRefreshingJWT = true;
try {
await Get.put(LoginDriverController()).getJWT();
} finally {
_isRefreshingJWT = false;
}
}
return 'token_expired';
}
// 5xx
if (sc >= 500) {
addError('Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
return 'failure';
}
return 'failure';
}
// ═══════════════════════════════════════════════════════════════
// post — طلب POST للسائق
// التغيير: إضافة X-Device-FP header
// ═══════════════════════════════════════════════════════════════
Future<dynamic> post({
required String link,
Map<String, dynamic>? payload,
}) async {
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
// فحص صلاحية التوكن قبل الإرسال — تجنب طلب مضمون الرفض
if (!_isJwtValid(token) && !_isRefreshingJWT) {
_isRefreshingJWT = true;
try {
await Get.put(LoginDriverController()).getJWT();
token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
} finally {
_isRefreshingJWT = false;
}
}
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
};
return await _makeRequest(link: link, payload: payload, headers: headers);
}
// ═══════════════════════════════════════════════════════════════
// get — طلب GET للسائق (يستخدم POST method)
// التغيير: إضافة X-Device-FP header + timeout مناسب لسوريا
// ═══════════════════════════════════════════════════════════════
Future<dynamic> get({
required String link,
Map<String, dynamic>? payload,
}) async {
try {
// فحص صلاحية التوكن قبل الإرسال
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
if (!_isJwtValid(token) && !_isRefreshingJWT) {
_isRefreshingJWT = true;
try {
await Get.put(LoginDriverController()).getJWT();
token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
} finally {
_isRefreshingJWT = false;
}
}
var url = Uri.parse(link);
var response = await http.post(
url,
body: payload,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(),
},
).timeout(const Duration(seconds: 60));
Log.print('get [$link]: ${response.statusCode}');
Log.print('get body: ${response.body}');
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') return response.body;
return jsonData['status'];
} else if (response.statusCode == 401) {
if (!_isRefreshingJWT) {
_isRefreshingJWT = true;
try {
await Get.put(LoginDriverController()).getJWT();
} finally {
_isRefreshingJWT = false;
}
}
return 'token_expired';
} else {
addError('Non-200: ${response.statusCode}', 'crud().get - Other',
url.toString());
return 'failure';
}
} on TimeoutException {
return 'failure';
} on SocketException {
return 'no_internet';
} catch (e) {
addError('GET Exception: $e', '', link);
return 'failure';
}
}
// ═══════════════════════════════════════════════════════════════
// postWallet — طلب POST للمحفظة
// التغيير: إضافة X-Device-FP header
// 3 headers: JWT + HMAC + FP
// ═══════════════════════════════════════════════════════════════
Future<dynamic> postWallet({
required String link,
Map<String, dynamic>? payload,
}) async {
var jwt = await LoginDriverController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $jwt',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
};
return await _makeRequest(link: link, payload: payload, headers: headers);
}
// ═══════════════════════════════════════════════════════════════
// getWallet — طلب GET للمحفظة (يستخدم POST method)
// التغيير: إضافة X-Device-FP header
// ═══════════════════════════════════════════════════════════════
Future<dynamic> getWallet({
required String link,
Map<String, dynamic>? payload,
}) async {
var s = await LoginDriverController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
var url = Uri.parse(link);
var response = await http.post(
url,
body: payload,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
},
).timeout(const Duration(seconds: 60));
if (response.statusCode == 200) {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') return response.body;
return jsonData['status'];
} else if (response.statusCode == 401) {
var jsonData = jsonDecode(response.body);
if (jsonData['error'] == 'Token expired') {
await Get.put(LoginDriverController()).getJwtWallet();
return 'token_expired';
}
addError('Unauthorized: ${jsonData['error']}', 'crud().getWallet - 401',
url.toString());
return 'failure';
} else {
addError('Non-200: ${response.statusCode}', 'crud().getWallet - Other',
url.toString());
return 'failure';
}
}
// ═══════════════════════════════════════════════════════════════
// postWalletMtn — طلب MTN للمحفظة
// التغيير: إضافة X-Device-FP header
// ═══════════════════════════════════════════════════════════════
Future<dynamic> postWalletMtn({
required String link,
Map<String, dynamic>? payload,
}) async {
final s = await LoginDriverController().getJwtWallet();
final hmac = box.read(BoxName.hmac);
final url = Uri.parse(link);
try {
final response = await http.post(
url,
body: payload,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $s',
'X-HMAC-Auth': hmac.toString(),
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
},
).timeout(const Duration(seconds: 60));
Map<String, dynamic> wrap(String status, {Object? message, int? code}) {
return {
'status': status,
'message': message,
'code': code ?? response.statusCode
};
}
if (response.statusCode == 200) {
try {
return jsonDecode(response.body);
} catch (e) {
return wrap('failure',
message: 'JSON decode error', code: response.statusCode);
}
} else if (response.statusCode == 401) {
try {
final jsonData = jsonDecode(response.body);
if (jsonData is Map && jsonData['error'] == 'Token expired') {
await Get.put(LoginDriverController()).getJWT();
return {
'status': 'failure',
'message': 'token_expired',
'code': 401
};
}
return wrap('failure', message: jsonData);
} catch (_) {
return wrap('failure', message: response.body);
}
} else {
try {
return wrap('failure', message: jsonDecode(response.body));
} catch (_) {
return wrap('failure', message: response.body);
}
}
} catch (e) {
return {
'status': 'failure',
'message': 'HTTP request error: $e',
'code': -1
};
}
}
// =======================================================================
// باقي الدوال الخارجية — لا تحتاج X-Device-FP (APIs خارجية)
// =======================================================================
Future<dynamic> getAgoraToken({
required String channelName,
required String uid,
}) async {
var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
var res = await http.get(
Uri.parse(
'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'),
headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'},
);
if (res.statusCode == 200) {
return jsonDecode(res.body)['token'];
}
}
Future<dynamic> getLlama({
required String link,
required String payload,
required String prompt,
}) async {
var url = Uri.parse(link);
var headers = {
'Content-Type': 'application/json',
'Authorization':
'Bearer LL-X5lJ0Px9CzKK0HTuVZ3u2u4v3tGWkImLTG7okGRk4t25zrsLqJ0qNoUzZ2x4ciPy',
};
var data = json.encode({
'model': 'Llama-3-70b-Inst-FW',
'messages': [
{
'role': 'user',
'content':
'Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload',
}
],
'temperature': 0.9,
});
var response = await http.post(url, body: data, headers: headers);
if (response.statusCode == 200) return response.body;
return response.statusCode;
}
Future allMethodForAI(String prompt, linkPHP, imagePath) async {
await ImageController().choosImage(linkPHP, imagePath);
Future.delayed(const Duration(seconds: 2));
var extractedString =
await arabicTextExtractByVisionAndAI(imagePath: imagePath);
var json = jsonDecode(extractedString);
var textValues = extractTextFromLines(json);
await Get.put(AI()).anthropicAI(textValues, prompt, imagePath);
}
String extractTextFromLines(Map<String, dynamic> jsonData) {
final readResult = jsonData['readResult'];
final blocks = readResult['blocks'];
final buffer = StringBuffer();
for (final block in blocks) {
for (final line in block['lines']) {
buffer.write(line['text']);
buffer.write('\n');
}
}
return buffer.toString().trim();
}
Future<dynamic> arabicTextExtractByVisionAndAI(
{required String imagePath}) async {
var headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': AK.ocpApimSubscriptionKey,
};
String imagePathFull =
'${AppLink.server}/card_image/$imagePath-${box.read(BoxName.driverID)}.jpg';
var request = http.Request(
'POST',
Uri.parse(
'https://eastus.api.cognitive.microsoft.com/computervision/imageanalysis:analyze?features=caption,read&model-version=latest&language=en&api-version=2024-02-01'),
);
request.body = json.encode({'url': imagePathFull});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200)
return await response.stream.bytesToString();
}
Future<dynamic> getChatGPT(
{required String link, required String payload}) async {
var url = Uri.parse(link);
var headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${Env.chatGPTkeySeferNew}',
};
var data = json.encode({
'model': 'gpt-3.5-turbo',
'messages': [
{
'role': 'user',
'content':
'Extract the desired information from the following passage as json decoded like vin,make,made,year,expiration_date,color,owner,registration_date just in this:\n\n$payload',
}
],
'temperature': 0.9,
});
var response = await http.post(url, body: data, headers: headers);
if (response.statusCode == 200) return response.body;
return response.statusCode;
}
Future<dynamic> postPayMob(
{required String link, Map<String, dynamic>? payload}) async {
var url = Uri.parse(link);
var response = await http.post(url,
body: payload, headers: {'Content-Type': 'application/json'});
var jsonData = jsonDecode(response.body);
if (response.statusCode == 200) {
if (jsonData['status'] == 'success') return response.body;
return jsonData['status'];
}
return response.statusCode;
}
// ── sendEmail — إصلاح: استخدام r() بدل X.r() القديم ─────────
Future<void> sendEmail(String link, Map<String, String>? payload) async {
// r() هي نفس دالة فك التشفير الثلاثي المختصرة
String token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
if (!_isJwtValid(token)) {
await LoginDriverController().getJWT();
token = r(box.read(BoxName.jwt)).toString().split(Env.addd)[0];
}
final headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Bearer $token',
'X-Device-FP': _getFpHeader(), // ← إثبات الجهاز
};
final request = http.Request('POST', Uri.parse(link));
request.bodyFields = payload ?? {};
request.headers.addAll(headers);
final response = await request.send();
if (response.statusCode != 200) {
final responseBody = await response.stream.bytesToString();
addError('sendEmail failed: ${response.statusCode}', responseBody,
'CRUD.sendEmail');
}
}
Future<dynamic> postFromDialogue(
{required String link, Map<String, dynamic>? payload}) async {
var url = Uri.parse(link);
var response = await http.post(
url,
body: payload,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
},
);
if (response.body.isNotEmpty) {
var jsonData = jsonDecode(response.body);
if (response.statusCode == 200 && jsonData['status'] == 'success') {
Get.back();
return response.body;
}
return jsonData['status'];
}
}
Future<void> sendVerificationRequest(String phoneNumber) async {
final accountSid = AK.accountSIDTwillo;
final authToken = AK.authTokenTwillo;
final verifySid = AK.twilloRecoveryCode;
await http.post(
Uri.parse(
'https://verify.twilio.com/v2/Services/$verifySid/Verifications'),
headers: {
'Authorization':
'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')),
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {'To': phoneNumber, 'Channel': 'sms'},
);
}
Future<dynamic> getGoogleApi(
{required String link, Map<String, dynamic>? payload}) async {
var url = Uri.parse(link);
var response = await http.post(url, body: payload);
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'OK') return jsonData;
return jsonData['status'];
}
Future<dynamic> update({
required String endpoint,
required Map<String, dynamic> data,
required String id,
}) async {
var url = Uri.parse('$endpoint/$id');
var response = await http.put(
url,
body: json.encode(data),
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}'
},
);
return json.decode(response.body);
}
Future<dynamic> delete({required String endpoint, required String id}) async {
var url = Uri.parse('$endpoint/$id');
var response = await http.delete(
url,
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}'
},
);
return json.decode(response.body);
}
Future<dynamic> getMapSaas({
required String link,
}) async {
var url = Uri.parse(link);
try {
var response = await http.get(
url,
headers: {
'Content-Type': 'application/json',
'x-api-key': Env.mapSaasKey,
},
);
Log.print('link -MapSaas: $link');
Log.print('response -MapSaas: ${response.body}');
if (response.statusCode == 200) {
return jsonDecode(response.body);
}
Log.print('MapSaas Error: ${response.statusCode} - ${response.body}');
return null;
} catch (e) {
Log.print('MapSaas Exception: $e');
return null;
}
}
Future<dynamic> postMapSaas({
required String link,
required Map<String, dynamic> payload,
}) async {
var url = Uri.parse(link);
try {
var response = await http.post(
url,
body: jsonEncode(payload),
headers: {
'Content-Type': 'application/json',
'x-api-key': Env.mapSaasKey,
},
);
Log.print('post -MapSaas link: $link');
Log.print('post -MapSaas payload: $payload');
Log.print('post -MapSaas response: ${response.body}');
if (response.statusCode == 200 || response.statusCode == 201) {
return jsonDecode(response.body);
}
Log.print('MapSaas Post Error: ${response.statusCode} - ${response.body}');
return null;
} catch (e) {
Log.print('MapSaas Post Exception: $e');
return null;
}
}
}
class NoInternetException implements Exception {
final String message;
NoInternetException(
[this.message =
'No internet connection. Please check your network and try again.']);
@override
String toString() => message;
}
class WeakNetworkException implements Exception {
final String message;
WeakNetworkException(
[this.message =
'Your network connection is too slow. Please try again later.']);
@override
String toString() => message;
}
class ApiException implements Exception {
final String message;
final int? statusCode;
ApiException(this.message, [this.statusCode]);
@override
String toString() =>
'ApiException: $message (Status Code: ${statusCode ?? 'N/A'})';
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
class LineChartPainter extends CustomPainter {
final List<double> data;
LineChartPainter(this.data);
@override
void paint(Canvas canvas, Size size) {
// Calculate the scale factor.
final scaleFactor = size.height / 240;
// Draw the line chart.
for (var i = 0; i < data.length - 1; i++) {
final x1 = i * size.width / data.length;
final y1 = data[i] * scaleFactor;
final x2 = (i + 1) * size.width / data.length;
final y2 = data[i + 1] * scaleFactor;
canvas.drawLine(Offset(x1, y1), Offset(x2, y2), Paint());
}
}
@override
bool shouldRepaint(LineChartPainter oldDelegate) => false;
}

View File

@@ -0,0 +1,205 @@
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'performance_test.dart'; // Make sure this path is correct
/// Analyzes various device hardware and software aspects to generate a compatibility score.
/// This class provides a standardized output for the UI to consume easily.
class DeviceAnalyzer {
final DeviceInfoPlugin _deviceInfo = DeviceInfoPlugin();
/// Reads the total RAM from the system's meminfo file.
/// Returns the value in Megabytes (MB).
Future<double> _readTotalRamMB() async {
try {
final file = File('/proc/meminfo');
if (!await file.exists()) return 0.0;
final lines = await file.readAsLines();
for (var line in lines) {
if (line.startsWith('MemTotal')) {
// Extracts the numeric value from the line.
final kb = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '0') ?? 0;
return kb / 1024.0; // Convert from Kilobytes to Megabytes
}
}
} catch (e) {
print('❌ Error reading total RAM: $e');
}
return 0.0;
}
/// Reads the current RAM usage percentage from the system's meminfo file.
Future<double> _readUsedRamPercent() async {
try {
final file = File('/proc/meminfo');
if (!await file.exists()) return 0.0;
final lines = await file.readAsLines();
int? total, available;
for (var line in lines) {
if (line.startsWith('MemTotal')) {
total = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
} else if (line.startsWith('MemAvailable')) {
available = int.tryParse(RegExp(r'\d+').stringMatch(line) ?? '');
}
}
if (total != null && available != null && total > 0) {
final used = total - available;
return (used / total) * 100.0;
}
} catch (e) {
print('❌ Error reading used RAM: $e');
}
return 0.0;
}
/// The main analysis function that runs all checks.
Future<Map<String, dynamic>> analyzeDevice() async {
List<Map<String, dynamic>> details = [];
if (!Platform.isAndroid) {
return {
'score': 0,
'details': [
{
'label': 'النظام غير مدعوم',
'status': false,
'achieved_score': 0,
'max_score': 100
}
]
};
}
final info = await _deviceInfo.androidInfo;
final data = info.data;
final features = List<String>.from(data['systemFeatures'] ?? []);
// 1. Android Version (Max: 10 points)
final version =
int.tryParse(info.version.release?.split('.').first ?? '0') ?? 0;
final int androidScore = version >= 9 ? 10 : 0;
details.add({
'label': 'إصدار أندرويد ${info.version.release}',
'status': androidScore > 0,
'achieved_score': androidScore,
'max_score': 10,
});
// 2. Total RAM (Max: 10 points)
final totalRam = await _readTotalRamMB();
int ramScore;
if (totalRam >= 8000) {
ramScore = 10;
} else if (totalRam >= 4000) {
ramScore = 5;
} else if (totalRam >= 3000) {
ramScore = 3;
} else {
ramScore = 0;
}
details.add({
'label': 'إجمالي الرام ${totalRam.toStringAsFixed(0)} ميجابايت',
'status': ramScore >= 5,
'achieved_score': ramScore,
'max_score': 10,
});
// 3. CPU Cores (Max: 10 points)
final cores = Platform.numberOfProcessors;
int coreScore = cores >= 6 ? 10 : (cores >= 4 ? 5 : 0);
details.add({
'label': 'أنوية المعالج ($cores)',
'status': coreScore >= 5,
'achieved_score': coreScore,
'max_score': 10,
});
// 4. Free Storage (Max: 5 points)
final freeBytes = data['freeDiskSize'] ?? 0;
final freeGB = freeBytes / (1024 * 1024 * 1024);
int storeScore = freeGB >= 5 ? 5 : (freeGB >= 2 ? 3 : 0);
details.add({
'label': 'المساحة الحرة ${freeGB.toStringAsFixed(1)} جيجابايت',
'status': storeScore >= 3,
'achieved_score': storeScore,
'max_score': 5,
});
// 5. GPS + Gyroscope Sensors (Max: 10 points)
bool okSensors = features.contains('android.hardware.location.gps') &&
features.contains('android.hardware.sensor.gyroscope');
final int sensorScore = okSensors ? 10 : 0;
details.add({
'label': 'حساسات GPS و Gyroscope',
'status': okSensors,
'achieved_score': sensorScore,
'max_score': 10,
});
// 6. Storage Write Speed (Max: 20 points)
final writeSpeed = await PerformanceTester.testStorageWriteSpeed();
int writeScore;
if (writeSpeed >= 30) {
writeScore = 20;
} else if (writeSpeed >= 15) {
writeScore = 15;
} else if (writeSpeed >= 5) {
writeScore = 10;
} else {
writeScore = 5;
}
details.add({
'label': 'سرعة الكتابة (${writeSpeed.toStringAsFixed(1)} MB/s)',
'status': writeScore >= 10,
'achieved_score': writeScore,
'max_score': 20,
});
// 7. CPU Compute Speed (Max: 20 points)
final cpuTime = await PerformanceTester.testCPUSpeed();
int cpuScore;
if (cpuTime <= 1.0) {
cpuScore = 20;
} else if (cpuTime <= 2.5) {
cpuScore = 15;
} else if (cpuTime <= 4.0) {
cpuScore = 10;
} else {
cpuScore = 5;
}
details.add({
'label': 'سرعة المعالجة (${cpuTime.toStringAsFixed(2)} ثانية)',
'status': cpuScore >= 10,
'achieved_score': cpuScore,
'max_score': 20,
});
// 8. Memory Pressure (Max: 15 points)
final usedPercent = await _readUsedRamPercent();
int memScore;
if (usedPercent <= 60) {
memScore = 15;
} else if (usedPercent <= 80) {
memScore = 10;
} else if (usedPercent <= 90) {
memScore = 5;
} else {
memScore = 0;
}
details.add({
'label': 'استخدام الرام الحالي (${usedPercent.toStringAsFixed(0)}%)',
'status': memScore >= 10,
'achieved_score': memScore,
'max_score': 15,
});
// Calculate the final total score by summing up the achieved scores.
final totalScore = details.fold<int>(
0, (sum, item) => sum + (item['achieved_score'] as int));
return {
'score': totalScore.clamp(0, 100),
'details': details,
};
}
}

View File

@@ -0,0 +1,80 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:get_storage/get_storage.dart';
import '../../constant/box_name.dart';
class DeviceInfo {
final String? manufacturer;
final String? model;
final String? deviceId;
final String? osVersion;
final String? platform;
final String? deviceName;
final bool? isPhysicalDevice;
DeviceInfo({
this.manufacturer,
this.model,
this.deviceId,
this.osVersion,
this.platform,
this.deviceName,
this.isPhysicalDevice,
});
Map<String, dynamic> toJson() => {
'manufacturer': manufacturer,
'model': model,
'deviceId': deviceId,
'osVersion': osVersion,
'platform': platform,
'deviceName': deviceName,
'isPhysicalDevice': isPhysicalDevice,
};
}
class DeviceController {
final box = GetStorage();
final _deviceInfo = DeviceInfoPlugin();
Future<DeviceInfo> getDeviceInfo() async {
if (Platform.isAndroid) {
return await _getAndroidDeviceInfo();
} else if (Platform.isIOS) {
return await _getIosDeviceInfo();
}
throw UnsupportedError('Unsupported platform');
}
Future<DeviceInfo> _getAndroidDeviceInfo() async {
final androidInfo = await _deviceInfo.androidInfo;
final deviceInfo = DeviceInfo(
manufacturer: androidInfo.manufacturer,
model: androidInfo.model,
deviceId: androidInfo.id,
osVersion: androidInfo.version.release,
platform: 'Android',
deviceName: androidInfo.device,
isPhysicalDevice: androidInfo.isPhysicalDevice,
);
box.write(BoxName.deviceInfo, deviceInfo.toJson());
return deviceInfo;
}
Future<DeviceInfo> _getIosDeviceInfo() async {
final iosInfo = await _deviceInfo.iosInfo;
final deviceInfo = DeviceInfo(
manufacturer: 'Apple',
model: iosInfo.model,
deviceId: iosInfo.identifierForVendor,
osVersion: iosInfo.systemVersion,
platform: 'iOS',
deviceName: iosInfo.name,
isPhysicalDevice: iosInfo.isPhysicalDevice,
);
box.write(BoxName.deviceInfo, deviceInfo.toJson());
return deviceInfo;
}
}

View File

@@ -0,0 +1,42 @@
import 'package:flutter/services.dart';
class DigitObscuringFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
final maskedText = maskDigits(newValue.text);
return newValue.copyWith(
text: maskedText,
selection: updateCursorPosition(maskedText, newValue.selection));
}
String maskDigits(String text) {
final totalDigits = text.length;
final visibleDigits = 4;
final hiddenDigits = totalDigits - visibleDigits * 2;
final firstVisibleDigits = text.substring(0, visibleDigits);
final lastVisibleDigits = text.substring(totalDigits - visibleDigits);
final maskedDigits = List.filled(hiddenDigits, '*').join();
return '$firstVisibleDigits$maskedDigits$lastVisibleDigits';
}
TextSelection updateCursorPosition(
String maskedText, TextSelection currentSelection) {
final cursorPosition = currentSelection.baseOffset;
final cursorOffset =
currentSelection.extentOffset - currentSelection.baseOffset;
final totalDigits = maskedText.length;
const visibleDigits = 4;
final hiddenDigits = totalDigits - visibleDigits * 2;
final updatedPosition = cursorPosition <= visibleDigits
? cursorPosition
: hiddenDigits + visibleDigits + (cursorPosition - visibleDigits);
return TextSelection.collapsed(
offset: updatedPosition, affinity: currentSelection.affinity);
}
}

View File

@@ -0,0 +1,41 @@
// import 'dart:io';
//
// import 'package:get/get.dart';
// import 'package:image_picker/image_picker.dart';
// import 'package:google_ml_kit/google_ml_kit.dart';
//
// class ImagePickerController extends GetxController {
// RxBool textScanning = false.obs;
// RxString scannedText = ''.obs;
//
// Future<void> getImage(ImageSource source) async {
// try {
// final pickedImage = await ImagePicker().pickImage(source: source);
// if (pickedImage != null) {
// textScanning.value = true;
// final imageFile = File(pickedImage.path);
// getRecognisedText(imageFile);
// }
// } catch (e) {
// textScanning.value = false;
// scannedText.value = "Error occurred while scanning";
// }
// }
//
// Future<void> getRecognisedText(File image) async {
// final inputImage = InputImage.fromFilePath(image.path);
// final textDetector = GoogleMlKit.vision.textRecognizer();
// final RecognizedText recognisedText =
// await textDetector.processImage(inputImage);
// await textDetector.close();
//
// scannedText.value = '';
// for (TextBlock block in recognisedText.blocks) {
// for (TextLine line in block.lines) {
// scannedText.value += line.text + '\n';
// }
// }
//
// textScanning.value = false;
// }
// }

View File

@@ -0,0 +1,32 @@
import 'dart:convert';
import 'package:encrypt/encrypt.dart' as encrypt;
import '../../constant/api_key.dart';
class KeyEncryption {
// استخدم مفتاح بطول 32 حرفًا
static final _key = encrypt.Key.fromUtf8(AK.keyOfApp);
static final _iv =
encrypt.IV.fromLength(16); // توليد تهيئة عشوائية بطول 16 بايت
static String encryptKey(String key) {
final encrypter =
encrypt.Encrypter(encrypt.AES(_key, mode: encrypt.AESMode.cbc));
final encrypted = encrypter.encrypt(key, iv: _iv);
final result = _iv.bytes + encrypted.bytes; // تضمين التهيئة مع النص المشفر
return base64Encode(result);
}
static String decryptKey(String encryptedKey) {
print('encryptedKey: ${AK.keyOfApp}');
final decoded = base64Decode(encryptedKey);
print('encryptedKey: $encryptedKey');
final iv = encrypt.IV(decoded.sublist(0, 16)); // استخراج التهيئة
final encrypted =
encrypt.Encrypted(decoded.sublist(16)); // استخراج النص المشفر
final encrypter =
encrypt.Encrypter(encrypt.AES(_key, mode: encrypt.AESMode.cbc));
return encrypter.decrypt(encrypted, iv: iv);
}
}

View File

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

View File

@@ -0,0 +1,95 @@
import 'dart:convert';
import 'dart:io';
import 'package:siro_driver/constant/api_key.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/main.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import '../../constant/links.dart';
import 'encrypt_decrypt.dart';
import 'upload_image.dart';
Future<String> faceDetector() async {
await ImageController().choosFace(AppLink.uploadEgypt, 'face_detect');
await Future.delayed(const Duration(seconds: 2));
var headers = {
// 'Authorization': 'Basic ${AK.basicCompareFaces}',
'Authorization': 'Basic hamza:12345678',
'Content-Type': 'application/json'
};
// var request = http.Request('POST', Uri.parse(//Todo
// 'https://face-detect-f6924392c4c7.herokuapp.com/compare_faces'));
var request = http.Request(
'POST', Uri.parse('https://mohkh.online:5000/compare_faces'));
request.body = json.encode({
"url1":
"${AppLink.seferCairoServer}/card_image/id_front-${(box.read(BoxName.driverID))}.jpg",
"url2":
"https://api.sefer.live/sefer/card_image/face_detect-${(box.read(BoxName.driverID))}.jpg"
});
print('request.body: ${request.body}');
request.headers.addAll(headers);
try {
http.Client client = await createHttpClient();
http.StreamedResponse response = await client.send(request);
// http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
String result = await response.stream.bytesToString();
print('result: ${result}');
return result;
} else {
print('Error: ${response.reasonPhrase}');
return 'Error: ${response.reasonPhrase}';
}
} catch (e) {
print('Exception occurred: $e');
return 'Error: $e';
}
}
Future<http.Client> createHttpClient() async {
final SecurityContext securityContext = SecurityContext();
HttpClient httpClient = HttpClient(context: securityContext);
httpClient.badCertificateCallback =
(X509Certificate cert, String host, int port) => true; // Bypass SSL
return IOClient(httpClient);
}
Future<String> faceDetector2(String url1, String url2) async {
var headers = {
'Authorization': 'Basic hamza:12345678',
'Content-Type': 'application/json'
};
var request = http.Request(
'POST', Uri.parse('https://mohkh.online:5000/compare_faces'));
request.body = json.encode({"url1": url1, "url2": url2});
request.headers.addAll(headers);
try {
http.Client client = await createHttpClient(); // Use custom client
DateTime startTime = DateTime.now();
http.StreamedResponse response = await client.send(request);
DateTime endTime = DateTime.now();
Duration duration = endTime.difference(startTime);
if (response.statusCode == 200) {
print(await response.stream.bytesToString());
print(duration.inSeconds);
return await response.stream.bytesToString();
} else {
print(await response.stream.bytesToString());
return 'Error: ${response.reasonPhrase}';
}
} catch (e) {
return 'Exception: $e';
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
import 'package:geolocator/geolocator.dart';
class GeoLocation {
Future<Position> getCurrentLocation() async {
bool serviceEnabled;
LocationPermission permission;
// Check if location services are enabled.
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Location services are not enabled, so we request the user to enable it.
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Permissions are denied, we cannot fetch the location.
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Permissions are denied forever, we cannot request permissions.
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
// When we reach here, permissions are granted and we can fetch the location.
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
}
}

View File

@@ -0,0 +1,123 @@
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
import 'package:get/get.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
void showInBrowser(String url) async {
if (await canLaunchUrl(Uri.parse(url))) {
launchUrl(Uri.parse(url));
} else {}
}
String cleanAndFormatPhoneNumber(String phoneNumber) {
// 1. Clean the number
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
// 2. Format logic (Syria/Egypt/International)
if (formattedNumber.length > 6) {
if (formattedNumber.startsWith('09')) {
formattedNumber = '+963${formattedNumber.substring(1)}';
} else if (formattedNumber.startsWith('01') && formattedNumber.length == 11) {
formattedNumber = '+20${formattedNumber.substring(1)}';
} else if (formattedNumber.startsWith('00')) {
formattedNumber = '+${formattedNumber.substring(2)}';
} else if (!formattedNumber.startsWith('+')) {
formattedNumber = '+$formattedNumber';
}
}
return formattedNumber;
}
Future<void> makePhoneCall(String phoneNumber) async {
String formattedNumber = cleanAndFormatPhoneNumber(phoneNumber);
if (!formattedNumber.startsWith('+963')) {
mySnackeBarError("Calling non-Syrian numbers is not supported".tr);
return;
}
// Create URI directly from String to avoid double encoding '+' as '%2B'
final Uri launchUri = Uri.parse('tel:$formattedNumber');
// 4. Execute with externalApplication mode
try {
if (!await launchUrl(launchUri, mode: LaunchMode.externalApplication)) {
throw 'Could not launch $launchUri';
}
} catch (e) {
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri);
} else {
print("Cannot launch url: $launchUri");
}
}
}
void launchCommunication(
String method, String contactInfo, String message) async {
String formattedContact = cleanAndFormatPhoneNumber(contactInfo);
// WhatsApp prefers the phone number without the '+' prefix
String whatsappContact = formattedContact.replaceAll('+', '');
String url;
if (Platform.isIOS) {
switch (method) {
case 'phone':
if (!formattedContact.startsWith('+963')) {
mySnackeBarError("Calling non-Syrian numbers is not supported".tr);
return;
}
url = 'tel:$formattedContact';
break;
case 'sms':
url = 'sms:$formattedContact?body=${Uri.encodeComponent(message)}';
break;
case 'whatsapp':
url =
'https://api.whatsapp.com/send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
break;
case 'email':
url =
'mailto:$formattedContact?subject=Subject&body=${Uri.encodeComponent(message)}';
break;
default:
return;
}
} else if (Platform.isAndroid) {
switch (method) {
case 'phone':
if (!formattedContact.startsWith('+963')) {
mySnackeBarError("Calling non-Syrian numbers is not supported".tr);
return;
}
url = 'tel:$formattedContact';
break;
case 'sms':
url = 'sms:$formattedContact?body=${Uri.encodeComponent(message)}';
break;
case 'whatsapp':
final bool whatsappInstalled =
await canLaunchUrl(Uri.parse('whatsapp://'));
if (whatsappInstalled) {
url =
'whatsapp://send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
} else {
url =
'https://api.whatsapp.com/send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
}
break;
case 'email':
url =
'mailto:$formattedContact?subject=Subject&body=${Uri.encodeComponent(message)}';
break;
default:
return;
}
} else {
return;
}
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {}
}

View File

@@ -0,0 +1,37 @@
import 'dart:convert';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/controller/functions/gemeni.dart';
class LlamaAi {
Future<Map> getCarRegistrationData(String input, prompt) async {
Map exrtatDataFinal = {};
String oneLine = input.replaceAll('\n', ' ');
// var res = await CRUD().getLlama(link: AppLink.gemini, payload: oneLine);
var res = await CRUD()
.getLlama(link: AppLink.llama, payload: oneLine, prompt: prompt);
var decod = jsonDecode(res.toString());
// exrtatDataFinal = jsonDecode(extractDataFromJsonString(decod['choices']));
extractDataFromJsonString(decod['choices'][0]['message']['content']);
return exrtatDataFinal;
}
String extractDataFromJsonString(String jsonString) {
// Remove any leading or trailing whitespace from the string
jsonString = jsonString.trim();
// Extract the JSON substring from the given string
final startIndex = jsonString.indexOf('{');
final endIndex = jsonString.lastIndexOf('}');
final jsonSubstring = jsonString.substring(startIndex, endIndex + 1);
// Parse the JSON substring into a Map
final jsonData = jsonDecode(jsonSubstring);
// Return the extracted data
return jsonEncode(jsonData);
}
}

View File

@@ -0,0 +1,85 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/src/extension_navigation.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import 'background_service.dart';
Future<void> requestNotificationPermission() async {
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt >= 33) {
// Android 13+
final status = await Permission.notification.request();
if (!status.isGranted) {
print('⚠️ إذن الإشعارات مرفوض');
return;
}
}
}
// بعد الحصول على الإذن، ابدأ الخدمة
await BackgroundServiceHelper.startService();
}
class PermissionsHelper {
/// طلب إذن الإشعارات على Android 13+
static Future<bool> requestNotificationPermission() async {
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt >= 33) {
final status = await Permission.notification.request();
if (status.isDenied) {
print('⚠️ إذن الإشعارات مرفوض');
mySnackbarWarning(
"يرجى منح صلاحية الإشعارات لضمان وصول الطلبات إليك");
return false;
}
if (status.isPermanentlyDenied) {
print('⚠️ إذن الإشعارات مرفوض بشكل دائم - افتح الإعدادات');
mySnackbarWarning('يرجى فتح الإعدادات وتفعيل صلاحية الإشعارات');
return false;
}
}
}
return true;
}
/// طلب جميع الإذونات المطلوبة
static Future<bool> requestAllPermissions() async {
// إذن الإشعارات (اختياري)
await requestNotificationPermission();
// 1. طلب إذن الموقع الأساسي فقط إذا كان مرفوضاً
var status = await Permission.location.status;
if (status.isDenied) {
status = await Permission.location.request();
}
if (status.isPermanentlyDenied) {
_showSettingsDialog('الموقع');
return false;
}
return status.isGranted || status.isLimited;
}
static void _showSettingsDialog(String permissionName) {
MyDialog().getDialog(
'صلاحية $permissionName مطلوبة',
'لقد قمت برفض صلاحية $permissionName سابقاً. يرجى تفعيلها من الإعدادات لتمكين التطبيق من العمل.',
() async {
await openAppSettings();
Get.back();
},
);
}
}

View File

@@ -0,0 +1,820 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:geolocator/geolocator.dart' as geo;
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:location/location.dart';
import 'package:battery_plus/battery_plus.dart';
import 'package:permission_handler/permission_handler.dart' as ph;
import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:siro_driver/constant/table_names.dart';
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../../print.dart';
import '../firebase/local_notification.dart';
import '../home/captin/home_captain_controller.dart';
import '../home/captin/map_driver_controller.dart';
import '../home/payment/captain_wallet_controller.dart';
import 'background_service.dart';
import 'crud.dart';
class LocationController extends GetxController with WidgetsBindingObserver {
// ===================================================================
// ====== Tunables ======
// ===================================================================
static const Duration recordIntervalNormal = Duration(seconds: 3);
static const Duration uploadBatchIntervalNormal = Duration(minutes: 2);
static const Duration recordIntervalPowerSave = Duration(seconds: 10);
static const Duration uploadBatchIntervalPowerSave = Duration(minutes: 5);
static const double lowWalletThreshold = -200;
static const int powerSaveTriggerLevel = 20;
static const int powerSaveExitLevel = 25;
// ===================================================================
// ====== Services & Variables ======
// ===================================================================
late final Location location = Location();
final Battery _battery = Battery();
IO.Socket? socket;
bool isSocketConnected = false;
Timer? _socketHeartbeat;
StreamSubscription<LocationData>? _locSub;
StreamSubscription<BatteryState>? _batterySub;
Timer? _recordTimer;
Timer? _uploadBatchTimer;
late final HomeCaptainController _homeCtrl;
late final CaptainWalletController _walletCtrl;
LatLng myLocation = LatLng(
box.read('last_lat') ?? 0.0,
box.read('last_lng') ?? 0.0,
);
double heading = box.read('last_heading') ?? 0.0;
double speed = 0.0;
double totalDistance = 0.0;
bool _isReady = false;
bool _isPowerSavingMode = false;
final List<Map<String, dynamic>> _trackBuffer = [];
final List<Map<String, dynamic>> _behaviorBuffer = [];
LatLng? _lastPosForDistance;
LatLng? _lastRecordedRealLoc;
DateTime? _lastRecordedTime;
LatLng? _lastSqlLoc;
double? _lastSpeed;
DateTime? _lastSpeedAt;
@override
Future<void> onInit() async {
super.onInit();
Log.print('🚀 LocationController Starting...');
// 1. Register Lifecycle Observer
WidgetsBinding.instance.addObserver(this);
box.write(BoxName.isAppInForeground, true);
// مراقب الحالة (Status Watcher)
box.listenKey(BoxName.statusDriverLocation, (value) {
if (value == 'blocked') {
Log.print("⛔ Driver is Blocked: Force Stopping Location Updates.");
stopLocationUpdates();
if (socket != null && socket!.connected) {
socket!.emit('update_location', {
'driver_id': box.read(BoxName.driverID),
'status': 'blocked',
'lat': myLocation.latitude,
'lng': myLocation.longitude,
'heading': heading,
'speed': speed * 3.6,
'distance': totalDistance
});
socket!.disconnect();
}
}
});
bool deps = await _awaitDependencies();
if (!deps) return;
_isReady = true;
initSocket();
await _initLocationSettings();
_listenToBatteryChanges();
if (box.read(BoxName.statusDriverLocation) != 'blocked') {
await startLocationUpdates();
}
Log.print('✅ LocationController Initialized.');
}
@override
void onClose() {
WidgetsBinding.instance.removeObserver(this);
box.write(BoxName.isAppInForeground, false);
stopLocationUpdates();
_batterySub?.cancel();
_stopHeartbeat();
socket?.dispose();
super.onClose();
}
// ===================================================================
// 🔥 Lifecycle Manager (Fixes Freeze & Background issues)
// ===================================================================
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
Log.print("📱 Lifecycle: App is in FOREGROUND");
box.write(BoxName.isAppInForeground, true);
// إيقاف خدمة الخلفية
BackgroundServiceHelper.stopService();
if (socket == null || (!socket!.connected && !_isInitializingSocket)) {
Log.print("🔄 Initializing Socket on resume...");
initSocket();
}
} else if (state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) {
Log.print("📱 Lifecycle: App is in BACKGROUND");
box.write(BoxName.isAppInForeground, false);
// تشغيل خدمة الخلفية للأندرويد لضمان بقاء التطبيق حياً
if (!Platform.isIOS) {
BackgroundServiceHelper.startService();
}
}
}
Future<bool> _awaitDependencies() async {
int attempts = 0;
while (attempts < 10) {
if (Get.isRegistered<HomeCaptainController>() &&
Get.isRegistered<CaptainWalletController>()) {
_homeCtrl = Get.find<HomeCaptainController>();
_walletCtrl = Get.find<CaptainWalletController>();
return true;
}
await Future.delayed(const Duration(milliseconds: 500));
attempts++;
}
return false;
}
// ===================================================================
// ====== Socket Logic (Improved) ======
// ===================================================================
bool _isInitializingSocket = false;
void initSocket() {
// منع الاستدعاءات المتداخلة التي تسبب قتل الاتصال قبل اكتماله
if (_isInitializingSocket) {
Log.print("⏳ Socket is already initializing. Skipping redundant call.");
return;
}
if (socket != null && socket!.connected) {
Log.print("✅ Socket is already connected. No need to re-init.");
return;
}
String driverId = box.read(BoxName.driverID).toString();
String token = box.read(BoxName.tokenDriver).toString();
String platform = Platform.isIOS ? 'ios' : 'android';
_isInitializingSocket = true;
// تنظيف السوكيت القديم فقط إذا كان موجوداً وغير متصل
if (socket != null) {
Log.print("🧹 Cleaning up old socket instance...");
socket!.clearListeners();
socket!.dispose();
socket = null;
}
Log.print(
"🟡 [LocationController] Initializing NEW Socket for Driver: $driverId");
try {
// العودة للـ Websocket حصراً لأنه الوحيد الذي ينجح في فتح القناة
socket = IO.io(
'https://location.intaleq.xyz',
IO.OptionBuilder()
.setTransports(['websocket'])
.setQuery({'driver_id': driverId, 'token': token, 'EIO': '3'})
.enableForceNew()
.build());
_setupSocketListeners();
socket!.connect();
} catch (e) {
_isInitializingSocket = false;
Log.print("❌ Socket Initialization Exception: $e");
}
}
void _setupSocketListeners() {
if (socket == null) return;
socket!.off('connect');
socket!.off('disconnect');
socket!.off('connect_error');
socket!.off('error');
socket!.onConnect((_) {
_isInitializingSocket = false;
// ننتظر قليلاً للتأكد من تعبئة الـ IDs
Future.delayed(const Duration(milliseconds: 1000), () {
String? sid = socket?.id;
String? eid = socket?.io.engine?.id;
Log.print(
'✅ Socket Connected! ID: ${sid ?? eid ?? 'N/A'} (SID: $sid, EID: $eid)');
if (sid != null || eid != null) {
isSocketConnected = true;
_startHeartbeat();
}
});
});
socket!.onDisconnect((data) {
_isInitializingSocket = false;
Log.print('❌ Socket Disconnected: $data');
isSocketConnected = false;
_stopHeartbeat();
});
socket!.onConnectError((err) {
_isInitializingSocket = false;
Log.print('❌ Socket Connect Error: $err');
});
socket!.onConnectTimeout((data) {
_isInitializingSocket = false;
Log.print('❌ Socket Connect Timeout: $data');
});
socket!.onError((err) {
_isInitializingSocket = false;
Log.print('❌ Socket General Error: $err');
});
socket!.on('reconnect_attempt', (attempt) {
Log.print('🔄 Socket Reconnecting... Attempt: $attempt');
});
// 🔥 الاستماع للطلبات الجديدة
socket!.on('new_ride_request', (data) {
Log.print("🔔 Socket: New Ride Request Arrived!");
// نستخدم Future.microtask لضمان عدم حظر الـ UI Thread
Future.microtask(() {
if (data != null) {
try {
List<dynamic> rawList = [];
if (data is String) {
var decoded = jsonDecode(data);
if (decoded is List) rawList = decoded;
} else if (data is List) {
if (data.isNotEmpty) {
rawList = (data[0] is List) ? data[0] : data;
}
}
if (rawList.isNotEmpty) {
Map<String, dynamic> convertedMap = {};
for (int i = 0; i < rawList.length; i++) {
convertedMap[i.toString()] = rawList[i];
}
handleIncomingOrder(convertedMap, "Socket");
}
} catch (e) {
Log.print("❌ Error processing socket data: $e");
}
}
});
});
// 🔥 الاستماع للإلغاء
socket!.on('cancel_ride', (data) {
Log.print("🚫 Socket: Ride Cancelled Event Received");
String reason = data['reason'] ?? 'No reason provided';
if (Get.isRegistered<MapDriverController>()) {
Get.find<MapDriverController>()
.processRideCancelledByPassenger(reason, source: "Socket");
}
});
}
// داخل LocationController
Future<void> handleIncomingOrder(
Map<String, dynamic> rideData, String source) async {
Log.print("📦 Socket Order Received from ($source)");
// 🔴 1. التحقق من حالة التطبيق قبل أي شيء 🔴
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
if (!isAppInForeground) {
Log.print(
"📱 [LocationController] Order received in background (iOS/Android). Source: $source");
if (Platform.isIOS) {
// على iOS، نقوم بإظهار إشعار محلي لأن الـ Overlay غير مدعوم
NotificationController().showNotification(
"طلب رحلة جديد 🚖",
"لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه",
jsonEncode(rideData),
'ding.wav');
}
return;
}
try {
// 2. التحقق من صحة البيانات
if (rideData.isEmpty || !rideData.containsKey('16')) {
Log.print("❌ Socket Error: Invalid Ride Data.");
return;
}
// 3. تجهيز البيانات (DriverList)
List<dynamic> driverList = [];
if (rideData.isNotEmpty) {
var sortedKeys = rideData.keys
.where((e) => int.tryParse(e) != null)
.map((e) => int.parse(e))
.toList()..sort();
for (var key in sortedKeys) {
driverList.add(rideData[key.toString()]);
}
}
// الحماية ضد البنية غير المكتملة
if (driverList.length <= 16) {
Log.print("❌ Socket Error: Parsed driver list is incomplete.");
return;
}
// 4. إغلاق النافذة (إن وجدت بالخطأ) والتنقل
try {
if (await TripOverlayPlugin.isOverlayActive()) {
Log.print("📲 Closing Overlay because App took control via Socket");
await TripOverlayPlugin.hideOverlay();
}
} catch (e) {
Log.print("Overlay check error: $e");
}
// 🔥 [Fix Active-Ride Guard] منع فتح صفحة الطلبات أثناء وجود السائق في رحلة نشطة
// هذا يمنع socket event جديد من تعطيل رحلة جارية
String? currentRideStatus = box.read(BoxName.rideStatus);
bool hasActiveRide = (currentRideStatus == 'Begin' ||
currentRideStatus == 'Apply' ||
currentRideStatus == 'Arrived');
String currentRoute = Get.currentRoute;
bool isOnMapPage = currentRoute.contains('MapPage') ||
currentRoute.contains('PassengerLocation');
if (hasActiveRide || isOnMapPage) {
Log.print(
"⛔ [LocationController] Ignoring new ride request — driver has active ride ($currentRideStatus) or is on map page ($currentRoute).");
return;
}
if (currentRoute != '/OrderRequestPage') {
Log.print("🚀 Socket: Navigating to OrderRequestPage...");
Get.toNamed('/OrderRequestPage', arguments: {
'myListString': jsonEncode(driverList),
'DriverList': driverList,
'body': 'New Trip Request via Socket ⚡'
});
} else {
Log.print(
"⚠️ User is already on OrderRequestPage. Skipping navigation.");
}
} catch (e) {
Log.print("❌ Socket Navigation Error: $e");
}
}
void _startHeartbeat() {
_socketHeartbeat?.cancel();
_socketHeartbeat = Timer.periodic(const Duration(seconds: 25), (timer) {
// [Fix 6] تخطي الإرسال إذا كان stream الموقع نشطاً.
// الـ _locSub يرسل update_location عند كل تحرك (كل 5-10 ثوانٍ) تلقائياً.
// الـ heartbeat يكون مفيداً فقط عندما يتوقف الـ stream (الجهاز ثابت أو أوقف الخدمة).
if (_locSub != null) return;
if (socket != null && isSocketConnected && myLocation.latitude != 0) {
emitLocationToSocket(myLocation, heading, speed);
}
});
}
void _stopHeartbeat() {
_socketHeartbeat?.cancel();
}
// In LocationController.dart
void emitLocationToSocket(LatLng pos, double head, double spd) {
String status = box.read(BoxName.statusDriverLocation) ?? 'on';
String? currentRideStatus = box.read(BoxName.rideStatus);
String? storedPassengerId = box.read(BoxName.passengerID);
String? storedRideId = box.read(BoxName.rideId);
// Basic payload
var payload = {
'driver_id': box.read(BoxName.driverID),
'lat': pos.latitude,
'lng': pos.longitude,
'heading': head,
'speed': spd * 3.6,
'status': status,
'distance': totalDistance,
};
// 🔥 القرار الذكي: حقن بيانات الراكب إذا كان هناك رحلة نشطة في الـ Box 🔥
bool hasActiveRide = (currentRideStatus == 'Begin' ||
currentRideStatus == 'Apply' ||
currentRideStatus == 'Arrived');
if (hasActiveRide && storedPassengerId != null) {
payload['passenger_id'] = storedPassengerId;
payload['ride_id'] = storedRideId;
}
// DebugLog.print to verify
//Log.print('🚀 Emitting Location: $payload');
if (socket != null && socket!.connected) {
socket!.emit('update_location', payload);
}
}
// ===================================================================
// ====== Tracking Logic ======
// ===================================================================
Future<void> startLocationUpdates() async {
_isReady = true;
String currentStatus = box.read(BoxName.statusDriverLocation) ?? 'off';
if (currentStatus == 'blocked') {
stopLocationUpdates();
return;
}
// Start background service
await BackgroundServiceHelper.startService();
if (socket == null || !socket!.connected) {
initSocket();
}
if (_locSub != null) return;
if (await _ensureServiceAndPermission()) {
_subscribeLocationStream();
_startBatchTimers();
}
}
Future<void> _subscribeLocationStream() async {
_locSub?.cancel();
int interval = _isPowerSavingMode ? 10000 : 5000;
await location.enableBackgroundMode(enable: true);
location.changeSettings(
accuracy: LocationAccuracy.navigation,
interval: interval,
distanceFilter: _isPowerSavingMode ? 20 : 10,
);
_locSub = location.onLocationChanged.listen((LocationData loc) async {
if (loc.latitude == null || loc.longitude == null) return;
final now = DateTime.now();
final pos = LatLng(loc.latitude!, loc.longitude!);
myLocation = pos;
speed = loc.speed ?? 0.0;
heading = loc.heading ?? 0.0;
box.write('last_lat', pos.latitude);
box.write('last_lng', pos.longitude);
box.write('last_heading', heading);
if (_lastPosForDistance != null) {
final d = _calculateDistance(_lastPosForDistance!, pos);
if (d > 5.0) totalDistance += d;
}
_lastPosForDistance = pos;
update();
emitLocationToSocket(pos, heading, speed);
if (Get.isRegistered<HomeCaptainController>()) {
final homeCtrl = Get.find<HomeCaptainController>();
if (homeCtrl.isActive &&
homeCtrl.mapHomeCaptainController != null &&
homeCtrl.isHomeMapActive &&
homeCtrl.isMapReadyForCommands) {
homeCtrl.mapHomeCaptainController?.animateCamera(
CameraUpdate.newLatLngZoom(pos, 17.5),
);
}
}
await _saveBehaviorIfMoved(pos, now, currentSpeed: speed);
}, onError: (e) => Log.print('❌ Location Stream Error: $e'));
}
Timer? _socketWatchdogTimer;
Future<void> stopLocationUpdates() async {
Log.print("🛑 Stopping Location Updates...");
_locSub?.cancel();
_locSub = null;
_recordTimer?.cancel();
_uploadBatchTimer?.cancel();
_socketHeartbeat?.cancel();
_socketWatchdogTimer?.cancel();
if (socket != null) {
socket!.clearListeners();
socket!.dispose();
}
if (!Platform.isIOS) {
await BackgroundServiceHelper.stopService();
}
socket = null;
isSocketConnected = false;
_isReady = false;
}
// ===================================================================
// ====== Batch Logic & Helpers ======
// ===================================================================
void _startBatchTimers() {
_recordTimer?.cancel();
_uploadBatchTimer?.cancel();
_socketWatchdogTimer?.cancel();
final recDur =
_isPowerSavingMode ? recordIntervalPowerSave : recordIntervalNormal;
final upDur = _isPowerSavingMode
? uploadBatchIntervalPowerSave
: uploadBatchIntervalNormal;
_recordTimer =
Timer.periodic(recDur, (_) => _recordCurrentLocationToBuffer());
_uploadBatchTimer = Timer.periodic(upDur, (_) => _flushBufferToServer());
// محاولة إعادة الاتصال بالسوكيت إذا انقطع كل 3 ثواني
_socketWatchdogTimer = Timer.periodic(const Duration(seconds: 3), (_) {
if (!isSocketConnected && !_isInitializingSocket) {
Log.print("🔄 Socket Watchdog: Attempting to reconnect socket...");
initSocket();
}
});
}
void _recordCurrentLocationToBuffer() {
if (myLocation.latitude == 0) return;
final now = DateTime.now();
double distFromLast = 0.0;
if (_lastRecordedRealLoc != null) {
distFromLast = _calculateDistance(_lastRecordedRealLoc!, myLocation);
}
bool moved = distFromLast > 10.0;
bool timeForced = _lastRecordedTime == null ||
now.difference(_lastRecordedTime!).inSeconds >= 60;
if ((moved && speed > 0.5) || timeForced) {
_lastRecordedRealLoc = myLocation;
_lastRecordedTime = now;
final point = {
'lat': double.parse(myLocation.latitude.toStringAsFixed(6)),
'lng': double.parse(myLocation.longitude.toStringAsFixed(6)),
'spd': double.parse((speed * 3.6).toStringAsFixed(1)),
'head': int.parse(heading.toStringAsFixed(0)),
'st': box.read(BoxName.statusDriverLocation) ?? 'off',
'ts': now.toIso8601String(),
};
_trackBuffer.add(point);
}
}
Future<void> _flushBufferToServer() async {
if (_trackBuffer.isEmpty) return;
int itemsToTake = _trackBuffer.length > 100 ? 100 : _trackBuffer.length;
List<Map<String, dynamic>> batch = _trackBuffer.sublist(0, itemsToTake);
final String driverId = (box.read(BoxName.driverID) ?? '').toString();
try {
var res = await CRUD().post(
link: '${AppLink.locationServer}/add_batch.php',
payload: {'driver_id': driverId, 'batch_data': jsonEncode(batch)},
);
if (res != 'failure') {
_trackBuffer.removeRange(0, itemsToTake);
} else {
_enforceBufferLimit();
}
} catch (e) {
Log.print('❌ Failed to upload batch: $e');
_enforceBufferLimit();
}
}
void _enforceBufferLimit() {
if (_trackBuffer.length > 500) {
_trackBuffer.removeRange(0, _trackBuffer.length - 500);
Log.print("⚠️ Buffer limit enforced. Removed oldest entries.");
}
}
void _listenToBatteryChanges() async {
_battery.onBatteryStateChanged.listen((state) async {
int level = await _battery.batteryLevel;
bool previousMode = _isPowerSavingMode;
if (level <= powerSaveTriggerLevel) _isPowerSavingMode = true;
if (level >= powerSaveExitLevel) _isPowerSavingMode = false;
if (previousMode != _isPowerSavingMode) {
_startBatchTimers();
_updateLocationSettings();
}
});
}
Future<void> _updateLocationSettings() async {
if (_locSub == null) return;
int interval = _isPowerSavingMode ? 10000 : 5000;
try {
await location.changeSettings(
accuracy: LocationAccuracy.navigation,
interval: interval,
distanceFilter: _isPowerSavingMode ? 20 : 10,
);
Log.print("🔋 Location settings updated. Power Save: $_isPowerSavingMode");
} catch (e) {
Log.print("❌ Failed to update location settings: $e");
}
}
Future<void> _saveBehaviorIfMoved(LatLng pos, DateTime now,
{required double currentSpeed}) async {
final dist =
(_lastSqlLoc == null) ? 999.0 : _calculateDistance(_lastSqlLoc!, pos);
if (dist < 15.0) return;
final accel = _calcAcceleration(currentSpeed, now) ?? 0.0;
_lastSqlLoc = pos;
_behaviorBuffer.add({
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
'latitude': pos.latitude,
'longitude': pos.longitude,
'acceleration': accel,
'created_at': now.toIso8601String(),
'updated_at': now.toIso8601String(),
});
if (_behaviorBuffer.length >= 10) {
_flushBehaviorBuffer();
}
}
void _flushBehaviorBuffer() {
if (_behaviorBuffer.isEmpty) return;
List<Map<String, dynamic>> batch = List.from(_behaviorBuffer);
_behaviorBuffer.clear();
Future.microtask(() async {
try {
for (var data in batch) {
await sql.insertData(data, TableName.behavior);
}
} catch (e) {
Log.print('SQLite Batch Insert Error: $e');
}
});
}
// استبدال دالة Haversine اليدوية بـ Geolocator في باقي الكود أيضاً
// لأنها تعتمد على C++ في الأندرويد و Obj-C في الآيفون (Native Speed)
double _calculateDistance(LatLng a, LatLng b) {
return geo.Geolocator.distanceBetween(
a.latitude, a.longitude, b.latitude, b.longitude);
}
double? _calcAcceleration(double currentSpeed, DateTime now) {
if (_lastSpeed != null && _lastSpeedAt != null) {
final dt = now.difference(_lastSpeedAt!).inMilliseconds / 1000.0;
if (dt > 0.5) {
final a = (currentSpeed - _lastSpeed!) / dt;
_lastSpeed = currentSpeed;
_lastSpeedAt = now;
return a;
}
}
_lastSpeed = currentSpeed;
_lastSpeedAt = now;
return null;
}
Future<void> _initLocationSettings() async {
if (await _ensureServiceAndPermission()) {
try {
await location.enableBackgroundMode(enable: true);
location.changeSettings(
accuracy: LocationAccuracy.navigation,
interval: 1000,
distanceFilter: 10);
} catch (e) {
Log.print("Warning: $e");
}
}
}
// 🔥🔥 هذه هي الدالة المعدلة التي تستخدم ph.Permission 🔥🔥
Future<bool> _ensureServiceAndPermission() async {
// 1. طلب إذن الإشعارات أولاً باستخدام permission_handler
if (Platform.isAndroid) {
var notificationStatus = await ph.Permission.notification.status;
if (!notificationStatus.isGranted) {
await ph.Permission.notification.request();
}
}
// 2. طلب تفعيل خدمة الموقع (GPS) من بكج location
bool serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) return false;
}
// 3. طلب إذن الموقع الأساسي من بكج location
PermissionStatus permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) return false;
}
return true;
}
// ... (باقي الكود)
Future<LocationData?> getLocation() async {
try {
if (await _ensureServiceAndPermission()) {
final locData = await location.getLocation();
if (locData != null && locData.latitude != null && locData.longitude != null) {
myLocation = LatLng(locData.latitude!, locData.longitude!);
heading = locData.heading ?? 0.0;
speed = locData.speed ?? 0.0;
box.write('last_lat', myLocation.latitude);
box.write('last_lng', myLocation.longitude);
box.write('last_heading', heading);
update();
if (Get.isRegistered<HomeCaptainController>()) {
final homeCtrl = Get.find<HomeCaptainController>();
if (homeCtrl.mapHomeCaptainController != null &&
homeCtrl.isMapReadyForCommands) {
Log.print("📍 [LocationController] Animating camera to single location update");
homeCtrl.mapHomeCaptainController?.animateCamera(
CameraUpdate.newLatLngZoom(myLocation, 17.5),
);
}
}
}
return locData;
}
} catch (e) {
Log.print('❌ FAILED to get single location: $e');
}
return null;
}
}

View File

@@ -0,0 +1,60 @@
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
import '../../print.dart';
import '../../views/widgets/mydialoug.dart';
import '../auth/captin/login_captin_controller.dart';
class LocationPermissions {
// late Location location;
// Future locationPermissions() async {
// location = Location();
// var permissionStatus = await location.requestPermission();
// if (permissionStatus == PermissionStatus.denied) {
// // The user denied the location permission.
// Get.defaultDialog(title: 'GPS Required Allow !.'.tr, middleText: '');
// return null;
// }
// }
}
Future<void> getPermissionLocation() async {
final PermissionStatus status = await Permission.locationAlways.status;
if (!await Permission.locationAlways.serviceStatus.isEnabled) {
Log.print('status.isGranted: ${status.isGranted}');
// box.write(BoxName.locationPermission, 'true');
await Permission.locationAlways.request();
Get.put(LoginDriverController()).update();
MyDialog().getDialog(
'Enable Location Permission'.tr, // {en:ar}
'Allowing location access will help us display orders near you. Please enable it now.'
.tr, // {en:ar}
() async {
Get.back();
box.write(BoxName.locationPermission, 'true');
await Permission.locationAlways.request();
},
);
}
}
Future<void> getPermissionLocation1() async {
PermissionStatus status = await Permission.locationWhenInUse.request();
if (status.isGranted) {
// After granting when in use, request "always" location permission
status = await Permission.locationAlways.request();
if (status.isGranted) {
print("Background location permission granted");
} else {
print("Background location permission denied");
}
} else {
print("Location permission denied");
await openAppSettings();
}
}

View File

@@ -0,0 +1,157 @@
import 'package:siro_driver/views/home/on_boarding_page.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:siro_driver/views/widgets/my_textField.dart';
import '../../constant/style.dart';
class LogOutController extends GetxController {
TextEditingController checkTxtController = TextEditingController();
final formKey = GlobalKey<FormState>();
final formKey1 = GlobalKey<FormState>();
final emailTextController = TextEditingController();
Future deleteMyAccountDriver(String id) async {
await CRUD().post(link: AppLink.removeUser, payload: {'id': id}).then(
(value) => Get.snackbar('Deleted'.tr, 'Your Account is Deleted',
backgroundColor: AppColor.redColor));
}
checkBeforeDelete() async {
var res = await CRUD().post(
link: AppLink.deletecaptainAccounr,
payload: {'id': box.read(BoxName.driverID)});
return res['message'][0]['id'];
}
deletecaptainAccount() {
Get.defaultDialog(
backgroundColor: AppColor.yellowColor,
title: 'Are you sure to delete your account?'.tr,
middleText:
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month ',
titleStyle: AppStyle.title,
content: Column(
children: [
Container(
width: Get.width,
decoration: AppStyle.boxDecoration,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month'
.tr,
style: AppStyle.title.copyWith(color: AppColor.redColor),
),
),
),
const SizedBox(
height: 20,
),
Form(
key: formKey,
child: SizedBox(
width: Get.width,
child: MyTextForm(
controller: checkTxtController,
label: 'Enter Your First Name'.tr,
hint: 'Enter Your First Name'.tr,
type: TextInputType.name,
),
))
],
),
confirm: MyElevatedButton(
title: 'Delete'.tr,
onPressed: () async {
if (checkTxtController.text == (box.read(BoxName.nameDriver))) {
// deletecaptainAccount();
var id = await checkBeforeDelete();
deleteMyAccountDriver(id);
} else {
mySnackeBarError('Your Name is Wrong'.tr);
}
}));
}
Future logOutPassenger() async {
Get.defaultDialog(
title: 'Are you Sure to LogOut?'.tr,
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
),
onPressed: () async {
// box.remove(BoxName.agreeTerms);
await box.erase();
await storage.deleteAll();
Get.offAll(OnBoardingPage());
},
child: Text(
'Sign Out'.tr,
style:
AppStyle.title.copyWith(color: AppColor.secondaryColor),
))
],
));
}
Future logOutCaptain() async {
Get.defaultDialog(
title: 'Are you Sure to LogOut?'.tr,
titleStyle: AppStyle.title,
content: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
),
onPressed: () async {
// box.remove(BoxName.agreeTerms);
await box.erase();
await storage.deleteAll();
Get.offAll(OnBoardingPage());
},
child: Text(
'Sign Out'.tr,
style:
AppStyle.title.copyWith(color: AppColor.secondaryColor),
))
],
));
}
deletePassengerAccount() async {
if (formKey1.currentState!.validate()) {
if (box.read(BoxName.email).toString() == emailTextController.text) {
await CRUD().post(link: AppLink.passengerRemovedAccountEmail, payload: {
'email': box.read(BoxName.email),
});
} else {
mySnackeBarError(
'Email you inserted is Wrong.'.tr,
);
}
}
}
}

View File

@@ -0,0 +1,48 @@
import 'dart:async';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'net_guard.dart';
typedef BodyEncoder = Future<http.Response> Function();
class HttpRetry {
/// ريتراي لـ network/transient errors فقط.
static Future<http.Response> sendWithRetry(
BodyEncoder send, {
int maxRetries = 3,
Duration baseDelay = const Duration(milliseconds: 400),
Duration timeout = const Duration(seconds: 12),
}) async {
// ✅ Pre-flight check for internet connection
if (!await NetGuard().hasInternet()) {
// Immediately throw a specific exception if there's no internet.
// This avoids pointless retries.
throw const SocketException("No internet connection");
}
int attempt = 0;
while (true) {
attempt++;
try {
final res = await send().timeout(timeout);
return res;
} on TimeoutException catch (_) {
if (attempt >= maxRetries) rethrow;
} on SocketException catch (_) {
if (attempt >= maxRetries) rethrow;
} on HandshakeException catch (_) {
if (attempt >= maxRetries) rethrow;
} on http.ClientException catch (e) {
// مثال: Connection reset by peer
final msg = e.message.toLowerCase();
final transient = msg.contains('connection reset') ||
msg.contains('broken pipe') ||
msg.contains('timed out');
if (!transient || attempt >= maxRetries) rethrow;
}
// backoff: 0.4s, 0.8s, 1.6s
final delay = baseDelay * (1 << (attempt - 1));
await Future.delayed(delay);
}
}
}

View File

@@ -0,0 +1,48 @@
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
class NetGuard {
static final NetGuard _i = NetGuard._();
NetGuard._();
factory NetGuard() => _i;
bool _notified = false;
/// فحص: (أ) فيه شبكة؟ (ب) فيه انترنت؟ (ج) السيرفر نفسه reachable؟
Future<bool> hasInternet({Uri? mustReach}) async {
final connectivity = await Connectivity().checkConnectivity();
if (connectivity == ConnectivityResult.none) return false;
final hasNet =
await InternetConnectionChecker.createInstance().hasConnection;
if (!hasNet) return false;
if (mustReach != null) {
try {
final host = mustReach.host;
final result = await InternetAddress.lookup(host);
if (result.isEmpty || result.first.rawAddress.isEmpty) return false;
// اختباري خفيف عبر TCP (80/443) — 400ms timeout
final port = mustReach.scheme == 'http' ? 80 : 443;
final socket = await Socket.connect(host, port,
timeout: const Duration(seconds: 1));
socket.destroy();
} catch (_) {
return false;
}
}
return true;
}
/// إظهار إشعار مرة واحدة ثم إسكات التكرارات
void notifyOnce(void Function(String title, String msg) show) {
if (_notified) return;
_notified = true;
show('لا يوجد اتصال بالإنترنت', 'تحقق من الشبكة ثم حاول مجددًا.');
// إعادة السماح بعد 15 ثانية
Future.delayed(const Duration(seconds: 15), () => _notified = false);
}
}

View File

@@ -0,0 +1,649 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:http/http.dart' as http;
import 'package:siro_driver/constant/api_key.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/info.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/constant/table_names.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../auth/captin/register_captin_controller.dart';
import 'launch.dart';
//
// class TextExtractionController extends GetxController {
// String extractedText = '';
// bool isloading = false;
// File? _scannedImage;
// // Convert the extracted text to JSON
// // Convert the extracted text to JSON
// String getTextAsJSON(String text) {
// final lines = text.split('\n');
// final jsonList = lines.map((line) {
// return {
// 'line_text': line,
// 'num_words': line.trim().split(' ').length,
// };
// }).toList();
//
// final json = {
// 'lines': jsonList,
// 'num_lines': lines.length,
// };
//
// return jsonEncode(json);
// }
//
// // Convert the extracted text to blocks by line
// List<String> getTextBlocks(String text) {
// return text.split('\n');
// }
//
// // Future<void> pickAndExtractText() async {
// // final pickedImage = await ImagePicker().pickImage(
// // source: ImageSource.camera,
// // preferredCameraDevice: CameraDevice.rear,
// // maxHeight: Get.height * .3,
// // maxWidth: Get.width * .8,
// // imageQuality: 99,
// // );
// // if (pickedImage != null) {
// // isloading = true;
// // update();
// // final imagePath = pickedImage.path;
// // final languages = [
// // 'eng',
// // 'ara'
// // ]; // Specify the languages you want to use for text extraction
//
// // try {
// // final text = await FlutterTesseractOcr.extractText(imagePath,
// // language:
// // languages.join('+'), // Combine multiple languages with '+'
// // args: {
// // "psm": "4",
// // "preserve_interword_spaces": "1",
// // // "rectangle": const Rect.fromLTWH(100, 100, 200, 200),
// // } // Additional options if needed
// // );
// // isloading = false;
// // final jsonText = getTextAsJSON(text);
// // final textBlocks = getTextBlocks(text);
// // update();
// // extractedText =
// // textBlocks.toString(); // Convert the extracted text to JSON.
//
// // // Print the JSON to the console.
// // update();
// // } catch (e) {
// // extractedText = '';
// // }
// // }
// // }
// }
// class TextMLGoogleRecognizerController extends GetxController {
// @override
// void onInit() {
// scanText();
// super.onInit();
// }
//
// // The ImagePicker instance
// final ImagePicker _imagePicker = ImagePicker();
//
// // The GoogleMlKit TextRecognizer instance
// final TextRecognizer _textRecognizer = TextRecognizer();
//
// // The scanned text
// String? scannedText;
// String? jsonOutput;
// final List<Map<String, dynamic>> lines = [];
//
// Map decode = {};
//
// Future<void> scanText() async {
// // Pick an image from the camera or gallery
// final XFile? image =
// await _imagePicker.pickImage(source: ImageSource.gallery);
//
// // If no image was picked, return
// if (image == null) {
// return;
// }
//
// // Convert the XFile object to an InputImage object
// final InputImage inputImage = InputImage.fromFile(File(image.path));
//
// // Recognize the text in the image
// final RecognizedText recognizedText =
// await _textRecognizer.processImage(inputImage);
// scannedText = recognizedText.text;
// Map extractedData = {};
// // Extract the scanned text line by line
// for (var i = 0; i < recognizedText.blocks.length; i++) {
// final block = recognizedText.blocks[i];
// for (final line in block.lines) {
// final lineText = line.text;
//
// if (lineText.contains('DL')) {
// final dlNumber = lineText.split('DL')[1].trim();
// extractedData['dl_number'] = dlNumber;
// }
// if (lineText.contains('USA')) {
// final usa = lineText.split('USA')[1].trim();
// extractedData['USA'] = usa;
// }
// if (lineText.contains('DRIVER LICENSE')) {
// final driverl = lineText;
// extractedData['DRIVER_LICENSE'] = driverl;
// }
//
// if (lineText.contains('EXP')) {
// final expiryDate = lineText.split('EXP')[1].trim();
// extractedData['expiry_date'] = expiryDate;
// }
//
// if (lineText.contains('DOB')) {
// final dob = lineText.split('DOB')[1].trim();
// extractedData['dob'] = dob;
// }
//
// if (lineText.contains("LN")) {
// if ((lineText.indexOf("LN") == 0)) {
// final lastName = lineText.split('LN')[1].trim();
// extractedData['lastName'] = lastName;
// }
// }
// if (lineText.contains("FN")) {
// final firstName = lineText.split('FN')[1].trim();
// extractedData['firstName'] = firstName;
// }
// if (lineText.contains("RSTR")) {
// final rstr = lineText.split('RSTR')[1].trim();
// extractedData['rstr'] = rstr;
// }
// if (lineText.contains("CLASS")) {
// final class1 = lineText.split('CLASS')[1].trim();
// extractedData['class'] = class1;
// }
// if (lineText.contains("END")) {
// final end = lineText.split('END')[1].trim();
// extractedData['end'] = end;
// }
// if (lineText.contains("DD")) {
// final dd = lineText.split('DD')[1].trim();
// extractedData['dd'] = dd;
// }
// if (lineText.contains("EYES")) {
// final eyes = lineText.split('EYES')[1].trim();
// extractedData['eyes'] = eyes;
// }
// if (lineText.contains("SEX")) {
// final parts = lineText.split("SEX ")[1];
// extractedData['sex'] = parts[0];
// }
// if (lineText.contains("HAIR")) {
// final hair = lineText.split('HAIR')[1].trim();
// extractedData['hair'] = hair;
// }
//
// if (lineText.contains('STREET') || lineText.contains(',')) {
// final address = lineText;
// extractedData['address'] = address;
// }
//
// // Repeat this process for other relevant data fields
// }
// }
//
// // Convert the list of lines to a JSON string
// jsonOutput = jsonEncode(extractedData);
// decode = jsonDecode(jsonOutput!);
//
// update();
// }
// }
class ScanDocumentsByApi extends GetxController {
bool isLoading = false;
Map<String, dynamic> responseMap = {};
final ImagePicker imagePicker = ImagePicker();
late Uint8List imagePortrait;
late Uint8List imageSignature;
late Uint8List imageDocumentFrontSide;
XFile? image;
XFile? imagePortraitFile;
XFile? imageFace;
late File tempFile;
late String imagePath;
DateTime now = DateTime.now();
late String name;
late String licenseClass;
late String documentNo;
late String address;
late String stateCode;
late String height;
late String sex;
late String postalCode;
late String dob;
late String expireDate;
// ///////////////////////
// late CameraController cameraController;
// late List<CameraDescription> cameras;
// bool isCameraInitialized = false;
// // final TextRecognizer _textRecognizer = TextRecognizer();
// String? scannedText;
// Future<void> initializeCamera(int cameraID) async {
// try {
// cameras = await availableCameras();
// //update();
// cameraController = CameraController(
// cameras[cameraID],
// ResolutionPreset.medium,
// enableAudio: false,
// );
// await cameraController.initialize();
// isCameraInitialized = true;
// update();
// } catch (e) {
// if (e is CameraException) {
// switch (e.code) {
// case 'CameraAccessDenied':
// Get.defaultDialog(
// title: 'Camera Access Denied.'.tr,
// middleText: '',
// confirm:
// MyElevatedButton(title: 'Open Settings', onPressed: () {}),
// );
// break;
// default:
// // Handle other errors here.
// break;
// }
// }
// }
// }
///
Future<void> scanDocumentsByApi() async {
// String? visionApi = await storage.read(key: BoxName.visionApi);
// String? visionApi = AK.visionApi;
// Pick an image from the camera or gallery
image = await imagePicker.pickImage(source: ImageSource.camera); //
// If no image was picked, return
if (image == null) {
return;
}
isLoading = true;
update();
var headers = {'X-BLOBR-KEY': AK.visionApi};
var request = http.MultipartRequest('POST',
Uri.parse('https://api.faceonlive.com/j2y3q25y1b6maif1/api/iddoc'));
request.files.add(await http.MultipartFile.fromPath('image', image!.path));
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
String responseString = await response.stream.bytesToString();
responseMap = jsonDecode(responseString);
var ocrData = responseMap['data']['ocr'];
name = ocrData['name'].toString();
licenseClass = ocrData['dlClass'].toString();
documentNo = ocrData['documentNumber'].toString();
address = ocrData['address'].toString();
height = ocrData['height'].toString();
postalCode = ocrData['addressPostalCode'].toString();
sex = ocrData['sex'].toString();
stateCode = ocrData['addressJurisdictionCode'].toString();
expireDate = ocrData['dateOfExpiry'].toString();
dob = ocrData['dateOfBirth'].toString();
if (responseMap['data'] != null &&
responseMap['data']['image'] != null &&
responseMap['data']['image']['portrait'] != null) {
imagePortrait = base64Decode(responseMap['data']['image']['portrait']);
String tempPath = Directory.systemTemp.path;
tempFile = File('$tempPath/image.jpg');
await tempFile.writeAsBytes(imagePortrait);
imagePath = tempFile.path;
// imagePortraitFile=File(imagePath) ;
update();
} else {
// Handle error or provide a default value
}
if (responseMap['data']['image']['signature'] != null) {
imageSignature =
base64Decode(responseMap['data']['image']['signature']);
} else {
imageSignature = imagePortrait;
// Handle error or provide a default value
}
if (responseMap['data'] != null &&
responseMap['data']['image'] != null &&
responseMap['data']['image']['documentFrontSide'] != null) {
imageDocumentFrontSide =
base64Decode(responseMap['data']['image']['documentFrontSide']);
} else {
// Handle error or provide a default value
}
isLoading = false;
update();
} else {}
}
late int times;
Future checkMatchFaceApi() async {
sql.getAllData(TableName.faceDetectTimes).then((value) {
if (value.isEmpty || value == null) {
sql.insertData({'faceDetectTimes': 1}, TableName.faceDetectTimes);
sql.getAllData(TableName.faceDetectTimes).then((value) {
times = value[0]['faceDetectTimes'];
update();
});
} else {
if (times < 4) {
times++;
matchFaceApi();
sql.updateData(
{'faceDetectTimes': times}, TableName.faceDetectTimes, 1);
} else {
Get.defaultDialog(
barrierDismissible: false,
title: 'You have finished all times '.tr,
titleStyle: AppStyle.title,
middleText: 'if you want help you can email us here'.tr,
middleTextStyle: AppStyle.title,
cancel: MyElevatedButton(
title: 'Thanks'.tr,
kolor: AppColor.greenColor,
onPressed: () => Get.back(),
),
confirm: MyElevatedButton(
title: 'Email Us'.tr,
kolor: AppColor.yellowColor, //
onPressed: () {
launchCommunication('email', 'support@mobile-app.store',
'${'Hi'.tr} ${AppInformation.appName}\n${'I cant register in your app in face detection '.tr}');
Get.back();
},
));
}
}
});
}
Map res = {};
Future matchFaceApi() async {
// String? visionApi = await storage.read(key: BoxName.visionApi);
imageFace = await imagePicker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.front,
);
// If no image was picked, return
if (image == null) {
return;
}
final imageFile = File(imageFace!.path);
// Uint8List imageBytes = await imageFile.readAsBytes();
var headers = {'X-BLOBR-KEY': AK.visionApi};
var request = http.MultipartRequest(
'POST',
Uri.parse(
'https://api.faceonlive.com/sntzbspfsdupgid1/api/face_compare'));
request.files
.add(await http.MultipartFile.fromPath('image1', imageFile.path));
request.files.add(await http.MultipartFile.fromPath('image2', imagePath));
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
res = jsonDecode(await response.stream.bytesToString());
update();
res['data']['result'].toString().contains('No face detected in image')
? Get.defaultDialog(
barrierDismissible: false,
title: 'No face detected'.tr,
middleText: ''.tr,
titleStyle: AppStyle.title,
confirm: MyElevatedButton(
kolor: AppColor.yellowColor,
title: 'Back'.tr,
onPressed: () {
Get.back();
},
)) //
: Get.defaultDialog(
// barrierDismissible: false,
title: 'Image detecting result is '.tr,
titleStyle: AppStyle.title,
content: Column(
children: [
Text(
res['data']['result'].toString(),
style: res['data']['result'].toString() == 'Different'
? AppStyle.title.copyWith(color: AppColor.redColor)
: AppStyle.title.copyWith(color: AppColor.greenColor),
),
res['data']['result'].toString() == 'Different'
? Text(
'${'Be sure for take accurate images please\nYou have'.tr} $times ${'from 3 times Take Attention'.tr}',
style: AppStyle.title,
)
: Text(
'image verified'.tr,
style: AppStyle.title,
)
],
),
confirm: res['data']['result'].toString() == 'Different'
? MyElevatedButton(
title: 'Back'.tr,
onPressed: () => Get.back(),
kolor: AppColor.redColor,
)
: MyElevatedButton(
title: 'Next'.tr,
onPressed: () async {
RegisterCaptainController registerCaptainController =
Get.put(RegisterCaptainController());
await registerCaptainController.register();
await registerCaptainController.addLisence();
await uploadImagePortrate();
// Get.to(() => CarLicensePage());
},
// {
// await uploadImage(
// tempFile, AppLink.uploadImagePortrate);
// Get.to(() => CarLicensePage());
// },
kolor: AppColor.greenColor,
));
} else {}
}
Future<String> uploadImagePortrate() async {
isLoading = true;
update();
final String token = box.read(BoxName.jwt)?.toString().split(AppInformation.addd)[0] ?? '';
final String fingerPrint = box.read(BoxName.deviceFingerprint)?.toString() ?? '';
var request = http.MultipartRequest(
'POST',
Uri.parse(AppLink.uploadImagePortrate),
);
request.files.add(
http.MultipartFile.fromBytes('image', imagePortrait),
);
request.headers.addAll({
'Authorization': 'Bearer $token',
'X-Device-FP': fingerPrint,
});
request.fields['driverID'] = box.read(BoxName.driverID).toString();
var response = await request.send();
var responseData = await response.stream.toBytes();
var responseString = String.fromCharCodes(responseData);
isLoading = false;
update();
return responseString;
}
@override
void onInit() {
// scanDocumentsByApi();
// initializeCamera(0);
sql.getAllData(TableName.faceDetectTimes).then((value) {
if (value.isEmpty) {
times = 0;
update();
// sql.insertData({'faceDetectTimes': 1}, TableName.faceDetectTimes);
} else {
times = value[0]['faceDetectTimes'];
}
});
super.onInit();
}
}
// class PassportDataExtractor extends GetxController {
// @override
// void onInit() {
// extractPassportData();
// super.onInit();
// }
//
// final ImagePicker _imagePicker = ImagePicker();
// late final XFile? image;
// final TextRecognizer _textRecognizer = TextRecognizer();
//
// Future<Map<String, dynamic>> extractPassportData() async {
// image = await _imagePicker.pickImage(source: ImageSource.gallery);
// update();
// if (image == null) {
// throw Exception('No image picked');
// }
//
// final InputImage inputImage = InputImage.fromFile(File(image!.path));
// final RecognizedText recognisedText =
// await _textRecognizer.processImage(inputImage);
//
// final Map<String, dynamic> extractedData = {};
// final List<Map<String, dynamic>> extractedTextWithCoordinates = [];
//
// for (TextBlock block in recognisedText.blocks) {
// for (TextLine line in block.lines) {
// final String lineText = line.text;
// final Rect lineBoundingBox = line.boundingBox!;
//
// extractedTextWithCoordinates.add({
// 'text': lineText,
// 'boundingBox': {
// 'left': lineBoundingBox.left,
// 'top': lineBoundingBox.top,
// 'width': lineBoundingBox.width,
// 'height': lineBoundingBox.height,
// },
// });
//
// // if (lineText.contains('Passport Number')) {
// // final String passportNumber =
// // lineText.split('Passport Number')[1].trim();
// // extractedData['passportNumber'] = passportNumber;
// // }
// // if (lineText.contains('Given Names')) {
// // final String givenNames = lineText.split('Given Names')[1].trim();
// // extractedData['givenNames'] = givenNames;
// // }
// // if (lineText.contains('Surname')) {
// // final String surname = lineText.split('Surname')[1].trim();
// // extractedData['surname'] = surname;
// // }
// // if (lineText.contains('Nationality')) {
// // final String nationality = lineText.split('Nationality')[1].trim();
// // extractedData['nationality'] = nationality;
// // }
// // if (lineText.contains('Date of Birth')) {
// // final String dob = lineText.split('Date of Birth')[1].trim();
// // extractedData['dateOfBirth'] = dob;
// // }
// // Add more field extraction conditions as needed
// }
// }
//
// extractedData['extractedTextWithCoordinates'] =
// extractedTextWithCoordinates;
// return extractedData;
// }
// }
//
// class PassportDataController extends GetxController {
// PassportDataExtractor passportDataExtractor = PassportDataExtractor();
// List<Map<String, dynamic>> extractedTextWithCoordinates = [];
//
// Future<void> extractDataAndDrawBoundingBoxes() async {
// try {
// Map<String, dynamic> extractedData =
// await passportDataExtractor.extractPassportData();
// extractedTextWithCoordinates =
// extractedData['extractedTextWithCoordinates'];
// update(); // Notify GetX that the state has changed
// } catch (e) {
// }
// }
// }
//
// class BoundingBoxPainter extends CustomPainter {
// final List<Map<String, dynamic>> boundingBoxes;
//
// BoundingBoxPainter(this.boundingBoxes);
//
// @override
// void paint(Canvas canvas, Size size) {
// final Paint paint = Paint()
// ..color = Colors.red
// ..style = PaintingStyle.stroke
// ..strokeWidth = 2.0;
//
// for (Map<String, dynamic> boundingBox in boundingBoxes) {
// double left = boundingBox['left'];
// double top = boundingBox['top'];
// double width = boundingBox['width'];
// double height = boundingBox['height'];
//
// Rect rect = Rect.fromLTWH(left, top, width, height);
// canvas.drawRect(rect, paint);
// }
// }
//
// @override
// bool shouldRepaint(covariant CustomPainter oldDelegate) {
// return false;
// }
// }

View File

@@ -0,0 +1,114 @@
import 'dart:io';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/views/widgets/mydialoug.dart';
import 'package:flutter/material.dart';
import 'package:flutter_confetti/flutter_confetti.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:get/get.dart';
import 'package:location/location.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
import '../auth/captin/login_captin_controller.dart';
import '../home/payment/captain_wallet_controller.dart';
Future<void> getPermissionOverlay() async {
if (Platform.isAndroid) {
final bool status = await FlutterOverlayWindow.isPermissionGranted();
if (status == false) {
MyDialog().getDialog(
'Allow overlay permission'.tr,
'To display orders instantly, please grant permission to draw over other apps.'
.tr,
() async {
Get.back();
await FlutterOverlayWindow.requestPermission();
},
);
}
}
}
Future<void> showDriverGiftClaim(BuildContext context) async {
if (box.read(BoxName.is_claimed).toString() == '0' ||
box.read(BoxName.is_claimed) == null) {
MyDialog().getDialog(
'You have gift 300 SYP'.tr, 'This for new registration'.tr, () async {
Get.back();
var res = await CRUD().post(link: AppLink.updateDriverClaim, payload: {
'driverId': box.read(BoxName.driverID),
});
if (res != 'failure') {
Get.find<CaptainWalletController>()
.addDriverWallet('new driver', '300', '300');
Confetti.launch(
context,
options:
const ConfettiOptions(particleCount: 100, spread: 70, y: 0.6),
);
box.write(BoxName.is_claimed, '1');
}
});
}
}
Future<void> closeOverlayIfFound() async {
if (Platform.isAndroid) {
bool isOverlayActive = await FlutterOverlayWindow.isActive();
if (isOverlayActive) {
await FlutterOverlayWindow.closeOverlay();
}
}
}
final location = Location();
Future<void> getLocationPermission() async {
bool serviceEnabled;
PermissionStatus permissionGranted;
// Check if location services are enabled
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
// Location services are still not enabled, handle the error
return;
}
}
// Check if the app has permission to access location
permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
// Location permission is still not granted, handle the error
permissionGranted = await location.requestPermission();
return;
}
}
if (permissionGranted.toString() == 'PermissionStatus.granted') {
box.write(BoxName.locationPermission, 'true');
Get.find<LoginDriverController>().update();
}
// update();
}
Future<void> getOverLay(String myListString) async {
bool isOverlayActive = await FlutterOverlayWindow.isActive();
if (isOverlayActive) {
await FlutterOverlayWindow.closeOverlay();
}
await FlutterOverlayWindow.showOverlay(
enableDrag: true,
flag: OverlayFlag.focusPointer,
visibility: NotificationVisibility.visibilityPublic,
positionGravity: PositionGravity.auto,
height: 700,
width: WindowSize.matchParent,
startPosition: const OverlayPosition(0, -150),
);
await FlutterOverlayWindow.shareData(myListString);
}

View File

@@ -0,0 +1,361 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../constant/info.dart';
import '../../main.dart';
import '../../print.dart';
import 'encrypt_decrypt.dart';
Future<void> checkForUpdate(BuildContext context) async {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.buildNumber;
final version = packageInfo.version;
Log.print('version: $version');
print('currentVersion is : $currentVersion');
// Fetch the latest version from your server
String latestVersion = await getPackageInfo();
box.write(BoxName.packagInfo, version);
if (latestVersion.isNotEmpty && latestVersion != currentVersion) {
showUpdateDialog(context);
}
}
Future<String> getPackageInfo() async {
final response = await CRUD().get(link: AppLink.packageInfo, payload: {
"platform": Platform.isAndroid ? 'android' : 'ios',
"appName": AppInformation.appVersion,
});
if (response != 'failure') {
return jsonDecode(response)['message'][0]['version'];
}
return '';
}
void showUpdateDialog(BuildContext context) {
final String storeUrl = Platform.isAndroid
? 'https://play.google.com/store/apps/details?id=com.intaleq_driver'
: 'https://apps.apple.com/jo/app/intaleq-driver/id6482995159';
showGeneralDialog(
context: context,
barrierDismissible: false,
barrierColor: Colors.black.withOpacity(0.5),
pageBuilder: (_, __, ___) {
return BackdropFilter(
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
child: Center(
child: AlertDialog(
// Using AlertDialog for a more Material Design look
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(16)), // More rounded corners
elevation: 4, // Add a bit more elevation
contentPadding: EdgeInsets.zero, // Remove default content padding
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Image.asset(
'assets/images/logo.png',
height: 72, // Slightly larger logo
width: 72,
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Text(
'Update Available'.tr,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
// Use theme's title style
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
'A new version of the app is available. Please update to the latest version.'
.tr, // More encouraging message
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
// Use theme's body style
color: Colors.black87,
),
),
),
const Divider(height: 0),
Row(
children: [
Expanded(
child: TextButton(
// Using TextButton for "Cancel"
onPressed: () => Navigator.pop(context),
style: TextButton.styleFrom(
foregroundColor: Colors.grey,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(16),
),
),
),
child: Text('Cancel'.tr),
),
),
const SizedBox(
height: 48,
child: VerticalDivider(width: 0), // Using VerticalDivider
),
Expanded(
child: ElevatedButton(
// Using ElevatedButton for "Update"
onPressed: () async {
if (await canLaunchUrl(Uri.parse(storeUrl))) {
await launchUrl(Uri.parse(storeUrl));
}
if (context.mounted) Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColor
.primaryColor, // Use theme's primary color
foregroundColor: Theme.of(context)
.colorScheme
.onPrimary, // Use theme's onPrimary color
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(16),
),
),
),
child: Text('Update'.tr),
),
),
],
),
],
),
),
),
);
},
transitionBuilder: (_, animation, __, child) {
return ScaleTransition(
scale: CurvedAnimation(
parent: animation,
curve: Curves.easeOutCubic, // More natural curve
),
child: child,
);
},
);
}
class DeviceHelper {
static Future<String> getDeviceFingerprint() async {
await EncryptionHelper.initialize();
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
var deviceData;
try {
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
deviceData = androidInfo.toMap();
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
deviceData = iosInfo.toMap();
} else {
throw UnsupportedError('Unsupported platform');
}
final String deviceId = Platform.isAndroid
? deviceData['id'] ?? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
: deviceData['identifierForVendor'] ?? 'unknown';
final String deviceModel = deviceData['model'] ?? 'unknown';
final String fingerprint =
EncryptionHelper.instance.encryptData('${deviceId}_$deviceModel');
box.write(BoxName.deviceFingerprint, fingerprint);
return (fingerprint);
} catch (e) {
debugPrint('Error generating device fingerprint: $e');
throw Exception('Failed to generate device fingerprint: $e');
}
}
}
class SecurityHelper {
/// Performs security checks and handles potential risks
static Future<void> performSecurityChecks() async {
bool isNotTrust = false;
bool isJailBroken = false;
bool isRealDevice = true;
bool isOnExternalStorage = false;
bool checkForIssues = false;
bool isDevMode = false;
bool isTampered = false;
String bundleId = "";
try {
isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
isJailBroken = await JailbreakRootDetection.instance.isJailBroken;
isRealDevice = await JailbreakRootDetection.instance.isRealDevice;
// This method is only relevant/implemented for Android
if (Platform.isAndroid) {
isOnExternalStorage =
await JailbreakRootDetection.instance.isOnExternalStorage;
}
List<JailbreakIssue> issues =
await JailbreakRootDetection.instance.checkForIssues;
checkForIssues = issues.isNotEmpty;
isDevMode = await JailbreakRootDetection.instance.isDevMode;
// Get Bundle ID
PackageInfo packageInfo = await PackageInfo.fromPlatform();
bundleId = packageInfo.packageName;
if (bundleId.isNotEmpty) {
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
}
} catch (e) {
debugPrint("Error during security checks: $e");
// Consider handling specific exceptions, not just general errors.
}
// Save values to storage (using GetStorage)
await box.write('isNotTrust', isNotTrust); // Use await for write operations
await box.write('isTampered', isTampered); // Use await
await box.write('isJailBroken', isJailBroken); // Use await
// debugPrint("Security Check Results:");
// debugPrint("isNotTrust: $isNotTrust");
// debugPrint("isJailBroken: $isJailBroken");
// debugPrint("isRealDevice: $isRealDevice");
// debugPrint("isOnExternalStorage: $isOnExternalStorage");
// debugPrint("checkForIssues: $checkForIssues");
// debugPrint("isDevMode: $isDevMode");
// debugPrint("isTampered: $isTampered");
// debugPrint("Bundle ID: $bundleId"); // Print the bundle ID
// Check for security risks and potentially show a warning
if (isJailBroken || isRealDevice == false || isTampered) {
// print("security_warning".tr); //using easy_localization
// Use a more robust approach to show a warning, like a dialog:
_showSecurityWarning();
} else {
box.write(BoxName.security_check, 'passed');
}
}
/// Deletes all app data
static Future<void> clearAllData() async {
//await storage.deleteAll(); // What's 'storage'? Be specific. Likely GetStorage as well.
await box.erase(); // Clear GetStorage data
exit(0); // This will terminate the app. Be VERY careful with this.
}
// static void _showSecurityWarning() {
// // Show a dialog, navigate to an error screen, etc.
// // Example using Get.dialog (if you use GetX):
//
// Get.dialog(
// AlertDialog(
// title: Text("Security Warning".tr), // Or use localized string
// content: Text(
// "Potential security risks detected. The application may not function correctly."
// .tr), //Or use localized string
// actions: [
// TextButton(
// onPressed: () async {
// await storage.deleteAll();
// await box.erase();
// Get.back(); // Close the dialog
// // Or, if you really must, exit the app (but give the user a chance!)
// exit(0);
// },
// child: Text("OK"), // Or use a localized string
// ),
// ],
// ),
// barrierDismissible: false, // Prevent closing by tapping outside
// );
// }
static void _showSecurityWarning() {
// Use an RxInt to track the remaining seconds. This is the KEY!
RxInt secondsRemaining = 10.obs;
Get.dialog(
CupertinoAlertDialog(
title: Text("Security Warning".tr),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Text(
"Potential security risks detected. The application will close in @seconds seconds."
.trParams({
// Use trParams for placeholders
'seconds': secondsRemaining.value.toString(),
}),
// Wrap the Text widget in Obx
)),
SizedBox(height: 24), // More spacing before the progress bar
Obx(() => SizedBox(
width: double.infinity, // Make progress bar full width
child: CupertinoActivityIndicator(
// in case of loading
radius: 15,
animating: true,
))),
SizedBox(height: 8),
Obx(() => ClipRRect(
borderRadius: BorderRadius.circular(8), // Rounded corners
child: LinearProgressIndicator(
value: secondsRemaining.value / 10,
backgroundColor: Colors.grey.shade300, // Lighter background
valueColor: AlwaysStoppedAnimation<Color>(
CupertinoColors.systemRed), // iOS-style red
minHeight: 8, // Slightly thicker progress bar
),
)),
],
),
),
barrierDismissible: false,
);
Timer.periodic(Duration(seconds: 1), (timer) {
secondsRemaining.value--;
if (secondsRemaining.value <= 0) {
timer.cancel();
// Get.back();
_clearDataAndExit();
}
});
}
static Future<void> _clearDataAndExit() async {
await storage.deleteAll();
await box.erase();
exit(0); // Exit the app
print('exit');
}
}

View File

@@ -0,0 +1,43 @@
import 'dart:io';
class PerformanceTester {
/// ✅ فحص سرعة الكتابة إلى التخزين (Storage Write Speed) بوحدة MB/s
static Future<double> testStorageWriteSpeed() async {
try {
final tempDir = Directory.systemTemp;
final testFile = File('${tempDir.path}/speed_test.txt');
final data = List<int>.filled(1024 * 1024 * 5, 0); // 5MB
final stopwatch = Stopwatch()..start();
await testFile.writeAsBytes(data, flush: true);
stopwatch.stop();
await testFile.delete();
double seconds = stopwatch.elapsedMilliseconds / 1000;
if (seconds == 0) seconds = 0.001;
final speed = 5 / seconds;
return double.parse(speed.toStringAsFixed(2));
} catch (e) {
print("❌ Storage write error: $e");
return 0.0;
}
}
/// ✅ فحص سرعة المعالج (CPU Compute Speed) بوحدة الثواني
static Future<double> testCPUSpeed() async {
try {
final stopwatch = Stopwatch()..start();
double x = 0;
for (int i = 0; i < 100000000; i++) {
x += i * 0.000001;
}
stopwatch.stop();
return stopwatch.elapsedMilliseconds / 1000.0;
} catch (e) {
print("❌ CPU compute error: $e");
return 999.0;
}
}
}

View File

@@ -0,0 +1,8 @@
// import 'package:ride/controller/functions/crud.dart';
// class RemoveAccount {
// void removeAccount()async{
// var res=await CRUD().post(link: link)
// }
// }

View File

@@ -0,0 +1,25 @@
// import 'package:credit_card_scanner/credit_card_scanner.dart';
// import 'package:get/get.dart';
//
// class ScanIdCard extends GetxController {
// CardDetails? _cardDetails;
// CardScanOptions scanOptions = const CardScanOptions(
// scanCardHolderName: true,
// enableDebugLogs: true,
// validCardsToScanBeforeFinishingScan: 5,
// possibleCardHolderNamePositions: [
// CardHolderNameScanPosition.aboveCardNumber,
// ],
// );
//
// Future<void> scanCard() async {
// final CardDetails? cardDetails =
// await CardScanner.scanCard(scanOptions: scanOptions);
// if (cardDetails == null) {
// return;
// }
//
// _cardDetails = cardDetails;
// update();
// }
// }

View File

@@ -0,0 +1,100 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:siro_driver/controller/auth/captin/login_captin_controller.dart';
import 'package:siro_driver/controller/functions/encrypt_decrypt.dart';
import '../../constant/box_name.dart';
import '../../constant/char_map.dart';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../../print.dart';
import 'crud.dart';
class SecureStorage {
final FlutterSecureStorage _storage = const FlutterSecureStorage();
void saveData(String key, value) async {
await _storage.write(key: key, value: value);
}
Future<String?> readData(String boxName) async {
final String? value = await _storage.read(key: boxName);
return value;
}
}
const List<String> keysToFetch = [
'serverPHP',
'seferAlexandriaServer',
'seferPaymentServer',
'seferCairoServer',
'seferGizaServer',
];
class AppInitializer {
List<Map<String, dynamic>> links = [];
Future<void> initializeApp() async {
if (box.read(BoxName.jwt) == null) {
await LoginDriverController().getJWT();
} else {
String token = r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0];
bool isTokenValid = false;
try {
final parts = token.split('.');
if (parts.length == 3) {
String payload = parts[1];
switch (payload.length % 4) {
case 2: payload += '=='; break;
case 3: payload += '='; break;
}
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
final exp = decoded['exp'];
if (exp != null) {
isTokenValid = DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
}
}
} catch (_) {}
if (!isTokenValid) {
await LoginDriverController().getJWT();
}
}
// await getKey();
}
Future<void> getAIKey(String key1) async {
var res =
await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key1});
if (res != 'failure') {
var d = jsonDecode(res)['message'];
final rawValue = d[key1].toString();
// ✅ اكتبها في storage
await storage.write(key: key1, value: rawValue);
await Future.delayed(Duration.zero);
}
}
Future<void> getKey() async {
try {
var res =
await CRUD().get(link: AppLink.getLocationAreaLinks, payload: {});
if (res != 'failure') {
links = List<Map<String, dynamic>>.from(jsonDecode(res)['message']);
await box.write(BoxName.locationName, links);
await box.write(BoxName.basicLink, (links[0]['server_link']));
await box.write(links[2]['name'], (links[2]['server_link']));
await box.write(links[1]['name'], (links[3]['server_link']));
await box.write(links[3]['name'], (links[1]['server_link']));
await box.write(BoxName.paymentLink, (links[4]['server_link']));
}
} catch (e) {}
}
}

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
class SecurityChecks {
static const platform = MethodChannel(
'com.intaleq_driver/security'); // Choose a unique channel name
static Future<bool> isDeviceCompromised() async {
try {
final bool result = await platform
.invokeMethod('isNativeRooted'); // Invoke the native method
return result;
} on PlatformException catch (e) {
print("Failed to check security status: ${e.message}");
return true; // Treat platform errors as a compromised device (for safety)
}
}
static isDeviceRootedFromNative(BuildContext context) async {
bool compromised = await isDeviceCompromised();
if (compromised) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => AlertDialog(
title: Text("Security Warning".tr),
content: Text(
"Your device appears to be compromised. The app will now close."
.tr),
actions: [
TextButton(
onPressed: () {
SystemNavigator.pop(); // Close the app
},
child: Text("OK"),
),
],
),
);
} else {
box.write(BoxName.security_check, 'passed');
// Continue with normal app flow
print("Device is secure.");
}
}
}

View File

@@ -0,0 +1,104 @@
import 'dart:convert';
import 'package:siro_driver/constant/api_key.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/info.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/auth/captin/register_captin_controller.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../auth/captin/login_captin_controller.dart';
import 'encrypt_decrypt.dart';
class SmsEgyptController extends GetxController {
var headers = {'Content-Type': 'application/json'};
Future<String> getSender() async {
var res = await CRUD().get(link: AppLink.getSender, payload: {});
if (res != 'failure') {
var d = jsonDecode(res)['message'][0]['senderId'].toString();
return d;
} else {
return "Sefer Egy";
}
}
Future<dynamic> sendSmsEgypt(String phone) async {
String sender = await getSender();
var body = jsonEncode({"receiver": "2$phone"});
var res = await http.post(
Uri.parse(AppLink.sendSms),
body: body,
headers: headers,
);
if (jsonDecode(res.body)['message'].toString() != "Success") {
await CRUD().post(link: AppLink.updatePhoneInvalidSMS, payload: {
"phone_number":
('+2${Get.find<RegisterCaptainController>().phoneController.text}')
});
box.write(BoxName.phoneDriver,
('+2${Get.find<RegisterCaptainController>().phoneController.text}'));
box.write(BoxName.phoneVerified, '1');
await Get.put(LoginDriverController()).loginWithGoogleCredential(
box.read(BoxName.driverID).toString(),
(box.read(BoxName.emailDriver).toString()),
);
} else {
Get.defaultDialog(
title: 'You will receive code in sms message'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () {
Get.back();
}));
}
}
Future checkCredit(String phone, otp) async {
var res = await http.post(
Uri.parse(AppLink.checkCredit),
body: {
"username": AppInformation.appName,
"password": AK.smsPasswordEgypt,
},
headers: headers,
);
}
Future sendSmsWithValidaty(String phone, otp) async {
var res = await http.post(
Uri.parse(AppLink.checkCredit),
body: {
"username": 'Sefer',
"password": AK.smsPasswordEgypt,
"message": "This is an example SMS message.",
"language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
"sender": "Sefer", //"Kazumi", // todo add sefer sender name
"receiver": "2$phone",
"validity": "10",
"StartTime": DateTime.now().toString() // "1/1/2024 10:00:00"
},
headers: headers,
);
}
Future sendSmsStatus(String smsid) async {
var res = await http.post(
Uri.parse(AppLink.checkCredit),
body: {
"username": AppInformation.appName,
"password": AK.smsPasswordEgypt,
"smsid": smsid //"00b77dfc-5b8f-474d-9def-9f0158b70f98"
},
headers: headers,
);
}
}

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/style.dart';
class Toast {
static void show(BuildContext context, String message, Color color) {
final snackBar = SnackBar(
clipBehavior: Clip.antiAliasWithSaveLayer,
backgroundColor: color,
elevation: 3,
content: Text(
message,
style: AppStyle.title.copyWith(color: AppColor.secondaryColor),
),
behavior: SnackBarBehavior.floating,
animation: const AlwaysStoppedAnimation(1.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0), // Custom border radius
),
width: Get.width * .8,
// shape: const StadiumBorder(
// side: BorderSide(
// color: AppColor.secondaryColor,
// width: 1.0,
// style: BorderStyle.solid,
// )),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context).showSnackBar(
snackBar,
);
}
}

View File

@@ -0,0 +1,93 @@
import 'dart:io';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/main.dart';
class TextToSpeechController extends GetxController {
final FlutterTts flutterTts = FlutterTts();
bool isSpeaking = false;
@override
void onInit() {
super.onInit();
initTts();
}
@override
void onClose() {
flutterTts.stop();
super.onClose();
}
// --- 1. تهيئة المحرك بإعدادات قوية للملاحة ---
Future<void> initTts() async {
try {
// جلب اللغة المحفوظة أو استخدام العربية كافتراضي
String lang = box.read(BoxName.lang) ?? 'ar-SA';
// تصحيح صيغة اللغة إذا لزم الأمر
if (lang == 'ar') lang = 'ar-SA';
if (lang == 'en') lang = 'en-US';
await flutterTts.setLanguage(lang);
await flutterTts.setSpeechRate(0.5); // سرعة متوسطة وواضحة
await flutterTts.setVolume(1.0);
await flutterTts.setPitch(1.0);
// إعدادات خاصة لضمان عمل الصوت مع الملاحة (خاصة للآيفون)
if (Platform.isIOS) {
await flutterTts
.setIosAudioCategory(IosTextToSpeechAudioCategory.playback, [
IosTextToSpeechAudioCategoryOptions.mixWithOthers,
IosTextToSpeechAudioCategoryOptions.duckOthers
]);
}
// الاستماع لحالة الانتهاء
flutterTts.setCompletionHandler(() {
isSpeaking = false;
update();
});
flutterTts.setStartHandler(() {
isSpeaking = true;
update();
});
} catch (e) {
print("TTS Init Error: $e");
}
}
// --- 2. دالة التحدث (تقاطع الكلام القديم) ---
Future<void> speakText(String text) async {
if (text.isEmpty) return;
try {
// إيقاف أي كلام حالي لضمان نطق التوجيه الجديد فوراً (أهم للملاحة)
await flutterTts.stop();
var result = await flutterTts.speak(text);
if (result == 1) {
isSpeaking = true;
update();
}
} catch (error) {
// لا تعرض سناك بار هنا لتجنب إزعاج السائق أثناء القيادة
print('Failed to speak text: $error');
}
}
// --- 3. دالة الإيقاف (ضرورية لزر الكتم) ---
Future<void> stop() async {
try {
var result = await flutterTts.stop();
if (result == 1) {
isSpeaking = false;
update();
}
} catch (e) {
print("Error stopping TTS: $e");
}
}
}

View File

@@ -0,0 +1,22 @@
// import 'package:ride/constant/credential.dart';
// import 'package:twilio_flutter/twilio_flutter.dart';
//
// class TwilioSMS {
// TwilioFlutter twilioFlutter = TwilioFlutter(
// accountSid: AppCredintials.accountSIDTwillo,
// authToken: AppCredintials.authTokenTwillo,
// twilioNumber: '+962 7 9858 3052');
//
// Future<void> sendSMS({
// required String recipientPhoneNumber,
// required String message,
// }) async {
// try {
// await twilioFlutter.sendSMS(
// toNumber: recipientPhoneNumber,
// messageBody: message,
// );
// } catch (e) {
// }
// }
// }

View File

@@ -0,0 +1,537 @@
import 'dart:convert';
import 'dart:io';
import 'package:siro_driver/constant/api_key.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path/path.dart';
import 'package:image/image.dart' as img;
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart' as path_provider;
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/info.dart';
import '../../main.dart';
import '../../print.dart';
import 'encrypt_decrypt.dart';
class ImageController extends GetxController {
File? myImage;
bool isloading = false;
CroppedFile? croppedFile;
final picker = ImagePicker();
var image;
Future<img.Image> detectAndCropDocument(File imageFile) async {
img.Image? image = img.decodeImage(await imageFile.readAsBytes());
if (image == null) throw Exception('Unable to decode image');
int left = image.width, top = image.height, right = 0, bottom = 0;
// Threshold for considering a pixel as part of the document (adjust as needed)
const int threshold = 240;
for (int y = 0; y < image.height; y++) {
for (int x = 0; x < image.width; x++) {
final pixel = image.getPixel(x, y);
final luminance = img.getLuminance(pixel);
if (luminance < threshold) {
left = x < left ? x : left;
top = y < top ? y : top;
right = x > right ? x : right;
bottom = y > bottom ? y : bottom;
}
}
}
// Add a small padding
left = (left - 5).clamp(0, image.width);
top = (top - 5).clamp(0, image.height);
right = (right + 5).clamp(0, image.width);
bottom = (bottom + 5).clamp(0, image.height);
return img.copyCrop(image,
x: left, y: top, width: right - left, height: bottom - top);
}
Future<File> rotateImageIfNeeded(File imageFile) async {
img.Image croppedDoc = await detectAndCropDocument(imageFile);
// Check if the document is in portrait orientation
bool isPortrait = croppedDoc.height > croppedDoc.width;
img.Image processedImage;
if (isPortrait) {
// Rotate the image by 90 degrees clockwise
processedImage = img.copyRotate(croppedDoc, angle: 90);
} else {
processedImage = croppedDoc;
}
// Get temporary directory
final tempDir = await path_provider.getTemporaryDirectory();
final tempPath = tempDir.path;
// Create the processed image file
File processedFile = File('$tempPath/processed_image.jpg');
await processedFile.writeAsBytes(img.encodeJpg(processedImage));
return processedFile;
}
Future<File> rotateImage(File imageFile) async {
// Read the image file
img.Image? image = img.decodeImage(await imageFile.readAsBytes());
if (image == null) return imageFile;
// Rotate the image by 90 degrees clockwise
img.Image rotatedImage = img.copyRotate(image, angle: 90);
// Get temporary directory
final tempDir = await path_provider.getTemporaryDirectory();
final tempPath = tempDir.path;
// Create the rotated image file
File rotatedFile = File('$tempPath/rotated_image.jpg');
await rotatedFile.writeAsBytes(img.encodeJpg(rotatedImage));
return rotatedFile;
}
choosImage(String link, String imageType) async {
try {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) return;
image = File(pickedImage.path);
croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(
title: 'Cropper'.tr,
),
],
);
if (croppedFile == null) return;
myImage = File(croppedFile!.path);
isloading = true;
update();
// Rotate the compressed image
File processedImage = await rotateImageIfNeeded(File(croppedFile!.path));
File compressedImage = await compressImage(processedImage);
print('link =$link');
// Log.print('link: ${link}');
//n8u22456
await uploadImage(
compressedImage,
{
'driverID':
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
'imageType': imageType,
},
link,
);
} catch (e) {
print('Error in choosImage: $e');
mySnackeBarError('Image Upload Failed'.tr);
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
// backgroundColor: AppColor.primaryColor);
} finally {
isloading = false;
update();
}
}
choosImageNewCAr(String link, String imageType) async {
try {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.rear,
);
if (pickedImage == null) return;
image = File(pickedImage.path);
croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(
title: 'Cropper'.tr,
),
],
);
if (croppedFile == null) return;
myImage = File(croppedFile!.path);
isloading = true;
update();
// Rotate the compressed image
File processedImage = await rotateImageIfNeeded(File(croppedFile!.path));
File compressedImage = await compressImage(processedImage);
print('link =$link');
// Log.print('link: ${link}');
//n8u22456
await uploadNewCar(
compressedImage,
{
'driverID': box.read(BoxName.driverID) +
'_' +
DateTime.now().toIso8601String(),
'imageType': imageType,
},
link,
);
} catch (e) {
print('Error in choosImage: $e');
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
// backgroundColor: AppColor.primaryColor);
mySnackeBarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
// choosFaceFromDriverLicense(String link, String imageType) async {
// final pickedImage = await picker.pickImage(
// source: ImageSource.camera,
// preferredCameraDevice: CameraDevice.rear,
// );
// if (pickedImage == null) return;
// image = File(pickedImage.path);
// File? processedImage;
// // For face images, use face detection and cropping
// processedImage = await detectAndCropFace(image!);
// if (processedImage == null) {
// Get.snackbar('Face Detection Failed', 'No face detected in the image.');
// return;
// }
// isloading = true;
// update();
// File compressedImage = await compressImage(processedImage);
// try {
// await uploadImage(
// compressedImage,
// {
// 'driverID': box.read(BoxName.driverID).toString(),
// 'imageType': imageType
// },
// link,
// );
// } catch (e) {
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
// backgroundColor: AppColor.redColor);
// } finally {
// isloading = false;
// update();
// }
// }
choosFace(String link, String imageType) async {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.front,
);
if (pickedImage != null) {
image = File(pickedImage.path);
isloading = true;
update();
// Compress the image
File compressedImage = await compressImage(File(pickedImage.path));
// Save the picked image directly
// File savedImage = File(pickedImage.path);
print('link =$link');
try {
await uploadImage(
compressedImage,
{
'driverID':
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
'imageType': imageType
},
link,
);
} catch (e) {
mySnackeBarError('Image Upload Failed'.tr);
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
// backgroundColor: AppColor.redColor);
} finally {
isloading = false;
update();
}
}
}
uploadImage(File file, Map data, String link) async {
final String token = r(box.read(BoxName.jwt)).split(AppInformation.addd)[0];
final String fingerPrint = box.read(BoxName.deviceFingerprint)?.toString() ?? '';
var request = http.MultipartRequest('POST', Uri.parse(link));
Log.print('uploadImage -> $link');
request.headers.addAll({
'Authorization': 'Bearer $token',
'X-Device-FP': fingerPrint,
});
var length = await file.length();
var stream = http.ByteStream(file.openRead());
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
Log.print('uploadImage response [${res.statusCode}]: ${res.body}');
if (res.statusCode == 200) {
return jsonDecode(res.body);
} else {
throw Exception('Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
uploadNewCar(File file, Map data, String link) async {
final String token = r(box.read(BoxName.jwt)).split(AppInformation.addd)[0];
final String fingerPrint = box.read(BoxName.deviceFingerprint)?.toString() ?? '';
var request = http.MultipartRequest('POST', Uri.parse(link));
request.headers.addAll({
'Authorization': 'Bearer $token',
'X-Device-FP': fingerPrint,
});
var length = await file.length();
var stream = http.ByteStream(file.openRead());
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
Log.print('uploadNewCar response [${res.statusCode}]: ${res.body}');
if (res.statusCode == 200) {
return jsonDecode(res.body);
} else {
throw Exception('Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
choosImagePicture(String link, String imageType) async {
final pickedImage = await picker.pickImage(
source: ImageSource.gallery,
// preferredCameraDevice: CameraDevice.rear,
// maxHeight: Get.height * .3,
// maxWidth: Get.width * .9,
// imageQuality: 100,
);
image = File(pickedImage!.path);
croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
IOSUiSettings(
title: 'Cropper'.tr,
),
],
);
myImage = File(pickedImage.path);
isloading = true;
update();
// Save the cropped image
// File savedCroppedImage = File(croppedFile!.path);
File compressedImage = await compressImage(File(croppedFile!.path));
print('link =$link');
try {
var response = await uploadImage(
compressedImage,
{'driverID': (box.read(BoxName.driverID)), 'imageType': imageType},
link,
);
// Save the returned URL from the V3 backend to local storage
if (response != null && response['status'] == 'success' && response['message'] != null) {
if (response['message']['file_link'] != null) {
box.write(BoxName.driverPhotoUrl, response['message']['file_link'].toString());
}
}
} catch (e) {
Log.print('e: ${e}');
mySnackeBarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
uploadImagePicture(File file, Map data, String link) async {
final String token = r(box.read(BoxName.jwt)).split(AppInformation.addd)[0];
final String fingerPrint = box.read(BoxName.deviceFingerprint)?.toString() ?? '';
var request = http.MultipartRequest('POST', Uri.parse(link));
request.headers.addAll({
'Authorization': 'Bearer $token',
'X-Device-FP': fingerPrint,
});
var length = await file.length();
var stream = http.ByteStream(file.openRead());
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
Log.print('uploadImagePicture response [${res.statusCode}]: ${res.body}');
if (res.statusCode == 200) {
return jsonDecode(res.body);
} else {
throw Exception('Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
}
Future<File> compressImage(File file) async {
final dir = await path_provider.getTemporaryDirectory();
final targetPath = "${dir.absolute.path}/temp.jpg";
var result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
targetPath,
quality: 70,
minWidth: 1024,
minHeight: 1024,
);
return File(result!.path);
}
// Future<File> detectAndCropFace(File imageFile) async {
// final inputImage = InputImage.fromFilePath(imageFile.path);
// final options = FaceDetectorOptions(
// enableClassification: false,
// enableLandmarks: false,
// enableTracking: false,
// minFaceSize: 0.15,
// performanceMode: FaceDetectorMode.accurate,
// );
// final faceDetector = FaceDetector(options: options);
// try {
// final List<Face> faces = await faceDetector.processImage(inputImage);
// final image = img.decodeImage(await imageFile.readAsBytes());
// if (image == null) throw Exception('Unable to decode image');
// int left, top, width, height;
// if (faces.isNotEmpty) {
// // Face detected, crop around the face
// final face = faces[0];
// double padding = 0.2; // 20% padding
// int paddingX = (face.boundingBox.width * padding).round();
// int paddingY = (face.boundingBox.height * padding).round();
// left = (face.boundingBox.left - paddingX).round();
// top = (face.boundingBox.top - paddingY).round();
// width = (face.boundingBox.width + 2 * paddingX).round();
// height = (face.boundingBox.height + 2 * paddingY).round();
// } else {
// // No face detected, crop the center of the image
// int size = min(image.width, image.height);
// left = (image.width - size) ~/ 2;
// top = (image.height - size) ~/ 2;
// width = size;
// height = size;
// }
// // Ensure dimensions are within image bounds
// left = left.clamp(0, image.width - 1);
// top = top.clamp(0, image.height - 1);
// width = width.clamp(1, image.width - left);
// height = height.clamp(1, image.height - top);
// final croppedImage =
// img.copyCrop(image, x: left, y: top, width: width, height: height);
// // Save the cropped image
// final tempDir = await path_provider.getTemporaryDirectory();
// final tempPath = tempDir.path;
// final croppedFile = File('$tempPath/cropped_image.jpg');
// await croppedFile.writeAsBytes(img.encodeJpg(croppedImage, quality: 100));
// return croppedFile;
// } finally {
// faceDetector.close();
// }
// }

View File

@@ -0,0 +1,15 @@
import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
class HomePageController extends GetxController {
late bool isVibrate = box.read(BoxName.isvibrate) ?? true;
void changeVibrateOption(bool value) {
isVibrate = box.read(BoxName.isvibrate) ?? true;
isVibrate = value;
box.write(BoxName.isvibrate, value);
update();
}
}

View File

@@ -0,0 +1,298 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import '../../../main.dart';
// ════════════════════════════════════════════
// نموذج التحدي
// ════════════════════════════════════════════
class Challenge {
final String id;
final String titleEn;
final String titleAr;
final String descriptionEn;
final String descriptionAr;
final IconData icon;
final Color color;
final int target;
final int reward; // نقاط
final String type; // 'daily' or 'weekly'
final String metric; // 'trips', 'earnings', 'hours', 'acceptance_rate'
int currentProgress;
bool isClaimed;
Challenge({
required this.id,
required this.titleEn,
required this.titleAr,
required this.descriptionEn,
required this.descriptionAr,
required this.icon,
required this.color,
required this.target,
required this.reward,
required this.type,
required this.metric,
this.currentProgress = 0,
this.isClaimed = false,
});
double get progress => (currentProgress / target).clamp(0.0, 1.0);
bool get isCompleted => currentProgress >= target;
}
// ════════════════════════════════════════════
// Controller
// ════════════════════════════════════════════
class ChallengesController extends GetxController {
bool isLoading = false;
List<Challenge> dailyChallenges = [];
List<Challenge> weeklyChallenges = [];
@override
void onInit() {
super.onInit();
_generateChallenges();
_loadProgress();
fetchChallengeProgress();
}
void _generateChallenges() {
final now = DateTime.now();
final isWeekend = now.weekday == 5 || now.weekday == 6; // الجمعة والسبت
dailyChallenges = [
Challenge(
id: 'daily_trips_5',
titleEn: 'Road Runner',
titleAr: 'سائق سريع',
descriptionEn: 'Complete 5 trips today',
descriptionAr: 'أكمل 5 رحلات اليوم',
icon: Icons.local_taxi_rounded,
color: const Color(0xFF2196F3),
target: 5,
reward: 50,
type: 'daily',
metric: 'trips',
),
Challenge(
id: 'daily_trips_10',
titleEn: 'Marathon Driver',
titleAr: 'سائق الماراثون',
descriptionEn: 'Complete 10 trips today',
descriptionAr: 'أكمل 10 رحلات اليوم',
icon: Icons.directions_car_rounded,
color: const Color(0xFFFF9800),
target: 10,
reward: 150,
type: 'daily',
metric: 'trips',
),
Challenge(
id: 'daily_earnings',
titleEn: 'Money Maker',
titleAr: 'صانع المال',
descriptionEn: 'Earn 3000 SYP today',
descriptionAr: 'اربح 3000 ل.س اليوم',
icon: Icons.monetization_on_rounded,
color: const Color(0xFF4CAF50),
target: 3000,
reward: 100,
type: 'daily',
metric: 'earnings',
),
if (isWeekend)
Challenge(
id: 'daily_weekend_bonus',
titleEn: 'Weekend Warrior',
titleAr: 'محارب عطلة نهاية الأسبوع',
descriptionEn: 'Complete 8 trips on the weekend',
descriptionAr: 'أكمل 8 رحلات في عطلة نهاية الأسبوع',
icon: Icons.celebration_rounded,
color: const Color(0xFFE91E63),
target: 8,
reward: 200,
type: 'daily',
metric: 'trips',
),
];
weeklyChallenges = [
Challenge(
id: 'weekly_trips_30',
titleEn: 'Weekly Champion',
titleAr: 'بطل الأسبوع',
descriptionEn: 'Complete 30 trips this week',
descriptionAr: 'أكمل 30 رحلة هذا الأسبوع',
icon: Icons.emoji_events_rounded,
color: const Color(0xFFFFD700),
target: 30,
reward: 300,
type: 'weekly',
metric: 'trips',
),
Challenge(
id: 'weekly_earnings',
titleEn: 'Big Earner',
titleAr: 'الربح الكبير',
descriptionEn: 'Earn 20,000 SYP this week',
descriptionAr: 'اربح 20,000 ل.س هذا الأسبوع',
icon: Icons.account_balance_wallet_rounded,
color: const Color(0xFF9C27B0),
target: 20000,
reward: 500,
type: 'weekly',
metric: 'earnings',
),
Challenge(
id: 'weekly_hours',
titleEn: 'Time Master',
titleAr: 'سيد الوقت',
descriptionEn: 'Drive for 20 hours this week',
descriptionAr: 'اقضِ 20 ساعة في القيادة هذا الأسبوع',
icon: Icons.timer_rounded,
color: const Color(0xFF00BCD4),
target: 20,
reward: 400,
type: 'weekly',
metric: 'hours',
),
];
}
void _loadProgress() {
final today = DateTime.now().toIso8601String().split('T')[0];
final savedDate = box.read('challenges_date');
if (savedDate != today) {
// يوم جديد — إعادة تعيين التحديات اليومية
box.write('challenges_date', today);
for (var c in dailyChallenges) {
box.write('challenge_${c.id}_claimed', false);
}
}
// تحميل حالة المطالبة
for (var c in dailyChallenges) {
c.isClaimed = box.read('challenge_${c.id}_claimed') ?? false;
}
for (var c in weeklyChallenges) {
c.isClaimed = box.read('challenge_${c.id}_claimed') ?? false;
}
}
Future<void> fetchChallengeProgress() async {
isLoading = true;
update();
try {
// جلب رحلات اليوم
var todayRes = await CRUD().getWallet(
link: AppLink.getDriverPaymentToday,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
int todayTrips = 0;
double todayEarnings = 0;
if (todayRes != null && todayRes != 'failure') {
var data = jsonDecode(todayRes);
todayEarnings = double.tryParse(data['message']?[0]?['todayAmount']?.toString() ?? '0') ?? 0;
todayTrips = int.tryParse(data['message']?[0]?['todayCount']?.toString() ?? '0') ?? 0;
}
// تحديث التحديات اليومية
for (var c in dailyChallenges) {
switch (c.metric) {
case 'trips':
c.currentProgress = todayTrips;
break;
case 'earnings':
c.currentProgress = todayEarnings.toInt();
break;
}
}
// 2. Fetch weekly earnings from PAYMENT server
var weeklyEarningsRes = await CRUD().getWallet(
link: AppLink.getDriverWeekPaymentMove,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
double weeklyEarnings = 0;
if (weeklyEarningsRes != null && weeklyEarningsRes != 'failure') {
var data = jsonDecode(weeklyEarningsRes);
if (data['message'] is List && data['message'].isNotEmpty) {
weeklyEarnings = double.tryParse(data['message'][0]['totalAmount']?.toString() ?? '0') ?? 0;
}
}
// 3. Fetch weekly trips and hours from RIDES server (avoiding earnings join)
var weeklyAggregateRes = await CRUD().get(
link: AppLink.getWeeklyAggregate,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
int weeklyTrips = 0;
double weeklyHours = 0;
if (weeklyAggregateRes != null && weeklyAggregateRes != 'failure') {
var data = jsonDecode(weeklyAggregateRes);
if (data['message'] is List) {
for (var day in data['message']) {
weeklyTrips += int.tryParse(day['trips']?.toString() ?? '0') ?? 0;
weeklyHours += double.tryParse(day['hours']?.toString() ?? '0') ?? 0;
}
}
}
for (var c in weeklyChallenges) {
switch (c.metric) {
case 'trips':
c.currentProgress = weeklyTrips;
break;
case 'earnings':
c.currentProgress = weeklyEarnings.toInt();
break;
case 'hours':
c.currentProgress = weeklyHours.toInt();
break;
}
}
} catch (e) {
debugPrint('❌ [Challenges] Error: $e');
}
isLoading = false;
update();
}
Future<void> claimReward(Challenge challenge) async {
if (!challenge.isCompleted || challenge.isClaimed) return;
try {
var res = await CRUD().post(
link: AppLink.claimChallengeReward,
payload: {
'driver_id': box.read(BoxName.driverID).toString(),
'challenge_id': challenge.id,
'points': challenge.reward.toString(),
},
);
if (res != null && res != 'failure') {
challenge.isClaimed = true;
box.write('challenge_${challenge.id}_claimed', true);
debugPrint('🎉 Claimed ${challenge.reward} points for ${challenge.id}');
update();
}
} catch (e) {
debugPrint('❌ [Challenges] Claim error: $e');
}
}
}

View File

@@ -0,0 +1,453 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import '../../main.dart';
// ════════════════════════════════════════════
// نماذج البيانات
// ════════════════════════════════════════════
class DriverLevel {
final String id;
final String nameEn;
final String nameAr;
final String emoji;
final Color color;
final Color gradientEnd;
final int minPoints;
final int maxPoints;
final double commissionDiscount; // نسبة الخصم على العمولة
final List<String> perks;
const DriverLevel({
required this.id,
required this.nameEn,
required this.nameAr,
required this.emoji,
required this.color,
required this.gradientEnd,
required this.minPoints,
required this.maxPoints,
required this.commissionDiscount,
required this.perks,
});
}
class Achievement {
final String id;
final String titleEn;
final String titleAr;
final String descriptionEn;
final String descriptionAr;
final IconData icon;
final Color color;
final int target;
final String type; // 'trips', 'rating', 'earnings', 'streak', 'referral'
bool isUnlocked;
int currentProgress;
final DateTime? unlockedAt;
Achievement({
required this.id,
required this.titleEn,
required this.titleAr,
required this.descriptionEn,
required this.descriptionAr,
required this.icon,
required this.color,
required this.target,
required this.type,
this.isUnlocked = false,
this.currentProgress = 0,
this.unlockedAt,
});
double get progress => (currentProgress / target).clamp(0.0, 1.0);
}
// ════════════════════════════════════════════
// المستويات الثابتة
// ════════════════════════════════════════════
class DriverLevels {
static const List<DriverLevel> all = [
DriverLevel(
id: 'bronze',
nameEn: 'Bronze',
nameAr: 'برونزي',
emoji: '🥉',
color: Color(0xFFCD7F32),
gradientEnd: Color(0xFFE8A854),
minPoints: 0,
maxPoints: 999,
commissionDiscount: 0,
perks: ['Basic features', 'Standard support'],
),
DriverLevel(
id: 'silver',
nameEn: 'Silver',
nameAr: 'فضي',
emoji: '🥈',
color: Color(0xFF9CA3AF),
gradientEnd: Color(0xFFC0C7D1),
minPoints: 1000,
maxPoints: 4999,
commissionDiscount: 1,
perks: ['Priority medium', 'Silver badge', '-1% commission'],
),
DriverLevel(
id: 'gold',
nameEn: 'Gold',
nameAr: 'ذهبي',
emoji: '🥇',
color: Color(0xFFFFD700),
gradientEnd: Color(0xFFFFA500),
minPoints: 5000,
maxPoints: 14999,
commissionDiscount: 2,
perks: ['High priority', 'Gold badge', '-2% commission'],
),
DriverLevel(
id: 'diamond',
nameEn: 'Diamond',
nameAr: 'ألماسي',
emoji: '💎',
color: Color(0xFF00BCD4),
gradientEnd: Color(0xFF3F51B5),
minPoints: 15000,
maxPoints: 999999,
commissionDiscount: 5,
perks: ['VIP first', 'Diamond badge', '-5% commission', 'Priority support'],
),
];
static DriverLevel getLevel(int points) {
for (int i = all.length - 1; i >= 0; i--) {
if (points >= all[i].minPoints) return all[i];
}
return all.first;
}
static DriverLevel? getNextLevel(int points) {
final current = getLevel(points);
final idx = all.indexOf(current);
if (idx < all.length - 1) return all[idx + 1];
return null;
}
static double getProgressToNext(int points) {
final current = getLevel(points);
final next = getNextLevel(points);
if (next == null) return 1.0;
return ((points - current.minPoints) / (next.minPoints - current.minPoints))
.clamp(0.0, 1.0);
}
}
// ════════════════════════════════════════════
// الـ Controller
// ════════════════════════════════════════════
class GamificationController extends GetxController {
bool isLoading = false;
int totalTrips = 0;
int totalPoints = 0;
double averageRating = 5.0;
int totalReferrals = 0;
int consecutiveDays = 0; // أيام متتالية
double totalEarnings = 0;
// === Driving Behavior ===
double behaviorScore = 100.0;
int hardBrakes = 0;
double maxSpeed = 0.0;
late DriverLevel currentLevel;
DriverLevel? nextLevel;
double progressToNext = 0;
List<Achievement> achievements = [];
// === Daily Goal ===
double dailyGoal = 0;
double dailyEarnings = 0;
double get dailyGoalProgress =>
dailyGoal > 0 ? (dailyEarnings / dailyGoal).clamp(0.0, 1.0) : 0.0;
bool get isDailyGoalMet => dailyGoalProgress >= 1.0;
@override
void onInit() {
super.onInit();
_loadLocalData();
_initializeAchievements();
_calculateLevel();
fetchGamificationData();
}
// ═══════ تحميل البيانات المحلية ═══════
void _loadLocalData() {
dailyGoal = (box.read('dailyGoal') ?? 0).toDouble();
totalTrips = box.read('gamification_totalTrips') ?? 0;
consecutiveDays = box.read('gamification_consecutiveDays') ?? 0;
}
// ═══════ حفظ الهدف اليومي ═══════
void setDailyGoal(double goal) {
dailyGoal = goal;
box.write('dailyGoal', goal);
update();
}
void updateDailyEarnings(double earnings) {
dailyEarnings = earnings;
update();
}
// ═══════ حساب المستوى ═══════
void _calculateLevel() {
currentLevel = DriverLevels.getLevel(totalPoints);
nextLevel = DriverLevels.getNextLevel(totalPoints);
progressToNext = DriverLevels.getProgressToNext(totalPoints);
update();
}
// ═══════ تهيئة الإنجازات ═══════
void _initializeAchievements() {
achievements = [
Achievement(
id: 'first_trip',
titleEn: 'First Trip',
titleAr: 'أول رحلة',
descriptionEn: 'Complete your first trip',
descriptionAr: 'أكمل أول رحلة لك',
icon: Icons.flag_rounded,
color: const Color(0xFF4CAF50),
target: 1,
type: 'trips',
),
Achievement(
id: 'trip_50',
titleEn: 'Road Warrior',
titleAr: 'محارب الطريق',
descriptionEn: 'Complete 50 trips',
descriptionAr: 'أكمل 50 رحلة',
icon: Icons.local_taxi_rounded,
color: const Color(0xFF2196F3),
target: 50,
type: 'trips',
),
Achievement(
id: 'trip_100',
titleEn: 'Century Rider',
titleAr: 'سائق المئة',
descriptionEn: 'Complete 100 trips',
descriptionAr: 'أكمل 100 رحلة',
icon: Icons.emoji_events_rounded,
color: const Color(0xFFFF9800),
target: 100,
type: 'trips',
),
Achievement(
id: 'trip_500',
titleEn: 'Road Legend',
titleAr: 'أسطورة الطريق',
descriptionEn: 'Complete 500 trips',
descriptionAr: 'أكمل 500 رحلة',
icon: Icons.stars_rounded,
color: const Color(0xFFE91E63),
target: 500,
type: 'trips',
),
Achievement(
id: 'five_star',
titleEn: 'Five Star Driver',
titleAr: 'سائق 5 نجوم',
descriptionEn: 'Maintain 5.0 rating',
descriptionAr: 'حافظ على تقييم 5.0',
icon: Icons.star_rounded,
color: const Color(0xFFFFD700),
target: 5,
type: 'rating',
),
Achievement(
id: 'streak_7',
titleEn: 'Weekly Streak',
titleAr: 'سلسلة أسبوعية',
descriptionEn: 'Work 7 consecutive days',
descriptionAr: 'اعمل 7 أيام متتالية',
icon: Icons.whatshot_rounded,
color: const Color(0xFFFF5722),
target: 7,
type: 'streak',
),
Achievement(
id: 'streak_30',
titleEn: 'Monthly Streak',
titleAr: 'سلسلة شهرية',
descriptionEn: 'Work 30 consecutive days',
descriptionAr: 'اعمل 30 يوم متتالي',
icon: Icons.local_fire_department_rounded,
color: const Color(0xFFD32F2F),
target: 30,
type: 'streak',
),
Achievement(
id: 'referral_5',
titleEn: 'Social Butterfly',
titleAr: 'الفراشة الاجتماعية',
descriptionEn: 'Refer 5 drivers',
descriptionAr: 'ادعُ 5 سائقين',
icon: Icons.people_rounded,
color: const Color(0xFF9C27B0),
target: 5,
type: 'referral',
),
];
}
// ═══════ تحديث تقدم الإنجازات ═══════
void _updateAchievementProgress() {
for (var ach in achievements) {
switch (ach.type) {
case 'trips':
ach.currentProgress = totalTrips;
break;
case 'rating':
ach.currentProgress = averageRating >= 5 ? 5 : averageRating.floor();
break;
case 'streak':
ach.currentProgress = consecutiveDays;
break;
case 'referral':
ach.currentProgress = totalReferrals;
break;
}
ach.isUnlocked = ach.currentProgress >= ach.target;
}
update();
}
// ═══════ جلب البيانات من السيرفر ═══════
Future<void> fetchGamificationData() async {
isLoading = true;
update();
try {
// 1. جلب عدد الرحلات الكلي
var tripRes = await CRUD().get(
link: AppLink.getTripCountByCaptain,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (tripRes != null && tripRes != 'failure') {
var data = jsonDecode(tripRes);
totalTrips =
int.tryParse(data['message']?[0]?['count']?.toString() ?? '0') ?? 0;
box.write('gamification_totalTrips', totalTrips);
}
// 2. جلب التقييم
var rateRes = await CRUD().get(
link: AppLink.getDriverRate,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (rateRes != null && rateRes != 'failure') {
var data = jsonDecode(rateRes);
averageRating =
double.tryParse(data['message']?[0]?['rating']?.toString() ?? '5') ??
5.0;
}
// 3. جلب النقاط (الرصيد)
var pointsRes = await CRUD().getWallet(
link: AppLink.getDriverPaymentPoints,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
if (pointsRes != null && pointsRes != 'failure') {
var data = jsonDecode(pointsRes);
totalPoints = double.tryParse(
data['message']?[0]?['total_amount']?.toString() ?? '0')
?.abs()
.toInt() ??
0;
}
// 4. جلب عدد الدعوات
var invRes = await CRUD().get(
link: AppLink.getInviteDriver,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (invRes != null && invRes != 'failure') {
var data = jsonDecode(invRes);
if (data['message'] is List) {
totalReferrals = (data['message'] as List).length;
}
}
// 5. جلب أرباح اليوم
var todayRes = await CRUD().getWallet(
link: AppLink.getDriverPaymentToday,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
if (todayRes != null && todayRes != 'failure') {
var data = jsonDecode(todayRes);
dailyEarnings = double.tryParse(
data['message']?[0]?['todayAmount']?.toString() ?? '0') ??
0;
}
// 6. جلب تقييم سلوك القيادة
var behaviorRes = await CRUD().get(
link: AppLink.getDriverBehavior,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (behaviorRes != null && behaviorRes != 'failure') {
var data = jsonDecode(behaviorRes);
if (data['message'] is List && data['message'].isNotEmpty) {
var behavior = data['message'][0];
behaviorScore = double.tryParse(behavior['avg_score']?.toString() ?? '100') ?? 100.0;
hardBrakes = int.tryParse(behavior['total_hard_brakes']?.toString() ?? '0') ?? 0;
maxSpeed = double.tryParse(behavior['max_speed']?.toString() ?? '0') ?? 0.0;
}
}
// 7. حساب الأيام المتتالية (محلياً)
_calculateConsecutiveDays();
} catch (e) {
debugPrint('❌ [Gamification] Error fetching data: $e');
}
_calculateLevel();
_updateAchievementProgress();
isLoading = false;
update();
}
void _calculateConsecutiveDays() {
String? lastActiveDate = box.read('lastActiveDate');
String today =
DateTime.now().toIso8601String().split('T')[0]; // 2026-05-08
if (lastActiveDate == null) {
consecutiveDays = 1;
} else if (lastActiveDate == today) {
// نفس اليوم — لا تغيير
} else {
DateTime last = DateTime.parse(lastActiveDate);
DateTime now = DateTime.parse(today);
if (now.difference(last).inDays == 1) {
consecutiveDays++;
} else {
consecutiveDays = 1;
}
}
box.write('lastActiveDate', today);
box.write('gamification_consecutiveDays', consecutiveDays);
}
// ═══════ إحصائيات سريعة ═══════
int get unlockedCount => achievements.where((a) => a.isUnlocked).length;
int get totalAchievements => achievements.length;
}

View File

@@ -0,0 +1,102 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import '../../main.dart';
class LeaderboardEntry {
final String driverId;
final String name;
final String photoUrl;
final int rank;
final double value; // trips or earnings
final bool isCurrentUser;
LeaderboardEntry(
{required this.driverId,
required this.name,
required this.photoUrl,
required this.rank,
required this.value,
this.isCurrentUser = false});
}
class LeaderboardController extends GetxController {
bool isLoading = false;
int selectedTab = 0; // 0=trips, 1=earnings
List<LeaderboardEntry> tripLeaderboard = [];
List<LeaderboardEntry> earningsLeaderboard = [];
int myRank = 0;
@override
void onInit() {
super.onInit();
fetchLeaderboard();
}
void changeTab(int tab) {
selectedTab = tab;
update();
}
List<LeaderboardEntry> get currentLeaderboard =>
selectedTab == 0 ? tripLeaderboard : earningsLeaderboard;
Future<void> fetchLeaderboard() async {
isLoading = true;
update();
try {
final myId = box.read(BoxName.driverID)?.toString() ?? '';
// Fetch trips leaderboard
var resTrips = await CRUD().post(
link: AppLink.getLeaderboard,
payload: {'type': 'trips'},
);
if (resTrips != null && resTrips != 'failure') {
var data = jsonDecode(resTrips);
if (data['message'] is List) {
tripLeaderboard = (data['message'] as List).map((e) => LeaderboardEntry(
driverId: e['driver_id'].toString(),
name: e['name'].toString(),
photoUrl: e['photoUrl']?.toString() ?? '',
rank: int.tryParse(e['rank']?.toString() ?? '0') ?? 0,
value: double.tryParse(e['value']?.toString() ?? '0') ?? 0,
isCurrentUser: e['driver_id'].toString() == myId,
)).toList();
}
}
// Fetch earnings leaderboard
var resEarnings = await CRUD().post(
link: AppLink.getLeaderboard,
payload: {'type': 'earnings'},
);
if (resEarnings != null && resEarnings != 'failure') {
var data = jsonDecode(resEarnings);
if (data['message'] is List) {
earningsLeaderboard = (data['message'] as List).map((e) => LeaderboardEntry(
driverId: e['driver_id'].toString(),
name: e['name'].toString(),
photoUrl: e['photoUrl']?.toString() ?? '',
rank: int.tryParse(e['rank']?.toString() ?? '0') ?? 0,
value: double.tryParse(e['value']?.toString() ?? '0') ?? 0,
isCurrentUser: e['driver_id'].toString() == myId,
)).toList();
}
}
// Find my rank
final myTripEntry = tripLeaderboard.firstWhereOrNull((e) => e.isCurrentUser);
final myEarnEntry = earningsLeaderboard.firstWhereOrNull((e) => e.isCurrentUser);
myRank = selectedTab == 0 ? (myTripEntry?.rank ?? 0) : (myEarnEntry?.rank ?? 0);
} catch (e) {
debugPrint('❌ [Leaderboard] Error: $e');
}
isLoading = false;
update();
}
}

View File

@@ -0,0 +1,189 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import '../../main.dart';
// ════════════════════════════════════════════
// نموذج الإحالة
// ════════════════════════════════════════════
class ReferralRecord {
final String id;
final String name;
final String phone;
final String status; // 'registered', 'active', 'inactive'
final String type; // 'driver', 'passenger'
final String joinDate;
final int tripCount;
ReferralRecord({
required this.id,
required this.name,
required this.phone,
required this.status,
required this.type,
required this.joinDate,
required this.tripCount,
});
factory ReferralRecord.fromJson(Map<String, dynamic> json) {
return ReferralRecord(
id: json['id']?.toString() ?? '',
name: json['name']?.toString() ?? json['nameArabic']?.toString() ?? '',
phone: json['phone']?.toString() ?? '',
status: json['status']?.toString() ?? 'registered',
type: json['type']?.toString() ?? 'driver',
joinDate: json['created_at']?.toString() ?? '',
tripCount: int.tryParse(json['trip_count']?.toString() ?? '0') ?? 0,
);
}
}
// ════════════════════════════════════════════
// Controller
// ════════════════════════════════════════════
class ReferralController extends GetxController {
bool isLoading = false;
List<ReferralRecord> driverReferrals = [];
List<ReferralRecord> passengerReferrals = [];
String referralCode = '';
int totalDriverReferrals = 0;
int totalPassengerReferrals = 0;
int activeReferrals = 0;
double totalRewardsEarned = 0;
@override
void onInit() {
super.onInit();
_generateReferralCode();
fetchReferralData();
}
void _generateReferralCode() {
final driverId = box.read(BoxName.driverID)?.toString() ?? '';
final name = box.read(BoxName.nameDriver)?.toString() ?? '';
if (driverId.isNotEmpty) {
// كود فريد: أول 3 حروف من الاسم + ID
final prefix = name.length >= 3 ? name.substring(0, 3).toUpperCase() : name.toUpperCase();
referralCode = '$prefix$driverId';
}
}
Future<void> fetchReferralData() async {
isLoading = true;
update();
try {
// 1. جلب دعوات السائقين
var driverRes = await CRUD().get(
link: AppLink.getInviteDriver,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (driverRes != null && driverRes != 'failure') {
var data = jsonDecode(driverRes);
if (data['message'] is List) {
driverReferrals = (data['message'] as List)
.map((e) => ReferralRecord.fromJson(e))
.toList();
totalDriverReferrals = driverReferrals.length;
}
}
// 2. جلب دعوات الركاب
var passengerRes = await CRUD().get(
link: AppLink.getDriverInvitationToPassengers,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (passengerRes != null && passengerRes != 'failure') {
var data = jsonDecode(passengerRes);
if (data['message'] is List) {
passengerReferrals = (data['message'] as List)
.map((e) => ReferralRecord.fromJson(e))
.toList();
totalPassengerReferrals = passengerReferrals.length;
}
}
// 3. جلب الإحصائيات الدقيقة للمكافآت
var statsRes = await CRUD().get(
link: AppLink.getReferralStats,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (statsRes != null && statsRes != 'failure') {
var data = jsonDecode(statsRes);
if (data['message'] is List && data['message'].isNotEmpty) {
var stats = data['message'][0];
totalRewardsEarned = double.tryParse(stats['totalRewards']?.toString() ?? '0') ?? 0;
activeReferrals = (int.tryParse(stats['driverInvites']?.toString() ?? '0') ?? 0) +
(int.tryParse(stats['passengerInvites']?.toString() ?? '0') ?? 0);
}
}
} catch (e) {
debugPrint('❌ [Referral] Error: $e');
}
isLoading = false;
update();
}
void copyCode() {
Clipboard.setData(ClipboardData(text: referralCode));
}
String get shareMessage {
final appName = 'Intaleq';
return 'Join $appName as a driver! Use my code: $referralCode\nDownload: https://intaleq.app/driver?ref=$referralCode';
}
String get shareMessagePassenger {
final appName = 'Intaleq';
return 'Get a ride with $appName! Use my code: $referralCode for a discount.\nDownload: https://intaleq.app?ref=$referralCode';
}
int get totalReferrals => totalDriverReferrals + totalPassengerReferrals;
// ═══════ إرسال دعوة سائق ═══════
Future<bool> inviteDriver(String phone) async {
try {
var res = await CRUD().post(
link: AppLink.addInviteDriver,
payload: {
'driver_id': box.read(BoxName.driverID).toString(),
'phone': phone,
},
);
if (res != null && res != 'failure') {
await fetchReferralData();
return true;
}
} catch (e) {
debugPrint('❌ [Referral] Invite driver error: $e');
}
return false;
}
// ═══════ إرسال دعوة راكب ═══════
Future<bool> invitePassenger(String phone) async {
try {
var res = await CRUD().post(
link: AppLink.addInvitationPassenger,
payload: {
'driver_id': box.read(BoxName.driverID).toString(),
'phone': phone,
},
);
if (res != null && res != 'failure') {
await fetchReferralData();
return true;
}
} catch (e) {
debugPrint('❌ [Referral] Invite passenger error: $e');
}
return false;
}
}

View File

@@ -0,0 +1,146 @@
import 'dart:convert';
import 'dart:math';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import '../../../constant/table_names.dart';
import '../../../main.dart';
import '../../../print.dart';
class DriverBehaviorController extends GetxController {
Future<List<Map<String, dynamic>>> getAllData() async {
return await sql.getAllData(TableName.behavior);
}
var isLoading = false.obs;
var overallScore = 100.0.obs;
var lastTrips = [].obs;
Future<void> fetchDriverBehavior() async {
isLoading.value = true;
try {
final response = await CRUD().get(
link: AppLink.get_driver_behavior,
payload: {"driver_id": box.read(BoxName.driverID).toString()},
);
if (response != 'failure') {
final json = jsonDecode(response);
overallScore.value =
double.parse(json['message']['overall_behavior_score'].toString());
lastTrips.value = json['message']['last_10_trips'];
} else {
// Get.snackbar("Error", json['message'] ?? "Unknown error");
}
} catch (e) {
// Get.snackbar("Error", "Exception: $e");
Log.print('e: ${e}');
} finally {
isLoading.value = false;
}
}
Future<Map<String, dynamic>> analyzeData() async {
final data = await getAllData();
if (data.isEmpty) return {};
double maxSpeed = 0;
double totalSpeed = 0;
int hardBrakes = 0;
double totalDistance = 0;
// متغيرات للمقارنة مع النقطة السابقة
double? prevLat, prevLng;
DateTime? prevTime;
// ترتيب البيانات حسب الوقت لضمان دقة الحساب (اختياري لكن مفضل)
// data.sort((a, b) => a['created_at'].compareTo(b['created_at']));
for (var item in data) {
// 1. قراءة البيانات بالأسماء الصحيحة من الجدول
double lat = item['latitude'] ?? item['lat'] ?? 0.0;
double lng = item['longitude'] ?? item['lng'] ?? 0.0;
double acc = item['acceleration'] ?? 0.0;
// قراءة الوقت لحساب السرعة
DateTime currentTime =
DateTime.tryParse(item['created_at'].toString()) ?? DateTime.now();
double currentSpeed = 0;
// 2. حساب السرعة والمسافة إذا وجدت نقطة سابقة
if (prevLat != null && prevLng != null && prevTime != null) {
double distKm = _calculateDistance(prevLat, prevLng, lat, lng);
int timeDiffSeconds = currentTime.difference(prevTime).inSeconds;
if (timeDiffSeconds > 0) {
// السرعة (كم/س) = (المسافة بالكيلومتر * 3600) / الزمن بالثواني
currentSpeed = (distKm * 3600) / timeDiffSeconds;
}
totalDistance += distKm;
}
// تحديث القيم الإحصائية
if (currentSpeed > maxSpeed) maxSpeed = currentSpeed;
totalSpeed += currentSpeed;
// حساب الفرملة القوية (يعتمد على التسارع المحفوظ مسبقاً)
if (acc.abs() > 3.0) hardBrakes++;
// حفظ النقطة الحالية لتكون هي "السابقة" في الدورة التالية
prevLat = lat;
prevLng = lng;
prevTime = currentTime;
}
// تجنب القسمة على صفر
double avgSpeed = (data.length > 1) ? totalSpeed / (data.length - 1) : 0;
// حساب تقييم السلوك
double behaviorScore = 100 - (hardBrakes * 5) - ((maxSpeed > 100) ? 10 : 0);
behaviorScore = behaviorScore.clamp(0.0, 100.0);
return {
'max_speed': maxSpeed,
'avg_speed': avgSpeed,
'hard_brakes': hardBrakes,
'total_distance': totalDistance,
'behavior_score': behaviorScore,
};
}
Future<void> sendSummaryToServer(String driverId, String tripId) async {
final summary = await analyzeData();
if (summary.isEmpty) return;
final Map<String, dynamic> body = {
'driver_id': driverId,
'trip_id': tripId,
...summary, // فيه doubles
};
// اجبر كل القيم على String
final payload = body.map((k, v) => MapEntry(k, v?.toString() ?? ''));
await CRUD().post(link: AppLink.saveBehavior, payload: payload);
await clearData();
}
Future<void> clearData() async {
await sql.deleteAllData(TableName.behavior);
}
double _calculateDistance(
double lat1, double lon1, double lat2, double lon2) {
const p = 0.017453292519943295;
final a = 0.5 -
cos((lat2 - lat1) * p) / 2 +
cos(lat1 * p) * cos(lat2 * p) * (1 - cos((lon2 - lon1) * p)) / 2;
return 12742 * asin(sqrt(a)); // distance in km
}
}

View File

@@ -0,0 +1,78 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../functions/launch.dart';
class ContactUsController extends GetxController {
final String phone1 = '+963992952235';
final String phone2 = '+963992952235';
final TimeOfDay workStartTime = const TimeOfDay(hour: 12, minute: 0);
final TimeOfDay workEndTime = const TimeOfDay(hour: 19, minute: 0);
bool _isWithinWorkTime(TimeOfDay now) {
return (now.hour > workStartTime.hour ||
(now.hour == workStartTime.hour &&
now.minute >= workStartTime.minute)) &&
(now.hour < workEndTime.hour ||
(now.hour == workEndTime.hour && now.minute <= workEndTime.minute));
}
void showContactDialog(BuildContext context) {
TimeOfDay now = TimeOfDay.now();
showCupertinoModalPopup(
context: context,
builder: (context) => CupertinoActionSheet(
title: Text('Contact Us'.tr),
message: Text('Choose a contact option'.tr),
actions: <Widget>[
if (_isWithinWorkTime(now))
CupertinoActionSheetAction(
child: Text(phone1),
onPressed: () => makePhoneCall(
phone1,
),
),
if (_isWithinWorkTime(now))
CupertinoActionSheetAction(
child: Text(phone2),
onPressed: () => makePhoneCall(phone2),
),
if (!_isWithinWorkTime(now))
CupertinoActionSheetAction(
child: Text(
'Work time is from 10:00 - 17:00.\nYou can send a WhatsApp message or email.'
.tr),
onPressed: () => Navigator.pop(context),
),
CupertinoActionSheetAction(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const Icon(
FontAwesome.whatsapp,
color: AppColor.greenColor,
),
Text('Send WhatsApp Message'.tr),
],
),
onPressed: () =>
launchCommunication('whatsapp', phone1, 'Hello'.tr),
),
CupertinoActionSheetAction(
child: Text('Send Email'.tr),
onPressed: () =>
launchCommunication('email', 'support@sefer.live', 'Hello'.tr),
),
],
cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr),
onPressed: () => Navigator.pop(context),
),
),
);
}
}

View File

@@ -0,0 +1,167 @@
import 'dart:convert';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/models/model/driver/rides_summary_model.dart';
import '../../../views/widgets/error_snakbar.dart';
class DurationController extends GetxController {
final data = DurationData;
// late AnimationController animationController;
late List<MonthlyDataModel> rideData;
late List<MonthlyRideModel> rideCountData;
late List<MonthlyPriceDriverModel> ridePriceDriverData;
Map<String, dynamic> jsonData1 = {};
Map<String, dynamic> jsonData2 = {};
bool isLoading = false;
String totalDurationToday = '';
var chartData;
var chartRideCount;
var chartRidePriceDriver;
List monthlyList = [];
@override
void onInit() async {
super.onInit();
await fetchData();
await fetchRideDriver();
await getStaticDriver();
}
getStaticDriver() async {
isLoading = true;
update();
var res = await CRUD().get(
link: AppLink.driverStatistic,
payload: {'driverID': box.read(BoxName.driverID)});
if (res == 'success') {
try {
monthlyList = jsonDecode(res)['message'];
} catch (e) {
monthlyList = [];
}
} else {
monthlyList = [];
}
isLoading = false;
update();
}
Future<void> fetchData() async {
isLoading = true;
update();
var res = await CRUD().get(
link: AppLink.getTotalDriverDuration,
payload: {'driver_id': box.read(BoxName.driverID)},
);
if (res == 'success') {
try {
jsonData1 = jsonDecode(res);
final List<dynamic> jsonData = jsonData1['message'];
rideData = jsonData.map<MonthlyDataModel>((item) {
return MonthlyDataModel.fromJson(item);
}).toList();
final List<FlSpot> spots = rideData
.map((data) => FlSpot(
data.day.toDouble(),
data.totalDuration.toDouble(),
))
.toList();
chartData = spots;
} catch (e) {
jsonData1 = {};
chartData = <FlSpot>[];
}
} else {
jsonData1 = {};
chartData = <FlSpot>[];
}
isLoading = false;
update();
}
Future<void> fetchRideDriver() async {
isLoading = true;
update(); // Notify the observers about the loading state change
var res = await CRUD().get(
link: AppLink.getRidesDriverByDay,
payload: {'driver_id': box.read(BoxName.driverID)},
);
if (res != 'failure' && res != 'no_internet' && res != 'token_expired') {
jsonData2 = jsonDecode(res);
var jsonResponse = jsonData2 as Map<String, dynamic>;
isLoading = false;
final List<dynamic> jsonData = jsonResponse['message'];
rideCountData = jsonData.map<MonthlyRideModel>((item) {
return MonthlyRideModel.fromJson(item);
}).toList();
ridePriceDriverData = jsonData.map<MonthlyPriceDriverModel>((item) {
return MonthlyPriceDriverModel.fromJson(item);
}).toList();
final List<FlSpot> spots = rideCountData
.map((data) => FlSpot(
data.day.toDouble(),
data.countRide.toDouble(),
))
.toList();
chartRideCount = spots;
final List<FlSpot> spotsDriverPrices = ridePriceDriverData
.map((data) => FlSpot(
data.day.toDouble(),
data.pricePerDay.toDouble(),
))
.toList();
chartRidePriceDriver = spotsDriverPrices;
update();
} else {
isLoading = false;
jsonData2 = {};
chartRideCount = <FlSpot>[];
chartRidePriceDriver = <FlSpot>[];
update();
if (res == 'no_internet') {
mySnackeBarError('No internet connection'.tr);
}
}
}
List<DurationData> parseData(List<dynamic> json) {
return json.map((entry) {
final Map<String, dynamic> entryMap = entry;
final day = DateTime.parse(entryMap['day']);
final totalDuration = _parseDuration(entryMap['total_duration']);
return DurationData(day, totalDuration);
}).toList();
}
Duration _parseDuration(String durationString) {
final parts = durationString.split(':');
final hours = int.parse(parts[0]);
final minutes = int.parse(parts[1]);
final seconds = int.parse(parts[2]);
return Duration(hours: hours, minutes: minutes, seconds: seconds);
}
}
class DurationData {
final DateTime day;
final Duration totalDuration;
DurationData(this.day, this.totalDuration);
}

View File

@@ -0,0 +1,58 @@
import 'dart:convert';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:get/get.dart';
class AssuranceHealthController extends GetxController {
bool isLoading = false;
Map tripCount = {};
Future getTripCountByCaptain() async {
var res = await CRUD().get(link: AppLink.getTripCountByCaptain, payload: {
"driver_id": box.read(BoxName.driverID).toString(),
});
if (res != 'failure') {
tripCount = jsonDecode(res)['message'];
update();
}
}
Future<void> addDriverHealthAssurance({
String? driverId,
String? assured,
required String healthInsuranceProvider,
}) async {
// Define the URL to your PHP backend
// Data to be sent to the backend
Map<String, String> data = {
"driver_id": box.read(BoxName.driverID).toString(),
"assured": '1',
"health_insurance_provider": healthInsuranceProvider,
};
try {
// Send the POST request to your backend
var response = await CRUD()
.post(link: AppLink.addHealthInsuranceProvider, payload: data);
if (response != 'failure') {
// Handle success (e.g., show a success message)
mySnackbarSuccess(
"You have successfully opted for health insurance.".tr);
} else {
// Handle failure (e.g., show an error message)
print("Failed to save health assurance data");
mySnackeBarError("Please enter a health insurance status.".tr);
}
} catch (e) {
// Handle any errors
print("Error: $e");
}
}
}

View File

@@ -0,0 +1,103 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/box_name.dart';
import '../../../../constant/links.dart';
import '../../../../constant/style.dart';
import '../../../../main.dart';
import '../../../../views/widgets/elevated_btn.dart';
import '../../../functions/crud.dart';
import '../../../functions/encrypt_decrypt.dart';
class HelpController extends GetxController {
bool isLoading = false;
final formKey = GlobalKey<FormState>();
final helpQuestionController = TextEditingController();
Map helpQuestionDate = {};
Map helpQuestionRepleyDate = {};
String status = '';
String qustion = '';
late int indexQuestion = 0;
getIndex(int i, String qustion1) async {
indexQuestion = i;
qustion = qustion1;
update();
}
void addHelpQuestion() async {
isLoading = true;
update();
var res = await CRUD().post(link: AppLink.addhelpCenter, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'helpQuestion': (helpQuestionController.text)
});
var d = jsonDecode(res);
isLoading = false;
update();
if (d['status'].toString() == 'success') {
getHelpQuestion();
// Get.snackbar('Feedback data saved successfully'.tr, '',
// backgroundColor: AppColor.greenColor,
// snackPosition: SnackPosition.BOTTOM);
}
}
void getHelpQuestion() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.gethelpCenter, payload: {
'driverID': box.read(BoxName.driverID).toString(),
});
if (res == "failure") {
isLoading = false;
update();
Get.defaultDialog(
title: 'There is no help Question here'.tr,
titleStyle: AppStyle.title,
middleText: '',
confirm: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
MyElevatedButton(
title: 'Add Question'.tr,
onPressed: () {
Get.back();
}),
MyElevatedButton(
title: 'Back'.tr,
onPressed: () {
Get.back();
Get.back();
}),
],
));
}
helpQuestionDate = jsonDecode(res);
isLoading = false;
update();
}
Future getHelpRepley(String id) async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getByIdhelpCenter, payload: {
'id': id,
});
if (res == "failure") {
status = 'not yet';
isLoading = false;
update();
}
helpQuestionRepleyDate = jsonDecode(res);
isLoading = false;
update();
}
@override
void onInit() {
getHelpQuestion();
super.onInit();
}
}

View File

@@ -0,0 +1,22 @@
import 'dart:convert';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import 'package:get/get.dart';
class MaintainCenterController extends GetxController {
bool isLoading = false;
Map tripCount = {};
Future getTripCountByCaptain() async {
var res = await CRUD().get(link: AppLink.getTripCountByCaptain, payload: {
"driver_id": box.read(BoxName.driverID).toString(),
});
if (res != 'failure') {
tripCount = jsonDecode(res)['message'];
update();
}
}
}

View File

@@ -0,0 +1,69 @@
import 'dart:convert';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/print.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:get/get.dart';
import 'package:video_player/video_player.dart';
class VideoController extends GetxController {
var videos = [];
var isLoading = true.obs;
final String apiUrl =
'${AppLink.seferCairoServer}/ride/videos_driver/get.php';
@override
void onInit() {
fetchVideos();
super.onInit();
}
late VideoPlayerController videoPlayerController;
// Initialize the video player with the provided URL
Future<void> initializeVideo(String videoUrl) async {
videoPlayerController =
VideoPlayerController.networkUrl(Uri.parse(videoUrl));
await videoPlayerController.initialize();
videoPlayerController
.setLooping(true); // Set to true if you want the video to loop
update(); // Update the UI after the video has been initialized
}
// Play the video
void play() {
videoPlayerController.play();
update();
}
// Pause the video
void pause() {
videoPlayerController.pause();
update();
}
@override
void onClose() {
videoPlayerController
.dispose(); // Dispose of the video player controller when not in use
super.onClose();
}
void fetchVideos() async {
try {
var res = await CRUD().get(link: apiUrl, payload: {});
if (res != 'failure') {
videos = jsonDecode(res)['message'];
// Log.print('videos: ${videos}');
update();
} else {
mySnackeBarError('');
}
} catch (e) {
mySnackeBarError(e.toString());
} finally {
isLoading(false);
}
}
}

View File

@@ -0,0 +1,850 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:siro_driver/constant/box_name.dart';
import 'dart:async';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../constant/table_names.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/home/my_wallet/walet_captain.dart';
import '../../../views/widgets/elevated_btn.dart';
import '../../firebase/firbase_messge.dart';
import '../../functions/background_service.dart';
import '../../functions/crud.dart';
import '../../functions/location_background_controller.dart';
import '../../functions/location_controller.dart';
import '../payment/captain_wallet_controller.dart';
class HomeCaptainController extends GetxController {
bool isActive = false;
DateTime? activeStartTime;
Duration activeDuration = Duration.zero;
Timer? activeTimer;
Map data = {};
bool isHomeMapActive = true;
InlqBitmap carIcon = InlqBitmap.fromAsset('assets/images/car.png');
bool isMapReadyForCommands = false;
bool isLoading = true;
late double kazan = 0;
double latePrice = 0;
double heavyPrice = 0;
double comfortPrice = 0,
speedPrice = 0,
deliveryPrice = 0,
mashwariPrice = 0,
familyPrice = 0,
fuelPrice = 0;
double naturePrice = 0;
bool isCallOn = false;
String totalMoneyToday = '0';
double? rating = 5;
String rideId = '0';
String countRideToday = '0';
String totalMoneyInSEFER = '0';
String totalDurationToday = '0';
Timer? timer;
Timer? _cameraFollowTimer;
LatLng myLocation = const LatLng(33.5138, 36.2765);
String totalPoints = '0';
String countRefuse = '0';
bool mapType = false;
bool mapTrafficON = false;
double widthMapTypeAndTraffic = 50;
// === متغيرات الهيت ماب الجديدة ===
bool isHeatmapVisible = false;
Set<Polygon> heatmapPolygons =
{}; // سنستخدم Polygon لرسم المربعات على جوجل مابس
// Inject the LocationController class
// final locationController = Get.put(LocationController());
// الكود الصحيح
final locationController = Get.find<LocationController>();
// final locationBackController = Get.put(LocationBackgroundController());
String formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${duration.inHours}:$twoDigitMinutes:$twoDigitSeconds";
}
// دالة لتغيير حالة الهيت ماب (عرض/إخفاء)
void toggleHeatmap() async {
isHeatmapVisible = !isHeatmapVisible;
print("🔥 [Heatmap] Visibility toggled to: $isHeatmapVisible");
if (isHeatmapVisible) {
startHeatmapCycle();
} else {
_heatmapTimer?.cancel();
heatmapPolygons.clear();
print("🧹 [Heatmap] Polygons cleared.");
}
update(); // تحديث الواجهة
}
// داخل MapDriverController
// متغير لتخزين المربعات
// Set<Polygon> heatmapPolygons = {};
// دالة جلب البيانات ورسم الخريطة
Future<void> fetchAndDrawHeatmap() async {
print("🚀 [Heatmap] Fetching live data...");
// استخدم الرابط المباشر لملف JSON لسرعة قصوى
final String jsonUrl =
"https://ride.intaleq.xyz/intaleq/ride/heatmap_data.json";
try {
// نستخدم timestamp لمنع الكاش من الموبايل نفسه
final response = await http.get(
Uri.parse("$jsonUrl?t=${DateTime.now().millisecondsSinceEpoch}"));
if (response.statusCode == 200) {
final List<dynamic> data = json.decode(response.body);
print("✅ [Heatmap] Data received. Points count: ${data.length}");
_generatePolygons(data);
} else {
print("⚠️ [Heatmap] Server error: ${response.statusCode}");
}
} catch (e) {
print("❌ [Heatmap] Error: $e");
}
}
void _generatePolygons(List<dynamic> data) {
print("🎨 [Heatmap] Processing polygons...");
Set<Polygon> tempPolygons = {};
// الأوفست لرسم المربع (نصف حجم الشبكة)
// الشبكة دقتها 0.01 درجة، لذا نصفها 0.005
double offset = 0.005;
int highCount = 0, medCount = 0, lowCount = 0;
for (var point in data) {
double lat = double.parse(point['lat'].toString());
double lng = double.parse(point['lng'].toString());
String intensity = point['intensity'] ?? 'low';
int count = int.parse(point['count'].toString()); // ✅ جلب العدد
Color color;
Color strokeColor;
// 🧠 منطق الألوان: ندمج الذكاء (Intensity) مع العدد (Count)
if (intensity == 'high' || count >= 5) {
highCount++;
// منطقة مشتعلة (أحمر)
// إما فيها طلبات ضائعة (Timeout) أو فيها عدد كبير من الطلبات
color = Colors.red.withValues(alpha: 0.35);
strokeColor = Colors.red.withValues(alpha: 0.8);
} else if (intensity == 'medium' || count >= 3) {
medCount++;
// منطقة متوسطة (برتقالي)
color = Colors.orange.withValues(alpha: 0.35);
strokeColor = Colors.orange.withValues(alpha: 0.8);
} else {
lowCount++;
// منطقة خفيفة (أصفر)
color = Colors.yellow.withValues(alpha: 0.3);
strokeColor = Colors.yellow.withValues(alpha: 0.6);
}
// رسم المربع
tempPolygons.add(Polygon(
polygonId: PolygonId("$lat-$lng"),
// consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
points: [
LatLng(lat - offset, lng - offset),
LatLng(lat + offset, lng - offset),
LatLng(lat + offset, lng + offset),
LatLng(lat - offset, lng + offset),
],
fillColor: color,
strokeColor: strokeColor,
strokeWidth: 2,
));
}
heatmapPolygons = tempPolygons;
print(
"✨ [Heatmap] Rendering Done. (🔥 High: $highCount, 🟠 Med: $medCount, 🟡 Low: $lowCount)");
print("📍 [Heatmap] Total Polygons on Map: ${heatmapPolygons.length}");
update(); // تحديث الخريطة
}
Timer? _heatmapTimer;
// دالة لتشغيل الخريطة الحرارية كل فترة (كل 5 دقائق) لضمان نشاط البيانات
void startHeatmapCycle() {
_heatmapTimer?.cancel();
fetchAndDrawHeatmap();
// Refresh every 15 min instead of 5 to reduce data & battery usage
_heatmapTimer = Timer.periodic(const Duration(minutes: 15), (timer) {
if (isHeatmapVisible) {
print("🔄 [Heatmap] Periodic refresh started...");
fetchAndDrawHeatmap();
} else {
timer.cancel();
}
});
}
void goToWalletFromConnect() {
Get.back();
Get.back();
Get.to(() => WalletCaptainRefactored());
}
void changeRideId() {
rideId = 'rideId';
update();
}
void addCustomCarIcon() {
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
update();
}
String stringActiveDuration = '';
int _fatigueSeconds = 0; // عداد ثواني الإرهاق المؤقت
// ==========================================
// ====== 🛡️ Fatigue Monitoring System ======
// ==========================================
void _checkFatigueBeforeOnline() {
int totalSecondsToday = box.read('fatigue_total_seconds') ?? 0;
String? lastOfflineStr = box.read('fatigue_last_offline');
if (lastOfflineStr != null) {
DateTime lastOffline = DateTime.parse(lastOfflineStr);
// If offline for more than 6 continuous hours, reset the fatigue counter
if (DateTime.now().difference(lastOffline).inHours >= 6) {
totalSecondsToday = 0;
box.write('fatigue_total_seconds', 0);
}
}
if (totalSecondsToday >= 12 * 3600) {
// 12 Hours
_forceOfflineDueToFatigue();
throw Exception('Fatigue Limit Exceeded');
}
}
void _forceOfflineDueToFatigue() {
if (isActive) {
isActive = false;
locationController.stopLocationUpdates();
activeStartTime = null;
activeTimer?.cancel();
update();
}
Get.defaultDialog(
title: 'Safety First 🛑'.tr,
middleText:
'You have been driving for 12 hours. For your safety and compliance, please take a 6-hour break.'
.tr,
barrierDismissible: false,
titleStyle:
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
confirm: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () => Get.back(),
child: Text('OK'.tr, style: const TextStyle(color: Colors.white)),
),
);
}
void onButtonSelected() {
if (!Get.isRegistered<CaptainWalletController>()) {
Get.put(CaptainWalletController());
}
totalPoints = Get.find<CaptainWalletController>().totalPoints;
// Toggle Active State
isActive = !isActive;
if (isActive) {
try {
_checkFatigueBeforeOnline(); // Throws exception if tired
if (double.parse(totalPoints) > -200) {
locationController.startLocationUpdates();
HapticFeedback.heavyImpact();
activeStartTime = DateTime.now();
activeTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
activeDuration = DateTime.now().difference(activeStartTime!);
stringActiveDuration = formatDuration(activeDuration);
// Increment Fatigue Counter (write to box every 30s)
_fatigueSeconds++;
if (_fatigueSeconds % 30 == 0) {
int totalSeconds =
(box.read('fatigue_total_seconds') ?? 0) + _fatigueSeconds;
box.write('fatigue_total_seconds', totalSeconds);
_fatigueSeconds = 0;
if (totalSeconds >= 12 * 3600) {
// 12 hours
_forceOfflineDueToFatigue();
}
}
update();
});
} else {
locationController.stopLocationUpdates();
activeStartTime = null;
activeTimer?.cancel();
savePeriod(activeDuration);
activeDuration = Duration.zero;
box.write('fatigue_last_offline', DateTime.now().toIso8601String());
update();
}
} catch (e) {
// Driver is fatigued, revert state
isActive = false;
update();
}
} else {
locationController.stopLocationUpdates();
activeStartTime = null;
activeTimer?.cancel();
savePeriod(activeDuration);
activeDuration = Duration.zero;
// Save offline time for Fatigue Monitoring reset
box.write('fatigue_last_offline', DateTime.now().toIso8601String());
update();
}
}
// متغيرات العداد للحظر
RxString remainingBlockTimeStr = "".obs;
Timer? _blockTimer;
/// دالة الفحص والدايلوج
void checkAndShowBlockDialog() {
String? blockStr = box.read(BoxName.blockUntilDate);
if (blockStr == null || blockStr.isEmpty) return;
DateTime blockExpiry = DateTime.parse(blockStr);
DateTime now = DateTime.now();
if (now.isBefore(blockExpiry)) {
// 1. إجبار السائق على وضع الأوفلاين
box.write(BoxName.statusDriverLocation, 'blocked');
update();
// 2. بدء العداد
_startBlockCountdown(blockExpiry);
// 3. إظهار الديالوج المانع
Get.defaultDialog(
title: "Your account is temporarily restricted ⛔".tr,
titleStyle:
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
barrierDismissible: false, // 🚫 ممنوع الإغلاق بالضغط خارجاً
onWillPop: () async => false, // 🚫 ممنوع زر الرجوع في الأندرويد
content: Obx(() => Column(
children: [
const Icon(Icons.timer_off_outlined,
size: 50, color: Colors.orange),
const SizedBox(height: 15),
Text(
"You have exceeded the allowed cancellation limit (3 times).\nYou cannot work until the penalty expires."
.tr,
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
Text(
remainingBlockTimeStr.value, // 🔥 الوقت يتحدث هنا
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black87),
),
const SizedBox(height: 20),
],
)),
confirm: Obx(() {
// الزر يكون مفعلاً فقط عندما ينتهي الوقت
bool isFinished = remainingBlockTimeStr.value == "00:00:00" ||
remainingBlockTimeStr.value == "Done";
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: isFinished ? Colors.green : Colors.grey,
),
onPressed: isFinished
? () {
Get.back(); // إغلاق الديالوج
box.remove(BoxName.blockUntilDate); // إزالة الحظر
Get.snackbar("Welcome".tr, "You can now receive orders".tr,
backgroundColor: Colors.green);
}
: null, // زر معطل
child: Text(isFinished ? "Go Online".tr : "Wait for timer".tr),
);
}),
);
} else {
// الوقت انتهى أصلاً -> تنظيف
box.remove(BoxName.blockUntilDate);
}
}
/// دالة العداد التنازلي
void _startBlockCountdown(DateTime expiry) {
_blockTimer?.cancel();
_blockTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
DateTime now = DateTime.now();
if (now.isAfter(expiry)) {
// انتهى الوقت
remainingBlockTimeStr.value = "Done";
timer.cancel();
} else {
// حساب الفرق وتنسيقه
Duration diff = expiry.difference(now);
String twoDigits(int n) => n.toString().padLeft(2, "0");
String hours = twoDigits(diff.inHours);
String minutes = twoDigits(diff.inMinutes.remainder(60));
String seconds = twoDigits(diff.inSeconds.remainder(60));
remainingBlockTimeStr.value = "$hours:$minutes:$seconds";
}
});
}
@override
void onClose() {
print("🔥 [HomeCaptain] onClose called. Tearing down map resources...");
_blockTimer?.cancel();
activeTimer?.cancel();
_cameraFollowTimer?.cancel();
_heatmapTimer?.cancel();
stopTimer();
mapHomeCaptainController = null;
super.onClose();
}
void getRefusedOrderByCaptain() async {
DateTime today = DateTime.now();
int todayDay = today.day;
String driverId = box.read(BoxName.driverID).toString();
String customQuery = '''
SELECT COUNT(*) AS count
FROM ${TableName.driverOrdersRefuse}
WHERE driver_id = '$driverId'
AND created_at LIKE '%$todayDay%'
''';
try {
List<Map<String, dynamic>> results =
await sql.getCustomQuery(customQuery);
countRefuse = results[0]['count'].toString();
update();
if (double.parse(totalPoints) <= -200) {
// if (int.parse(countRefuse) > 3 || double.parse(totalPoints) <= -200) {
locationController.stopLocationUpdates();
activeStartTime = null;
activeTimer?.cancel();
savePeriod(activeDuration);
activeDuration = Duration.zero;
update();
Get.defaultDialog(
// backgroundColor: CupertinoColors.destructiveRed,
barrierDismissible: false,
title: 'You Are Stopped For this Day !'.tr,
content: Text(
'You Refused 3 Rides this Day that is the reason \nSee you Tomorrow!'
.tr,
style: AppStyle.title,
),
confirm: MyElevatedButton(
title: 'Ok , See you Tomorrow'.tr,
onPressed: () {
// إغلاق الديالوج والعودة قسرياً
navigatorKey.currentState?.pop();
Get.back();
}));
}
} catch (e) {}
}
void changeMapType() {
mapType = !mapType;
// heightButtomSheetShown = isButtomSheetShown == true ? 240 : 0;
update();
}
void changeMapTraffic() {
mapTrafficON = !mapTrafficON;
update();
}
// late IntaleqMapController mapHomeCaptainController;
IntaleqMapController? mapHomeCaptainController;
LatLng? _lastCameraLoc; // لتتبع آخر موقع حرك الكاميرا
// --- FIX 2: Smart Map Creation ---
void onMapCreated(IntaleqMapController controller) {
print("🔥 [HomeCaptain] onMapCreated started");
mapHomeCaptainController = controller;
// We delay the first move to ensure the native side is fully ready
Future.delayed(const Duration(milliseconds: 800), () {
if (isClosed || mapHomeCaptainController == null) return;
try {
var currentLoc = locationController.myLocation;
if (currentLoc.latitude != 0 &&
currentLoc.latitude != null &&
!currentLoc.latitude.isNaN) {
print(
"🔥 [HomeCaptain] Safely moving camera to: ${currentLoc.latitude}");
mapHomeCaptainController!.moveCamera(
CameraUpdate.newLatLngZoom(currentLoc, 17.5),
);
} else {
print("🔥 [HomeCaptain] Safely moving to default Damascus");
mapHomeCaptainController!.moveCamera(
CameraUpdate.newLatLngZoom(myLocation, 12),
);
}
// Mark as ready for regular listener updates
isMapReadyForCommands = true;
} catch (e) {
print("❌ [HomeCaptain] Map move failed: $e");
}
});
}
void savePeriod(Duration period) {
final periods = box.read<List<dynamic>>(BoxName.periods) ?? [];
periods.add(period.inSeconds);
box.write(BoxName.periods, periods);
}
Duration calculateTotalDuration() {
final periods = box.read<List<dynamic>>(BoxName.periods) ?? [];
Duration totalDuration = Duration.zero;
for (dynamic periodInSeconds in periods) {
final periodDuration = Duration(seconds: periodInSeconds);
totalDuration += periodDuration;
}
return totalDuration;
}
Timer? _localDurationTimer;
RxString totalDurationDisplay = "00:00:00".obs; // لعرض الوقت في الواجهة
Duration _currentDuration = Duration.zero; // لتخزين الوقت ككائن Duration
void startPeriodicExecution() async {
await getCaptainDurationOnToday();
String? initialDurationStr = totalDurationToday;
if (initialDurationStr != '0') {
// تحويل النص (01:20:30) إلى كائن Duration
List<String> parts = initialDurationStr.split(':');
_currentDuration = Duration(
hours: int.parse(parts[0]),
minutes: int.parse(parts[1]),
seconds: int.parse(parts[2]),
);
// بدء العداد المحلي
_startLocalClock();
}
// Timer.periodic(const Duration(seconds: 30), (timer) async {
// await getCaptainDurationOnToday();
// });
}
void _startLocalClock() {
_localDurationTimer?.cancel();
_localDurationTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
// زيادة ثانية واحدة محلياً
_currentDuration += const Duration(seconds: 1);
// تحديث النص المعروض في الواجهة (Formatting)
totalDurationDisplay.value = _formatDuration(_currentDuration);
// اختيارياً: كل 5 دقائق فقط، قم بتحديث القيمة من السيرفر للتأكد من المزامنة
if (timer.tick % 300 == 0) {
getCaptainDurationOnToday();
}
});
}
String _formatDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String hours = twoDigits(duration.inHours);
String minutes = twoDigits(duration.inMinutes.remainder(60));
String seconds = twoDigits(duration.inSeconds.remainder(60));
return "$hours:$minutes:$seconds";
}
void stopTimer() {
_localDurationTimer?.cancel();
}
getlocation() async {
isLoading = true;
update();
try {
// ننتظر جلب الموقع مع مهلة 10 ثوانٍ لتجنب التعليق
var locData = await locationController.getLocation().timeout(
const Duration(seconds: 10),
onTimeout: () => null,
);
if (locData != null && locData.latitude != null) {
myLocation = LatLng(locData.latitude!, locData.longitude!);
print(
"📍 [HomeCaptain] Location updated: ${myLocation.latitude}, ${myLocation.longitude}");
} else {
print(
"⚠️ [HomeCaptain] Could not get current location, using default.");
}
} catch (e) {
print("❌ Error in getlocation: $e");
} finally {
isLoading = false;
update();
}
}
Map walletDriverPointsDate = {};
Future getCaptainWalletFromBuyPoints() async {
// isLoading = true;
update();
var res = await CRUD().getWallet(
link: AppLink.getDriverPaymentPoints,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
isLoading = false;
// update();
if (res != 'failure') {
walletDriverPointsDate = jsonDecode(res);
double totalPointsDouble = double.parse(
walletDriverPointsDate['message'][0]['total_amount'].toString());
totalPoints = totalPointsDouble.toStringAsFixed(0);
update();
} else {
totalPoints = '0';
}
}
// 3. دالة نستدعيها عند قبول الطلب
void pauseHomeMapUpdates() {
isHomeMapActive = false;
update();
}
// 4. دالة نستدعيها عند العودة للصفحة الرئيسية
void resumeHomeMapUpdates() {
isHomeMapActive = true;
// تم حذف استدعاء onMapCreated المتكرر لمنع قفز الخريطة عند العودة
update();
}
@override
void onInit() async {
// ✅ تم إرجاعه كتعليق لمنع الديالوج عند التشغيل (كما كان في الكود الأصلي)
// bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
// if (permissionsGranted) {
// await BackgroundServiceHelper.startService();
// }
Get.put(FirebaseMessagesController());
addToken();
await getlocation();
onButtonSelected();
getDriverRate();
addCustomCarIcon();
getKazanPercent();
getPaymentToday();
getCountRideToday();
getAllPayment();
startPeriodicExecution();
getCaptainWalletFromBuyPoints();
// onMapCreated(mapHomeCaptainController!);
// totalPoints = Get.find<CaptainWalletController>().totalPoints.toString();
// getRefusedOrderByCaptain();
// 🔥 الفحص عند تشغيل التطبيق
checkAndShowBlockDialog();
box.write(BoxName.statusDriverLocation, 'off');
// 2. عدل الليسنر ليصبح مشروطاً
// Camera follow timer — only moves when the driver has
// actually moved > 15 meters, saving GPU/battery on idle.
_cameraFollowTimer = Timer.periodic(const Duration(seconds: 8), (timer) {
if (isClosed ||
!isHomeMapActive ||
mapHomeCaptainController == null ||
!isMapReadyForCommands ||
!isActive) return;
var loc = locationController.myLocation;
if (loc.latitude != 0 && loc.latitude != null && !loc.latitude.isNaN) {
// Skip if driver hasn't moved significantly
if (_lastCameraLoc != null) {
final double dist = Geolocator.distanceBetween(
_lastCameraLoc!.latitude,
_lastCameraLoc!.longitude,
loc.latitude,
loc.longitude,
);
if (dist < 15) return;
}
_lastCameraLoc = loc;
try {
if (mapHomeCaptainController != null) {
mapHomeCaptainController?.animateCamera(
CameraUpdate.newLatLngZoom(loc, 17.5),
);
}
} catch (e) {
print("❌ [HomeCaptain] Camera movement failed: $e");
}
}
});
// LocationController().getLocation();
super.onInit();
}
addToken() async {
String? fingerPrint = await storage.read(key: BoxName.fingerPrint);
final payload = {
'token': (box.read(BoxName.tokenDriver)),
'captain_id': (box.read(BoxName.driverID)).toString(),
'fingerPrint': (fingerPrint).toString()
};
// Log.print('payload: ${payload}');
CRUD().post(link: AppLink.addTokensDriver, payload: payload);
}
getPaymentToday() async {
var res = await CRUD().getWallet(
link: AppLink.getDriverPaymentToday,
payload: {'driverID': box.read(BoxName.driverID).toString()});
if (res != 'failure') {
data = jsonDecode(res);
totalMoneyToday = data['message'][0]['todayAmount'].toString();
update();
} else {}
}
getKazanPercent() async {
var res = await CRUD().get(
link: AppLink.getKazanPercent,
payload: {'country': box.read(BoxName.countryCode).toString()},
);
if (res != 'failure') {
var json = jsonDecode(res);
kazan = double.parse(json['message'][0]['kazan']);
naturePrice = double.parse(json['message'][0]['naturePrice']);
heavyPrice = double.parse(json['message'][0]['heavyPrice']);
latePrice = double.parse(json['message'][0]['latePrice']);
comfortPrice = double.parse(json['message'][0]['comfortPrice']);
speedPrice = double.parse(json['message'][0]['speedPrice']);
deliveryPrice = double.parse(json['message'][0]['deliveryPrice']);
mashwariPrice = double.parse(json['message'][0]['freePrice']);
familyPrice = double.parse(json['message'][0]['familyPrice']);
fuelPrice = double.parse(json['message'][0]['fuelPrice']);
}
update();
}
double mpg = 0;
calculateConsumptionFuel() {
mpg = fuelPrice / 12; //todo in register car add mpg in box
}
getCountRideToday() async {
var res = await CRUD().get(
link: AppLink.getCountRide,
payload: {'driver_id': box.read(BoxName.driverID).toString()});
data = jsonDecode(res);
countRideToday = data['message'][0]['count'].toString();
update();
}
getDriverRate() async {
var res = await CRUD().get(
link: AppLink.getDriverRate,
payload: {'driver_id': box.read(BoxName.driverID).toString()});
if (res != 'failure') {
var decod = jsonDecode(res);
if (decod['message'][0]['rating'] != null) {
rating = double.parse(decod['message'][0]['rating'].toString());
} else {
rating = 5.0; // Set a default value (e.g., 5.0 for full rating)
}
} else {
rating = 5;
}
}
getAllPayment() async {
var res = await CRUD().getWallet(
link: AppLink.getAllPaymentFromRide,
payload: {'driverID': box.read(BoxName.driverID).toString()});
if (res == 'failure') {
totalMoneyInSEFER = '0';
} else {
data = jsonDecode(res);
totalMoneyInSEFER = data['message'][0]['total_amount'];
}
update();
}
void changeToAppliedRide(String status) {
box.write(BoxName.rideStatus, status);
Log.print('rideStatus from homcaptain : ${box.read(BoxName.rideStatus)}');
update();
}
Future<void> getCaptainDurationOnToday() async {
try {
var res = await CRUD().get(
link: AppLink.getTotalDriverDurationToday,
payload: {'driver_id': box.read(BoxName.driverID).toString()},
);
if (res == null || res == 'failure') {
totalDurationToday = '0';
update();
return;
}
var data = jsonDecode(res);
totalDurationToday = data['message']?[0]?['total_duration'] ?? '0';
} catch (e) {
print('Error in getCaptainDurationOnToday: $e');
totalDurationToday = '0';
}
update();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
class NavigationStep {
final String instruction;
final String maneuver;
final double distance;
final String duration;
final LatLng startLocation;
final LatLng endLocation;
final String htmlInstructions;
NavigationStep({
required this.instruction,
required this.maneuver,
required this.distance,
required this.duration,
required this.startLocation,
required this.endLocation,
required this.htmlInstructions,
});
factory NavigationStep.fromJson(Map<String, dynamic> json) {
return NavigationStep(
instruction: json['html_instructions'] ?? '',
maneuver: json['maneuver'] ?? 'straight',
distance: (json['distance']['value'] ?? 0).toDouble(),
duration: json['duration']['text'] ?? '',
startLocation: LatLng(
json['start_location']['lat'].toDouble(),
json['start_location']['lng'].toDouble(),
),
endLocation: LatLng(
json['end_location']['lat'].toDouble(),
json['end_location']['lng'].toDouble(),
),
htmlInstructions: json['html_instructions'] ?? '',
);
}
// Get clean instruction text (remove HTML tags)
String get cleanInstruction {
return instruction
.replaceAll(RegExp(r'<[^>]*>'), '')
.replaceAll('&nbsp;', ' ');
}
// Get instruction icon based on maneuver
IconData get instructionIcon {
switch (maneuver.toLowerCase()) {
case 'turn-left':
return Icons.turn_left;
case 'turn-right':
return Icons.turn_right;
case 'turn-slight-left':
return Icons.turn_slight_left;
case 'turn-slight-right':
return Icons.turn_slight_right;
case 'turn-sharp-left':
return Icons.turn_sharp_left;
case 'turn-sharp-right':
return Icons.turn_sharp_right;
case 'uturn-left':
case 'uturn-right':
return Icons.u_turn_left;
case 'straight':
return Icons.straight;
case 'ramp-left':
return Icons.ramp_left;
case 'ramp-right':
return Icons.ramp_right;
case 'merge':
return Icons.merge;
case 'fork-left':
case 'fork-right':
return Icons.call_split;
case 'ferry':
return Icons.directions_boat;
case 'roundabout-left':
case 'roundabout-right':
return Icons.roundabout_left;
default:
return Icons.navigation;
}
}
}

View File

@@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:siro_driver/constant/api_key.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/controller/functions/tts.dart';
import '../../../main.dart';
/// Handles map-related logic: fetching routes, drawing polylines, and managing markers.
class NavigationService extends GetxService {
final CRUD _crud = CRUD();
final TextToSpeechController _tts = Get.put(TextToSpeechController());
final RxSet<Marker> markers = <Marker>{}.obs;
final RxSet<Polyline> polylines = <Polyline>{}.obs;
final RxString currentInstruction = "".obs;
InlqBitmap carIcon = InlqBitmap.defaultMarker;
InlqBitmap passengerIcon = InlqBitmap.defaultMarker;
InlqBitmap startIcon = InlqBitmap.defaultMarker;
InlqBitmap endIcon = InlqBitmap.defaultMarker;
@override
void onInit() {
super.onInit();
_loadCustomIcons();
}
void _loadCustomIcons() {
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
passengerIcon = InlqBitmap.fromAsset('assets/images/picker.png');
startIcon = InlqBitmap.fromAsset('assets/images/A.png');
endIcon = InlqBitmap.fromAsset('assets/images/b.png');
}
Future<Map<String, dynamic>?> getRoute({
required LatLng origin,
required LatLng destination,
}) async {
final url =
'${AppLink.googleMapsLink}directions/json?language=${box.read(BoxName.lang)}&destination=${destination.latitude},${destination.longitude}&origin=${origin.latitude},${origin.longitude}&key=${AK.mapAPIKEY}';
final response = await _crud.getGoogleApi(link: url, payload: {});
if (response != null && response['routes'].isNotEmpty) {
return response['routes'][0];
}
return null;
}
void drawRoute(Map<String, dynamic> routeData, {Color color = Colors.blue}) {
final pointsString = routeData["overview_polyline"]["points"];
final points = PolylineUtils.decode(pointsString);
final polyline = Polyline(
polylineId: PolylineId(routeData["summary"] ?? DateTime.now().toString()),
points: points,
width: 8,
color: color,
);
polylines.add(polyline);
}
void updateCarMarker(LatLng position, double heading) {
markers.removeWhere((m) => m.markerId.value == 'MyLocation');
markers.add(
Marker(
markerId: MarkerId('MyLocation'.tr),
position: position,
icon: carIcon,
rotation: heading,
anchor: const Offset(0.5, 0.5),
flat: true,
),
);
}
void setInitialMarkers(
LatLng passengerLocation, LatLng passengerDestination) {
markers.clear();
markers.add(Marker(
markerId: const MarkerId('passengerLocation'),
position: passengerLocation,
icon: passengerIcon,
));
markers.add(Marker(
markerId: const MarkerId('passengerDestination'),
position: passengerDestination,
icon: endIcon,
));
}
void clearRoutes() {
polylines.clear();
currentInstruction.value = "";
}
}

View File

@@ -0,0 +1,742 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'dart:math' as math;
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
import '../../../env/env.dart';
import '../../../main.dart';
import '../../../print.dart';
import '../../../views/home/Captin/driver_map_page.dart';
import '../../../views/home/Captin/orderCaptin/marker_generator.dart';
import '../../../views/widgets/mydialoug.dart';
import '../../firebase/local_notification.dart';
import '../../functions/crud.dart';
import '../../functions/location_controller.dart';
import '../../home/captin/home_captain_controller.dart';
class OrderRequestController extends GetxController
with WidgetsBindingObserver {
// --- متغيرات التايمر ---
double progress = 1.0;
int duration = 15;
int remainingTime = 15;
Timer? _timer;
bool applied = false;
final locationController = Get.put(LocationController());
// 🔥 متغير لمنع تكرار القبول
bool _isRideTakenHandled = false;
// --- الأيقونات والماركرز ---
InlqBitmap? driverIcon;
Map<MarkerId, Marker> markersMap = {};
Set<Marker> get markers => markersMap.values.toSet();
// --- البيانات والتحكم ---
// 🔥 تم إضافة myMapData لدعم السوكيت الجديد
List<dynamic>? myList;
Map<dynamic, dynamic>? myMapData;
IntaleqMapController? mapController;
// الإحداثيات (أزلنا late لتجنب الأخطاء القاتلة)
double latPassenger = 0.0;
double lngPassenger = 0.0;
double latDestination = 0.0;
double lngDestination = 0.0;
// --- متغيرات العرض ---
String passengerRating = "5.0";
String tripType = "Standard";
String totalTripDistance = "--";
String totalTripDuration = "--";
String tripPrice = "--";
String timeToPassenger = "Calculating...".tr;
String distanceToPassenger = "--";
// --- الخريطة ---
Set<Polyline> polylines = {};
// حالة التطبيق والصوت
bool isInBackground = false;
final AudioPlayer audioPlayer = AudioPlayer();
@override
Future<void> onInit() async {
// 🛑 حماية من الفتح المتكرر لنفس الطلب
if (Get.arguments == null) {
print("❌ OrderController Error: No arguments received.");
Get.back(); // إغلاق الصفحة فوراً
return;
}
super.onInit();
WidgetsBinding.instance.addObserver(this);
_checkOverlay();
// 🔥 تهيئة البيانات هي الخطوة الأولى والأهم
_initializeData();
_parseExtraData();
// 1. تجهيز أيقونة السائق
await _prepareDriverIcon();
// 2. وضع الماركرز المبدئية
_updateMarkers(
paxTime: "...",
paxDist: "",
destTime: totalTripDuration,
destDist: totalTripDistance);
// 3. رسم مبدئي
_initialMapSetup();
// 4. الاستماع للسوكيت
_listenForRideTaken();
// 5. حساب المسارين
await _calculateFullJourney();
// 6. تشغيل التايمر
startTimer();
}
// ----------------------------------------------------------------------
// 🔥🔥🔥 Smart Data Handling (List & Map Support) 🔥🔥🔥
// ----------------------------------------------------------------------
void _initializeData() {
var args = Get.arguments;
print("📦 Order Controller Received Type: ${args.runtimeType}");
print("📦 Order Controller Data: $args");
if (args != null) {
// الحالة 1: قائمة مباشرة (Legacy / Some Firebase formats)
if (args is List) {
myList = args;
}
// الحالة 2: خريطة (Map)
else if (args is Map) {
// أ) هل هي قادمة من Firebase وتحتوي على DriverList؟
if (args.containsKey('DriverList')) {
var listData = args['DriverList'];
if (listData is List) {
myList = listData;
} else if (listData is String) {
// أحياناً تصل كنص مشفر داخل الـ Map
try {
myList = jsonDecode(listData);
} catch (e) {
print("Error decoding DriverList: $e");
}
}
}
// ب) هل هي قادمة من Socket بالمفاتيح الرقمية ("0", "1", ...)؟
else {
myMapData = args;
}
}
}
// تعبئة الإحداثيات باستخدام الدالة الذكية _getValueAt
latPassenger = _parseCoord(_getValueAt(0));
lngPassenger = _parseCoord(_getValueAt(1));
latDestination = _parseCoord(_getValueAt(3));
lngDestination = _parseCoord(_getValueAt(4));
print(
"📍 Parsed Coordinates: Pax($latPassenger, $lngPassenger) -> Dest($latDestination, $lngDestination)");
}
/// 🔥 دالة ذكية تجلب القيمة سواء كانت البيانات في List أو Map
dynamic _getValueAt(int index) {
// الأولوية للقائمة
if (myList != null && index < myList!.length) {
return myList![index];
}
// ثم الخريطة (السوكيت) - المفاتيح عبارة عن String
if (myMapData != null && myMapData!.containsKey(index.toString())) {
return myMapData![index.toString()];
}
return null;
}
/// الدالة التي يستخدمها باقي الكود لجلب البيانات كنصوص
String _safeGet(int index) {
var val = _getValueAt(index);
if (val != null) {
return val.toString();
}
return "";
}
double _parseCoord(dynamic val) {
if (val == null) return 0.0;
String s = val.toString().replaceAll(',', '').trim();
if (s.contains(' ')) s = s.split(' ')[0];
return double.tryParse(s) ?? 0.0;
}
void _parseExtraData() {
passengerRating = _safeGet(33).isEmpty ? "5.0" : _safeGet(33);
tripType = _safeGet(31);
// Format numbers to avoid many decimal places
String rawDist = _safeGet(5);
if (rawDist.isNotEmpty) {
double? d = double.tryParse(rawDist);
totalTripDistance = d != null ? "${d.toStringAsFixed(1)} km" : rawDist;
}
String rawDur = _safeGet(19);
if (rawDur.isNotEmpty) {
double? d = double.tryParse(rawDur);
totalTripDuration = d != null ? "${d.toStringAsFixed(0)} min" : rawDur;
}
String rawPrice = _safeGet(2);
if (rawPrice.isNotEmpty) {
double? p = double.tryParse(rawPrice);
tripPrice = p != null ? p.toStringAsFixed(0) : rawPrice;
}
}
// ----------------------------------------------------------------------
// 🔥🔥🔥 Core Logic: Concurrent API Calls & Bounds 🔥🔥🔥
// ----------------------------------------------------------------------
Future<void> _calculateFullJourney() async {
// Don't block on mapController being null - we'll draw routes
// and markers first, then zoom when controller is ready
bool canZoom = mapController != null;
try {
// Reuse stored location from LocationController instead of
// making a duplicate GPS hardware call (already fetched in
// _initialMapSetup).
LatLng driverLatLng;
double driverHeading = 0.0;
if (Get.isRegistered<LocationController>()) {
final locCtrl = Get.find<LocationController>();
if (locCtrl.myLocation.latitude != 0 ||
locCtrl.myLocation.longitude != 0) {
driverLatLng = locCtrl.myLocation;
driverHeading = locCtrl.heading;
} else {
Position driverPos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
driverHeading = driverPos.heading;
}
} else {
Position driverPos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
driverHeading = driverPos.heading;
}
updateDriverLocation(driverLatLng, driverHeading);
// Clear old polylines to avoid "ghost lines"
polylines.clear();
var pickupFuture = _fetchRouteData(
start: driverLatLng,
end: LatLng(latPassenger, lngPassenger),
color: Colors.amber,
id: 'pickup_route');
var tripFuture = _fetchRouteData(
start: LatLng(latPassenger, lngPassenger),
end: LatLng(latDestination, lngDestination),
color: Colors.black,
id: 'trip_route',
getSteps: true); // 🔥 نطلب الخطوات للمسار
var results = await Future.wait([pickupFuture, tripFuture]);
var pickupResult = results[0];
var tripResult = results[1];
if (pickupResult != null) {
distanceToPassenger = pickupResult['distance_text'];
timeToPassenger = pickupResult['duration_text'];
polylines.add(pickupResult['polyline']);
}
if (tripResult != null) {
totalTripDistance = tripResult['distance_text'];
totalTripDuration = tripResult['duration_text'];
polylines.add(tripResult['polyline']);
// 🔥 تخزين استجابة السيرفر كاملة (بما فيها الـ points والـ instructions)
if (tripResult['raw_response'] != null) {
box.write('cached_trip_route', tripResult['raw_response']);
}
}
await _updateMarkers(
paxTime: timeToPassenger,
paxDist: distanceToPassenger,
destTime: totalTripDuration,
destDist: totalTripDistance);
// Now zoom to fit all polylines and markers (if controller available)
if (canZoom) {
zoomToFitRide();
}
update();
} catch (e) {
print("❌ Error in Journey Calculation: $e");
}
}
String _formatDistance(dynamic rawDist) {
if (rawDist == null || rawDist.toString().isEmpty) return "--";
double dist = double.tryParse(rawDist.toString()) ?? 0.0;
if (dist <= 0) return "--";
if (dist < 1000) return "${dist.toStringAsFixed(0)} m";
return "${(dist / 1000).toStringAsFixed(1)} km";
}
String _formatDuration(dynamic rawDur) {
if (rawDur == null || rawDur.toString().isEmpty) return "--";
double dur = double.tryParse(rawDur.toString()) ?? 0.0;
if (dur <= 0) return "1 min"; // Minimum 1 min for UI
if (dur < 60) return "${dur.toStringAsFixed(0)} sec";
return "${(dur / 60).toStringAsFixed(0)} min";
}
Future<Map<String, dynamic>?> _fetchRouteData(
{required LatLng start,
required LatLng end,
required Color color,
required String id,
bool getSteps = false}) async {
try {
if (start.latitude == 0 || end.latitude == 0) return null;
// Don't block on mapController — route data fetch is independent
final saasUrl = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: {
'fromLat': start.latitude.toString(),
'fromLng': start.longitude.toString(),
'toLat': end.latitude.toString(),
'toLng': end.longitude.toString(),
'steps': getSteps ? 'true' : 'false',
'alternatives': 'false',
'locale': 'ar',
});
final response = await http.get(saasUrl, headers: {
'x-api-key': Env.mapSaasKey,
'Content-Type': 'application/json',
});
if (response.statusCode != 200) {
throw Exception("Routing request failed: ${response.statusCode}");
}
final data = jsonDecode(response.body);
print("🛣️ Route API Response [$id]: ${data}");
// The map-saas API returns the route data directly at the root,
// with 'points' being an encoded polyline string.
final String? encodedPoints = data['points']?.toString();
if (encodedPoints != null && encodedPoints.isNotEmpty) {
List<LatLng> path = controllerDecodePolyline(encodedPoints);
print("📍 Path for [$id] has ${path.length} points.");
final num? rawDist = data['distance'] is num ? data['distance'] : null;
final num? rawDur = data['duration'] is num ? data['duration'] : null;
final distanceText = data['distance_text'] ?? _formatDistance(rawDist);
final durationText = data['duration_text'] ?? _formatDuration(rawDur);
Polyline polyline = Polyline(
polylineId: PolylineId(id),
color: color,
width: 5,
points: path,
);
return {
'distance_text': distanceText,
'duration_text': durationText,
'polyline': polyline,
'encoded_polyline': encodedPoints,
'raw_response': response.body, // 🔥 نمرر الـ JSON كاملاً
};
}
} catch (e) {
print("Route Fetch Error: $e");
}
return null;
}
void zoomToFitRide() {
if (mapController == null) return;
List<LatLng> allPoints = [];
// Add all polyline points to the bounds calculation
for (var polyline in polylines) {
allPoints.addAll(polyline.points);
}
// Fallback to basic markers if polylines are empty
if (allPoints.isEmpty) {
allPoints.addAll([
LatLng(latPassenger, lngPassenger),
LatLng(latDestination, lngDestination),
]);
}
if (allPoints.isEmpty) return;
double minLat = allPoints.first.latitude;
double maxLat = allPoints.first.latitude;
double minLng = allPoints.first.longitude;
double maxLng = allPoints.first.longitude;
for (var p in allPoints) {
if (p.latitude < minLat) minLat = p.latitude;
if (p.latitude > maxLat) maxLat = p.latitude;
if (p.longitude < minLng) minLng = p.longitude;
if (p.longitude > maxLng) maxLng = p.longitude;
}
// Add some padding to the bounds
double latPad = (maxLat - minLat) * 0.25;
double lngPad = (maxLng - minLng) * 0.2;
mapController!.animateCamera(CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(minLat - latPad, minLng - lngPad),
northeast: LatLng(maxLat + latPad, maxLng + lngPad),
),
));
}
// ----------------------------------------------------------------------
// Markers & Setup
// ----------------------------------------------------------------------
Future<void> _prepareDriverIcon() async {
driverIcon = await MarkerGenerator.createDriverMarker();
}
Future<void> _updateMarkers(
{required String paxTime,
required String paxDist,
String? destTime,
String? destDist}) async {
// حماية إذا لم يتم جلب الإحداثيات
if (latPassenger == 0 || latDestination == 0) return;
final InlqBitmap pickupIcon =
await MarkerGenerator.createCustomMarkerBitmap(
title: paxTime,
subtitle: paxDist,
color: Colors.amber.shade900, // Matching the amber pickup line
iconData: Icons.person_pin_circle,
);
final InlqBitmap dropoffIcon =
await MarkerGenerator.createCustomMarkerBitmap(
title: destTime ?? totalTripDuration,
subtitle: destDist ?? totalTripDistance,
color: Colors.red.shade800,
iconData: Icons.flag,
);
markersMap[const MarkerId('pax')] = Marker(
markerId: const MarkerId('pax'),
position: LatLng(latPassenger, lngPassenger),
icon: pickupIcon,
anchor: const Offset(0.5, 0.85),
);
markersMap[const MarkerId('dest')] = Marker(
markerId: const MarkerId('dest'),
position: LatLng(latDestination, lngDestination),
icon: dropoffIcon,
anchor: const Offset(0.5, 0.85),
);
update();
}
void _initialMapSetup() async {
Position driverPos = await Geolocator.getCurrentPosition();
LatLng driverLatLng = LatLng(driverPos.latitude, driverPos.longitude);
if (driverIcon != null) {
markersMap[const MarkerId('driver')] = Marker(
markerId: const MarkerId('driver'),
position: driverLatLng,
icon: driverIcon!,
rotation: driverPos.heading,
anchor: const Offset(0.5, 0.5),
flat: true,
zIndex: 10);
}
if (latPassenger != 0 && lngPassenger != 0) {
polylines.add(Polyline(
polylineId: const PolylineId('temp_line'),
points: [driverLatLng, LatLng(latPassenger, lngPassenger)],
color: Colors.grey,
width: 2,
));
zoomToFitRide();
}
update();
}
void updateDriverLocation(LatLng newPos, double heading) {
if (driverIcon != null) {
markersMap[const MarkerId('driver')] = Marker(
markerId: const MarkerId('driver'),
position: newPos,
icon: driverIcon!,
rotation: heading,
anchor: const Offset(0.5, 0.5),
flat: true,
zIndex: 10,
);
update();
}
}
void onMapCreated(IntaleqMapController controller) {
mapController = controller;
_calculateFullJourney();
}
// --- قبول الطلب وإدارة التايمر ---
void startTimer() {
_timer?.cancel();
remainingTime = duration;
_playAudio();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (remainingTime <= 0) {
timer.cancel();
_stopAudio();
if (!applied) Get.back();
} else {
remainingTime--;
progress = remainingTime / duration;
update();
}
});
}
void endTimer() => _timer?.cancel();
void changeApplied() => applied = true;
void _playAudio() async {
try {
await audioPlayer.setAsset('assets/order.mp3', preload: true);
await audioPlayer.setLoopMode(LoopMode.one);
await audioPlayer.play();
} catch (e) {
print(e);
}
}
void _stopAudio() => audioPlayer.stop();
void _listenForRideTaken() {
if (locationController.socket != null) {
locationController.socket!.off('ride_taken');
locationController.socket!.on('ride_taken', (data) {
if (_isRideTakenHandled) return;
String takenRideId = data['ride_id'].toString();
String myCurrentRideId = _safeGet(16);
String whoTookIt = data['taken_by_driver_id'].toString();
String myDriverId = box.read(BoxName.driverID).toString();
if (takenRideId == myCurrentRideId && whoTookIt != myDriverId) {
_isRideTakenHandled = true;
endTimer();
// 1. حذف الإشعار من شريط التنبيهات فوراً
NotificationController().cancelOrderNotification();
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
// إغلاق أي ديالوج مفتوح قسرياً
if (Get.isDialogOpen ?? false) {
navigatorKey.currentState?.pop();
}
Get.back();
mySnackbarInfo("The order has been accepted by another driver.".tr);
}
});
}
}
// Lifecycle
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) {
isInBackground = true;
} else if (state == AppLifecycleState.resumed) {
isInBackground = false;
FlutterOverlayWindow.closeOverlay();
}
}
void _checkOverlay() async {
if (Platform.isAndroid && await FlutterOverlayWindow.isActive()) {
await FlutterOverlayWindow.closeOverlay();
}
}
// Accept Order Logic
Future<void> acceptOrder() async {
endTimer();
_stopAudio();
// 1. إرسال الطلب
var res = await CRUD()
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
'id': _safeGet(16),
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'passengerToken': _safeGet(9),
'driver_id': box.read(BoxName.driverID),
});
Log.print('res from orderrequestpage: ${res}');
// ============================================================
// تصحيح: فحص الرد بدقة (Map أو String)
// ============================================================
bool isFailure = false;
if (res is Map && res['status'] == 'failure') {
isFailure = true;
} else if (res == 'failure') {
isFailure = true;
}
if (isFailure) {
// ⛔ حالة الفشل: الطلب مأخوذ
MyDialog().getDialog(
"Sorry, the order was taken by another driver.".tr, '', () {
// بما أن MyDialog يغلق نفسه الآن، نحتاج Get.back() واحدة فقط لإغلاق صفحة الطلب
Get.back();
});
} else {
// ✅ حالة النجاح
// حماية من الكراش: التأكد من وجود HomeCaptainController قبل استخدامه
if (!Get.isRegistered<HomeCaptainController>()) {
Get.put(HomeCaptainController());
} else {
Get.find<HomeCaptainController>().changeRideId();
}
box.write(BoxName.statusDriverLocation, 'on');
changeApplied();
var rideArgs = {
'passengerLocation': '${_safeGet(0)},${_safeGet(1)}',
'passengerDestination': '${_safeGet(3)},${_safeGet(4)}',
'Duration': totalTripDuration,
'totalCost': _safeGet(26),
'Distance': totalTripDistance,
'name': _safeGet(8),
'phone': _safeGet(10),
'email': _safeGet(28),
'WalletChecked': _safeGet(13),
'tokenPassenger': _safeGet(9),
'direction':
'https://www.google.com/maps/dir/${_safeGet(0)}/${_safeGet(1)}/',
'DurationToPassenger': timeToPassenger,
'rideId': _safeGet(16),
'passengerId': _safeGet(7),
'driverId': _safeGet(18),
'durationOfRideValue': totalTripDuration,
'paymentAmount': _safeGet(2),
'paymentMethod': _safeGet(13) == 'true' ? 'visa' : 'cash',
'isHaveSteps': _safeGet(20),
'step0': _safeGet(21),
'step1': _safeGet(22),
'step2': _safeGet(23),
'step3': _safeGet(24),
'step4': _safeGet(25),
'passengerWalletBurc': _safeGet(26),
'timeOfOrder': DateTime.now().toString(),
'totalPassenger': _safeGet(2),
'carType': _safeGet(31),
'kazan': _safeGet(32),
'startNameLocation': _safeGet(29),
'endNameLocation': _safeGet(30),
};
box.write(BoxName.rideArguments, rideArgs);
// الانتقال النهائي
Get.off(() => PassengerLocationMapPage(), arguments: rideArgs);
}
}
@override
void onClose() {
locationController.socket?.off('ride_taken');
audioPlayer.dispose();
WidgetsBinding.instance.removeObserver(this);
_timer?.cancel();
// mapController?.dispose();
super.onClose();
}
List<LatLng> controllerDecodePolyline(String encoded) {
List<LatLng> points = [];
int index = 0, len = encoded.length;
int lat = 0, lng = 0;
while (index < len) {
int b, shift = 0, result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
points.add(LatLng(lat / 1E5, lng / 1E5));
}
return points;
}
}

View File

@@ -0,0 +1,212 @@
<style>
* { box-sizing: border-box; }
.wrap { padding: 1.25rem 1rem; font-size: 14px; color: var(--color-text-primary); direction: rtl; }
h1 { font-size: 18px; font-weight: 500; margin: 0 0 3px; }
.sub { font-size: 13px; color: var(--color-text-secondary); margin: 0 0 1.25rem; }
.badge { display: inline-flex; align-items: center; font-size: 11px; font-weight: 500; padding: 2px 8px; border-radius: 20px; white-space: nowrap; }
.b-ok { background: var(--color-background-success); color: var(--color-text-success); }
.b-new { background: var(--color-background-danger); color: var(--color-text-danger); }
.b-med { background: var(--color-background-warning); color: var(--color-text-warning); }
.b-min { background: var(--color-background-info); color: var(--color-text-info); }
.progress-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 1.25rem; }
.pcard { background: var(--color-background-secondary); border-radius: var(--border-radius-md); padding: 12px 14px; }
.pcard .val { font-size: 28px; font-weight: 500; }
.pcard .lbl { font-size: 12px; color: var(--color-text-secondary); margin-top: 2px; }
.ok-val { color: var(--color-text-success); }
.bad-val { color: var(--color-text-danger); }
.new-val { color: var(--color-text-warning); }
.section { margin-bottom: 1.4rem; }
.section-hdr { font-size: 14px; font-weight: 500; margin: 0 0 8px; display: flex; align-items: center; gap: 8px; }
.card { background: var(--color-background-primary); border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-lg); margin-bottom: 8px; overflow: hidden; }
.card.fixed { border-right: 3px solid var(--color-border-success); }
.card.broken{ border-right: 3px solid var(--color-border-danger); }
.card.new { border-right: 3px solid var(--color-border-warning); }
.card.minor { border-right: 3px solid var(--color-border-info); }
.ch { display: flex; align-items: flex-start; gap: 8px; padding: 10px 14px; cursor: pointer; }
.ch:hover { background: var(--color-background-secondary); }
.ch-icon { font-size: 15px; flex-shrink: 0; margin-top: 1px; }
.ch-title { font-size: 13.5px; font-weight: 500; flex: 1; line-height: 1.4; }
.ch-badge { flex-shrink: 0; }
.chev { font-size: 11px; color: var(--color-text-tertiary); transition: transform .2s; margin-right: auto; margin-left: 4px; }
.chev.open { transform: rotate(90deg); }
.cb { display: none; padding: 0 14px 14px; border-top: 0.5px solid var(--color-border-tertiary); }
.cb.open { display: block; }
.cb p { font-size: 13px; color: var(--color-text-secondary); line-height: 1.7; margin: 8px 0 6px; }
pre { font-family: var(--font-mono); font-size: 11.5px; background: var(--color-background-tertiary); border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-md); padding: 9px 11px; overflow-x: auto; margin: 6px 0; line-height: 1.6; white-space: pre; }
.fix { background: var(--color-background-success); border-radius: var(--border-radius-md); padding: 8px 11px; margin-top: 8px; font-size: 13px; line-height: 1.6; }
.fix strong { color: var(--color-text-success); font-size: 11px; display: block; margin-bottom: 2px; }
.warn { background: var(--color-background-warning); border-radius: var(--border-radius-md); padding: 8px 11px; margin-top: 8px; font-size: 13px; line-height: 1.6; }
.warn strong { color: var(--color-text-warning); font-size: 11px; display: block; margin-bottom: 2px; }
code { font-family: var(--font-mono); font-size: 12px; background: var(--color-background-secondary); padding: 0 4px; border-radius: 3px; }
.score-row { display: flex; align-items: center; gap: 10px; font-size: 13px; margin-bottom: 7px; }
.score-lbl { min-width: 160px; color: var(--color-text-secondary); }
.strack { flex: 1; height: 6px; background: var(--color-border-tertiary); border-radius: 3px; position: relative; }
.sfill { height: 100%; border-radius: 3px; }
.sval { min-width: 36px; font-size: 12px; color: var(--color-text-secondary); text-align: left; }
</style>
<div class="wrap">
<h1>مراجعة النسخة المحدّثة — V2</h1>
<p class="sub">مقارنة مع المراجعة السابقة · 16 مشكلة فُحصت</p>
<div class="progress-row">
<div class="pcard"><div class="val ok-val">11</div><div class="lbl">مشكلة مُصلحة ✅</div></div>
<div class="pcard"><div class="val bad-val">2</div><div class="lbl">مشكلة جديدة أدخلتها الإصلاحات ⚠️</div></div>
<div class="pcard"><div class="val new-val">3</div><div class="lbl">مشكلة لم تُعالج بعد</div></div>
<div class="pcard"><div class="val ok-val">69%</div><div class="lbl">تحسن من المراجعة الأولى</div></div>
</div>
<div class="score-row"><span class="score-lbl">صحة المنطق البرمجي</span><div class="strack"><div class="sfill" style="width:72%;background:#3B8BD4"></div></div><span class="sval">72% ↑</span></div>
<div class="score-row"><span class="score-lbl">نظافة الكود</span><div class="strack"><div class="sfill" style="width:63%;background:#1D9E75"></div></div><span class="sval">63% ↑</span></div>
<div class="score-row" style="margin-bottom:1.4rem"><span class="score-lbl">قابلية الصيانة</span><div class="strack"><div class="sfill" style="width:58%;background:#1D9E75"></div></div><span class="sval">58% ↑</span></div>
<!-- FIXED -->
<div class="section">
<div class="section-hdr"><span class="badge b-ok">✅ مُصلح</span> ما تم إصلاحه بشكل صحيح</div>
<div class="card fixed">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title">C-1 — استبدال الحلقة التكرارية والاستدعاء الذاتي بـ <code>Timer.periodic</code></span><span class="ch-badge badge b-ok">ممتاز</span><span class="chev"></span></div>
<div class="cb"><p>تم حذف <code>updateLocation()</code> كاملاً واستبدالها بـ <code>startUpdateLocationTimer()</code> و <code>stopUpdateLocationTimer()</code>. التايمر مسجّل في <code>onClose()</code> و <code>_stopAllServices()</code>. إصلاح ممتاز.</p>
<div class="warn"><strong>ملاحظة مهمة</strong>لا يظهر في الكود استدعاء لـ <code>startUpdateLocationTimer()</code> من أي مكان. يجب التأكد أنها تُستدعى من الـ View أو من <code>startRideFromDriver()</code>.</div></div>
</div>
<div class="card fixed">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title">C-4 — تحديث <code>myLocation</code> في <code>_handleLocationUpdate()</code></span><span class="chev"></span></div>
<div class="cb"><pre>void _handleLocationUpdate(geo.Position pos) {
final newLoc = LatLng(pos.latitude, pos.longitude);
myLocation = newLoc; // ← [Fix C-4] ✅ صحيح
// ...</pre></div>
</div>
<div class="card fixed">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title">M-4 — دمج <code>checkForNextStep()</code> مع <code>_checkNavigationStep()</code></span><span class="chev"></span></div>
<div class="cb"><p><code>checkForNextStep</code> أصبحت wrapper بسيط يستدعي <code>_checkNavigationStep</code>. منطق واحد، لا تعارض.</p></div>
</div>
<div class="card fixed">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title">M-5 — <code>disposeEverything()</code> لا تستدعي <code>onClose()</code> يدوياً</span><span class="chev"></span></div>
<div class="cb"><pre>void disposeEverything() {
_stopAllServices(); // ✅ بدون onClose()
}</pre></div>
</div>
<div class="card fixed">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title">C-3 جزئي — دالة مساعدة <code>_parseDistanceToMeters()</code> مشتركة</span><span class="chev"></span></div>
<div class="cb"><p>تم استخراج منطق تحليل المسافة إلى دالة واحدة تستخدمها كلا <code>finishRideFromDriver()</code> و <code>_validateTripDistance()</code>. يحل مشكلة التضارب في الوحدات.</p>
<div class="warn"><strong>لم يُحل كاملاً</strong>التحقق من المسافة لا يزال يحدث مرتين (انظر مشكلة C-3 أدناه).</div></div>
</div>
<div class="card fixed">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title">M-1 + M-2 + M-6 + N-1 + N-5 — إصلاحات طفيفة متعددة</span><span class="chev"></span></div>
<div class="cb">
<p><strong>M-1:</strong> <code>jitterMeters</code><code>jitterKm = 0.01</code></p>
<p><strong>M-2:</strong> <code>distance</code> المحلية → <code>distToPassenger</code></p>
<p><strong>M-6:</strong> تعليق يوضح أن الوحدة كيلومتر ✅</p>
<p><strong>N-1:</strong> <code>&directionsmode</code><code>?directionsmode</code></p>
<p><strong>N-5:</strong> إضافة <code>update()</code> في <code>getLocationArea()</code></p>
<p><strong>M-3:</strong> حذف <code>_performanceReadings</code> والمتغيرات الميتة ✅</p>
</div>
</div>
</div>
<!-- NEW BUGS -->
<div class="section">
<div class="section-hdr"><span class="badge b-new">🚨 جديد</span> مشاكل أدخلتها الإصلاحات</div>
<div class="card new">
<div class="ch" onclick="t(this)"><span class="ch-icon">🚨</span><span class="ch-title">BUG جديد — <code>Completer</code> في C-2 يُسبب Deadlock عند إغلاق الديالوج بـ Back</span><span class="ch-badge badge b-new">حرج</span><span class="chev"></span></div>
<div class="cb">
<p>الإصلاح استخدم <code>Completer</code> بشكل صحيح لحل مشكلة الـ callback الآني، لكنه أدخل مشكلة أخرى: لو أغلق المستخدم الديالوج بزر الرجوع (Back) في Android بدون ضغط OK، فإن <code>completer.future</code> لن تكتمل أبداً، والدالة ستبقى معلّقة (deadlock) لأن <code>_validateTripDistance()</code> هي <code>async</code> وتنتظر نتيجة لن تأتي:</p>
<pre>final completer = Completer&lt;bool&gt;();
MyDialog().getDialog('Exit Ride?'.tr, '', () {
if (!completer.isCompleted) completer.complete(true);
Get.back();
});
return await completer.future; // ← ينتظر للأبد إذا أُغلق بـ Back</pre>
<div class="fix"><strong>الحل</strong>أضف <code>barrierDismissible: false</code> للديالوج، أو استخدم <code>completer.complete(false)</code> عند إغلاق الديالوج بدون تأكيد (عبر <code>WillPopScope</code> أو <code>onDismissed</code> callback في <code>MyDialog</code>).</div>
</div>
</div>
<div class="card new">
<div class="ch" onclick="t(this)"><span class="ch-icon">🚨</span><span class="ch-title">C-3 لا يزال — المستخدم يرى ديالوجَي تأكيد متتاليَين عند إنهاء الرحلة بالزر</span><span class="ch-badge badge b-new">حرج</span><span class="chev"></span></div>
<div class="cb">
<p>رغم إضافة <code>_parseDistanceToMeters()</code>، تدفق الكود لا يزال يُقدّم ديالوجَين:</p>
<pre>// finishRideFromDriver(isFromSlider: false):
MyDialog().getDialog('Are you sure to exit ride?', '', () {
Get.back();
finishRideFromDriver1(); // ← isFromSlider = false افتراضياً
});
// finishRideFromDriver1():
if (!await _validateTripDistance(false)) return; // ← يُقدّم ديالوجاً ثانياً!</pre>
<p>المستخدم يرى "هل أنت متأكد؟" → يضغط OK → يرى "Exit Ride?" مرة ثانية → ينتظر مجدداً.</p>
<div class="fix"><strong>الحل</strong>احذف الديالوج من <code>finishRideFromDriver()</code> وأبقه في <code>_validateTripDistance()</code> فقط. أو مرّر <code>isFromSlider: true</code> لما يأتي من موافقة مسبقة.</div>
</div>
</div>
</div>
<!-- REMAINING -->
<div class="section">
<div class="section-hdr"><span class="badge b-med">⚠️ لم تُعالج</span> مشاكل لا تزال قائمة</div>
<div class="card broken">
<div class="ch" onclick="t(this)"><span class="ch-icon">⚠️</span><span class="ch-title">M-7 — Null checks على <code>String</code> غير قابلة للـ null</span><span class="chev"></span></div>
<div class="cb">
<pre>if (isSocialPressed == true && passengerId != null && rideId != null) {
// ^^^^^^^^^^^ دائماً non-null</pre>
<p>لو <code>passengerId == ''</code> يمر الشرط ويُرسل بيانات فارغة للسيرفر. الفحص الصحيح: <code>passengerId.isNotEmpty && rideId.isNotEmpty</code>.</p>
</div>
</div>
<div class="card broken">
<div class="ch" onclick="t(this)"><span class="ch-icon">⚠️</span><span class="ch-title">N-2 — تأخير 1 ثانية Hardcoded في <code>argumentLoading()</code></span><span class="chev"></span></div>
<div class="cb">
<pre>await Future.delayed(const Duration(seconds: 1));
await getRoute(...);</pre>
<p>لا يزال موجوداً. Race condition يجب معالجته بـ <code>Completer</code> بدلاً من تخمين الوقت.</p>
</div>
</div>
<div class="card broken">
<div class="ch" onclick="t(this)"><span class="ch-icon">⚠️</span><span class="ch-title">N-4 — <code>step0</code> إلى <code>step4</code> بدلاً من <code>List&lt;String&gt;</code></span><span class="chev"></span></div>
<div class="cb">
<pre>String step0 = ''; String step1 = ''; // ...
step0 = Get.arguments['step0']?.toString() ?? '';
step1 = Get.arguments['step1']?.toString() ?? '';</pre>
<p>لا تزال 5 متغيرات منفصلة. <code>List&lt;String&gt; steps = List.filled(5, '')</code> أوضح وأسهل في المعالجة.</p>
</div>
</div>
</div>
<!-- STILL MINOR -->
<div class="section">
<div class="section-hdr"><span class="badge b-min"> بسيطة</span> ملاحظات إضافية على هذه النسخة</div>
<div class="card minor">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title"><code>_suggestOptimization()</code> لا تزال موجودة لكن لا يستدعيها أحد</span><span class="chev"></span></div>
<div class="cb"><p>بعد حذف <code>_performanceReadings</code> و <code>_analyzePerformance()</code>، بقيت <code>_suggestOptimization()</code> معزولة. إما أن تُستدعى من مكان ما أو تُحذف.</p></div>
</div>
<div class="card minor">
<div class="ch" onclick="t(this)"><span class="ch-icon"></span><span class="ch-title">الاستيرادات المكررة لـ <code>dart:math</code> و <code>geolocator</code> لا تزال</span><span class="chev"></span></div>
<div class="cb">
<pre>import 'dart:math';
import 'dart:math' as math; // مكرر
import 'package:geolocator/geolocator.dart' as geo;
import 'package:geolocator/geolocator.dart'; // مكرر</pre>
<p>يُسبب تحذيرات من المحلل ويُشوّش قراءة الكود. احذف النسخة غير المعرّفة.</p>
</div>
</div>
</div>
</div>
<script>
function t(header) {
const b = header.nextElementSibling;
const ch = header.querySelector('.chev');
const o = b.classList.contains('open');
b.classList.toggle('open', !o);
if (ch) ch.classList.toggle('open', !o);
}
</script>

View File

@@ -0,0 +1,106 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/main.dart';
class WorkSlot {
int dayOfWeek; // 1=Mon ... 7=Sun
TimeOfDay startTime;
TimeOfDay endTime;
bool isActive;
WorkSlot({required this.dayOfWeek, required this.startTime, required this.endTime, this.isActive = true});
Map<String, dynamic> toJson() => {
'day': dayOfWeek, 'startH': startTime.hour, 'startM': startTime.minute,
'endH': endTime.hour, 'endM': endTime.minute, 'active': isActive,
};
factory WorkSlot.fromJson(Map<String, dynamic> json) => WorkSlot(
dayOfWeek: json['day'] ?? 1,
startTime: TimeOfDay(hour: json['startH'] ?? 8, minute: json['startM'] ?? 0),
endTime: TimeOfDay(hour: json['endH'] ?? 17, minute: json['endM'] ?? 0),
isActive: json['active'] ?? true,
);
String get dayName {
const days = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
return days[dayOfWeek];
}
String get dayNameAr {
const days = ['', 'الإثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد'];
return days[dayOfWeek];
}
String formatTime(TimeOfDay t) => '${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
String get timeRange => '${formatTime(startTime)} - ${formatTime(endTime)}';
}
class ScheduleController extends GetxController {
List<WorkSlot> schedule = [];
@override
void onInit() {
super.onInit();
_loadSchedule();
}
void _loadSchedule() {
final saved = box.read('work_schedule');
if (saved != null) {
try {
final list = jsonDecode(saved) as List;
schedule = list.map((e) => WorkSlot.fromJson(e)).toList();
} catch (_) {
_initDefault();
}
} else {
_initDefault();
}
update();
}
void _initDefault() {
schedule = List.generate(7, (i) => WorkSlot(
dayOfWeek: i + 1,
startTime: const TimeOfDay(hour: 8, minute: 0),
endTime: const TimeOfDay(hour: 18, minute: 0),
isActive: i < 6, // الجمعة عطلة
));
}
void _save() {
box.write('work_schedule', jsonEncode(schedule.map((s) => s.toJson()).toList()));
update();
}
void toggleDay(int dayOfWeek) {
final slot = schedule.firstWhere((s) => s.dayOfWeek == dayOfWeek);
slot.isActive = !slot.isActive;
_save();
}
void updateStartTime(int dayOfWeek, TimeOfDay time) {
schedule.firstWhere((s) => s.dayOfWeek == dayOfWeek).startTime = time;
_save();
}
void updateEndTime(int dayOfWeek, TimeOfDay time) {
schedule.firstWhere((s) => s.dayOfWeek == dayOfWeek).endTime = time;
_save();
}
double get totalWeeklyHours {
double total = 0;
for (var s in schedule) {
if (!s.isActive) continue;
final startMin = s.startTime.hour * 60 + s.startTime.minute;
final endMin = s.endTime.hour * 60 + s.endTime.minute;
total += (endMin - startMin) / 60;
}
return total;
}
int get activeDays => schedule.where((s) => s.isActive).length;
}

View File

@@ -0,0 +1,14 @@
import 'package:get/get.dart';
class MyMenuController extends GetxController {
bool isDrawerOpen = true;
void getDrawerMenu() {
if (isDrawerOpen == true) {
isDrawerOpen = false;
} else {
isDrawerOpen = true;
}
update();
}
}

View File

@@ -0,0 +1,31 @@
import 'package:intaleq_maps/intaleq_maps.dart';
List<LatLng> decodePolylineIsolate(String encoded) {
List<LatLng> points = [];
int index = 0, len = encoded.length;
int lat = 0, lng = 0;
while (index < len) {
int b, shift = 0, result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.codeUnitAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
points.add(LatLng(lat / 1E5, lng / 1E5));
}
return points;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,145 @@
// lib/controllers/navigation/route_matcher_worker.dart
import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:math';
/// Worker entrypoint (spawnUri/spawn).
/// Messages:
/// - init: {'type':'init','coords': Float64List}
/// - match: {'type':'match','id': int, 'lat': double, 'lng': double, 'lastIndex': int, 'window': int}
/// - dispose: {'type':'dispose'}
///
/// Responses are sent back as Map via SendPort:
/// - {'type':'ready'}
/// - {'type':'matchResult','id': id, 'index': overallIndex, 'lat': lat, 'lng': lng, 'dist': meters}
void routeMatcherIsolateEntry(SendPort sendPort) {
final ReceivePort port = ReceivePort();
sendPort.send({'type': 'ready', 'port': port.sendPort});
Float64List? flat; // [lat,lng,lat,lng,...]
int nPoints = 0;
port.listen((dynamic message) {
try {
if (message is Map<String, dynamic>) {
final type = message['type'] as String? ?? '';
if (type == 'init') {
final data = message['coords'] as Float64List?;
if (data != null) {
flat = data;
nPoints = flat!.length ~/ 2;
sendPort.send({'type': 'inited', 'points': nPoints});
} else {
sendPort.send({'type': 'error', 'message': 'init missing coords'});
}
} else if (type == 'match') {
if (flat == null) {
sendPort.send({'type': 'error', 'message': 'not inited'});
return;
}
final int id = message['id'] as int;
final double lat = (message['lat'] as num).toDouble();
final double lng = (message['lng'] as num).toDouble();
final int lastIndex = (message['lastIndex'] as int?) ?? 0;
final int window = (message['window'] as int?) ?? 120;
final result =
_findClosestWindowInternal(flat!, lat, lng, lastIndex, window);
sendPort.send({
'type': 'matchResult',
'id': id,
'index': result['index'],
'lat': result['lat'],
'lng': result['lng'],
'dist': result['dist']
});
} else if (type == 'dispose') {
port.close();
sendPort.send({'type': 'disposed'});
} else {
sendPort.send({'type': 'error', 'message': 'unknown message type'});
}
}
} catch (e, st) {
sendPort.send(
{'type': 'error', 'message': e.toString(), 'stack': st.toString()});
}
});
}
/// Internal helper: projection on segments, windowed search.
/// Returns Map {index, lat, lng, dist}
Map<String, dynamic> _findClosestWindowInternal(
Float64List flat, double lat, double lng, int lastIndex, int window) {
final int n = flat.length ~/ 2;
final int start = max(0, lastIndex - window);
final int end = min(n - 1, lastIndex + window);
double minDist = double.infinity;
int bestIdx = lastIndex;
double bestLat = flat[lastIndex * 2];
double bestLng = flat[lastIndex * 2 + 1];
for (int i = start; i < end; i++) {
final double aLat = flat[i * 2];
final double aLng = flat[i * 2 + 1];
final double bLat = flat[(i + 1) * 2];
final double bLng = flat[(i + 1) * 2 + 1];
final proj = _closestPointOnSegmentLatLng(lat, lng, aLat, aLng, bLat, bLng);
final double d = proj['dist'] as double;
if (d < minDist) {
minDist = d;
bestLat = proj['lat'] as double;
bestLng = proj['lng'] as double;
// choose overall index: i or i+1 depending on t
final double t = proj['t'] as double;
bestIdx = i + (t > 0.5 ? 1 : 0);
}
}
return {'index': bestIdx, 'lat': bestLat, 'lng': bestLng, 'dist': minDist};
}
/// Projection math on geodetic points approximated in degrees (good for short distances).
Map<String, dynamic> _closestPointOnSegmentLatLng(
double px, double py, double ax, double ay, double bx, double by) {
// Here px=lat, py=lng; ax=lat, ay=lng, etc.
final double x0 = px;
final double y0 = py;
final double x1 = ax;
final double y1 = ay;
final double x2 = bx;
final double y2 = by;
final double dx = x2 - x1;
final double dy = y2 - y1;
double t = 0.0;
final double len2 = dx * dx + dy * dy;
if (len2 > 0) {
t = ((x0 - x1) * dx + (y0 - y1) * dy) / len2;
if (t < 0) t = 0;
if (t > 1) t = 1;
}
final double projX = x1 + t * dx;
final double projY = y1 + t * dy;
final double distMeters = _haversineDistanceMeters(x0, y0, projX, projY);
return {'lat': projX, 'lng': projY, 't': t, 'dist': distMeters};
}
/// Haversine distance (meters)
double _haversineDistanceMeters(
double lat1, double lng1, double lat2, double lng2) {
final double R = 6371000.0;
final double dLat = _deg2rad(lat2 - lat1);
final double dLon = _deg2rad(lng2 - lng1);
final double a = sin(dLat / 2) * sin(dLat / 2) +
cos(_deg2rad(lat1)) * cos(_deg2rad(lat2)) * sin(dLon / 2) * sin(dLon / 2);
final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c;
}
double _deg2rad(double deg) => deg * pi / 180.0;

View File

@@ -0,0 +1,357 @@
import 'dart:convert';
import 'package:local_auth/local_auth.dart';
import 'package:siro_driver/constant/style.dart';
import 'package:siro_driver/controller/firebase/firbase_messge.dart';
import 'package:siro_driver/controller/firebase/local_notification.dart';
import 'package:siro_driver/views/widgets/elevated_btn.dart';
import 'package:siro_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/main.dart';
import '../../../views/widgets/mydialoug.dart';
import '../../firebase/notification_service.dart';
class CaptainWalletController extends GetxController {
bool isLoading = false;
final formKeyTransfer = GlobalKey<FormState>();
final formKeyAccount = GlobalKey<FormState>();
Map walletDate = {};
Map walletDateVisa = {};
Map walletDriverPointsDate = {};
final formKey = GlobalKey<FormState>();
String totalAmount = '0';
double kazan = 0;
String totalAmountVisa = '0';
String totalPoints = '0';
final amountFromBudgetController = TextEditingController();
final newDriverPhoneController = TextEditingController();
final phoneWallet = TextEditingController();
final cardBank = TextEditingController();
final bankCode = TextEditingController();
payFromBudget() async {
if (formKey.currentState!.validate()) {
var pointFromBudget = int.parse((amountFromBudgetController.text));
// await getPaymentId('fromBudgetToPoints',
// int.parse((amountFromBudgetController.text)) * -1);
var paymentToken3 =
await generateToken((pointFromBudget * -1).toString());
var paymentID = await getPaymentId(
'fromBudgetToPoints', (pointFromBudget * -1).toString());
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
'amount': (pointFromBudget * -1).toString(),
'rideId': paymentID.toString(),
'payment_method': 'myBudget',
'passengerID': 'myBudgetToPoint',
'token': paymentToken3,
'driverID': box.read(BoxName.driverID).toString(),
});
Future.delayed(const Duration(seconds: 1));
await addDriverWallet(
'fromBudget', pointFromBudget.toString(), pointFromBudget.toString());
update();
Get.back();
await refreshCaptainWallet();
NotificationController().showNotification(
'You have successfully charged your account'.tr,
'$pointFromBudget ${'has been added to your budget'.tr}',
'tone1',
'',
);
}
}
Future refreshCaptainWallet() async {
await getCaptainWalletFromRide();
await getCaptainWalletFromBuyPoints();
// await checkAccountCaptainBank();
}
List amountToNewDriverMap = [];
bool isNewTransfer = false;
Future detectNewDriverFromMyBudget() async {
if (formKeyTransfer.currentState!.validate()) {
if (int.parse(amountFromBudgetController.text) <
double.parse(totalAmountVisa) &&
int.parse(amountFromBudgetController.text) > 10) {
//get new driver details
isNewTransfer = true;
update();
var res = await CRUD().get(
link: AppLink.getDriverDetails,
payload: {'driver_phone': '+2${newDriverPhoneController.text}'});
isNewTransfer = false;
update();
if (res != 'failure') {
var d = jsonDecode(res);
amountToNewDriverMap = d['data'];
// update();
} else {
mySnackeBarError("This driver is not registered".tr);
}
} else {
mySnackeBarError('Your Budget less than needed'.tr);
}
}
}
Future getCaptainWalletFromRide() async {
isLoading = true;
update();
var res = await CRUD().getWallet(
link: AppLink.getAllPaymentFromRide,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
// isLoading = false;
if (res != 'failure') {
walletDate = jsonDecode(res);
totalAmount = walletDate['message'][0]['total_amount'] ?? '0';
update();
var res1 = await CRUD().getWallet(
link: AppLink.getAllPaymentVisa,
payload: {'driverID': box.read(BoxName.driverID).toString()});
walletDateVisa = jsonDecode(res1);
totalAmountVisa = walletDateVisa['message'][0]['diff'].toString();
update();
} else {
totalAmount = "0";
totalAmountVisa = "0";
}
}
Future getCaptainWalletFromBuyPoints() async {
// isLoading = true;
update();
var res = await CRUD().getWallet(
link: AppLink.getDriverPaymentPoints,
payload: {'driverID': box.read(BoxName.driverID).toString()},
);
isLoading = false;
// update();
if (res != 'failure') {
walletDriverPointsDate = jsonDecode(res);
double totalPointsDouble = double.parse(
walletDriverPointsDate['message'][0]['total_amount'].toString());
totalPoints = totalPointsDouble.toStringAsFixed(0);
} else {
totalPoints = '0';
}
update();
}
String paymentToken = '';
Future<String> generateToken(String amount) async {
var res =
await CRUD().postWallet(link: AppLink.addPaymentTokenDriver, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'amount': amount.toString(),
});
var d = (res);
return d['message'];
}
// late String paymentID;
Future<String> getPaymentId(String paymentMethod, amount) async {
// paymentToken = await generateToken(amount);
var res =
await CRUD().postWallet(link: AppLink.addDriverPaymentPoints, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'amount': amount.toString(),
'payment_method': paymentMethod.toString(),
});
var d = (res);
// paymentID = d['message'].toString();
return d['message'];
}
Future addDriverWallet(String paymentMethod, point, count) async {
paymentToken = await generateToken(count);
var paymentID = await getPaymentId(paymentMethod, point.toString());
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'paymentID': paymentID.toString(),
'amount': point,
'token': paymentToken,
'paymentMethod': paymentMethod.toString(),
});
}
Future addDriverPayment(String paymentMethod, point, wayPay) async {
paymentToken = await generateToken(point);
var paymentID = await getPaymentId(paymentMethod, point.toString());
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
'amount': point.toString(),
'rideId': paymentID.toString(),
'payment_method': paymentMethod,
'passengerID': wayPay,
'token': paymentToken,
'driverID': box.read(BoxName.driverID).toString(),
});
}
Future addDriverWalletFromPromo(String paymentMethod, point) async {
var resPromotion =
await CRUD().postWallet(link: AppLink.addpromotionDriver, payload: {
'driver_id': box.read(BoxName.driverID).toString(),
'payment_amount': point,
'timePromo': paymentMethod,
});
if (resPromotion != 'failure') {
paymentToken = await generateToken(point);
var paymentID = await getPaymentId(paymentMethod, point.toString());
var res =
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
'amount': point,
'rideId': paymentID.toString(),
'payment_method': paymentMethod.toString(),
'passengerID': paymentMethod,
'token': paymentToken,
'driverID': box.read(BoxName.driverID).toString(),
});
if (res != 'failure') {
String title = 'wallet_updated'.tr; // Notification title
String message = '${'wallet_credited_message'.tr} $point';
String tone = 'default_tone'.tr; // Notification tone or sound
String payLoad =
'wallet_updated'; // Additional data payload for the notification
Get.find<NotificationController>()
.showNotification(title, message, tone, payLoad);
}
} else {
Get.back();
mySnackeBarError(
"A promotion record for this driver already exists for today.".tr);
}
}
Future addDriverWalletToInvitor(String paymentMethod, driverID, point) async {
paymentToken = await generateToken(point);
var paymentID = await getPaymentId(paymentMethod, point.toString());
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
'driverID': driverID,
'amount': point,
'token': paymentToken,
'rideId': paymentID.toString(),
'payment_method': paymentMethod.toString(),
'passengerID': paymentMethod,
});
await addSeferWallet(paymentMethod,
(double.parse(point) * -2).toString()); // deduct 2 from sefer wallet
}
Future addSeferWallet(String paymentMethod, String point) async {
var seferToken = await generateToken(point.toString());
await CRUD().postWallet(link: AppLink.addSeferWallet, payload: {
'amount': point.toString(),
'paymentMethod': paymentMethod,
'passengerId': 'driver',
'token': seferToken,
'driverId': box.read(BoxName.driverID).toString(),
});
}
Future addTransferDriversWallet(String paymentMethod1, paymentMethod2) async {
var paymentID =
await getPaymentId(paymentMethod1, amountFromBudgetController.text);
paymentToken = await generateToken(
(int.parse(amountFromBudgetController.text) * -1).toString());
await CRUD().postWallet(link: AppLink.addDrivePayment, payload: {
'amount': (int.parse(amountFromBudgetController.text) * -1).toString(),
'rideId': paymentID.toString(),
'payment_method': paymentMethod1,
'passengerID': 'To ${amountToNewDriverMap[0]['id']}',
'token': paymentToken,
'driverID': box.read(BoxName.driverID).toString(),
});
paymentID = await getPaymentId(paymentMethod2,
(int.parse(amountFromBudgetController.text) - 5).toString());
paymentToken = await generateToken(amountFromBudgetController.text);
var res1 =
await CRUD().postWallet(link: AppLink.addDriversWalletPoints, payload: {
'driverID': amountToNewDriverMap[0]['id'].toString(),
'paymentID': paymentID.toString(),
'amount': ((int.parse(amountFromBudgetController.text) - 5))
// kazan) // double.parse(kazan) .08 for egypt
.toStringAsFixed(
0), // this will convert buddget to poitns by kazan .08
'token': paymentToken,
'paymentMethod': paymentMethod2.toString(),
});
if (res1 != 'failure') {
// Get.find<FirebaseMessagesController>().sendNotificationToDriverMAP(
// 'Transfer',
// '${'You have transfer to your wallet from'.tr}'
// '${box.read(BoxName.nameDriver)}',
// amountToNewDriverMap[0]['token'].toString(),
// [],
// 'order1.wav');
NotificationService.sendNotification(
target: amountToNewDriverMap[0]['token'].toString(),
title: 'Transfer'.tr,
body: '${'You have transfer to your wallet from'.tr}'
'${box.read(BoxName.nameDriver)}',
isTopic: false, // Important: this is a token
tone: 'ding',
driverList: [], category: 'Transfer',
);
await addSeferWallet('payout fee', '5');
Get.defaultDialog(
title: 'transfer Successful'.tr,
middleText: '',
titleStyle: AppStyle.title,
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () async {
Get.back();
Get.back();
await refreshCaptainWallet();
}));
}
}
getKazanPercent() async {
var res = await CRUD().get(
link: AppLink.getKazanPercent,
payload: {'country': box.read(BoxName.countryCode).toString()},
);
if (res != 'failure') {
var json = jsonDecode(res);
kazan = double.parse(json['message'][0]['kazan'].toString());
// naturePrice = double.parse(json['message'][0]['naturePrice']);
// heavyPrice = double.parse(json['message'][0]['heavyPrice']);
// latePrice = double.parse(json['message'][0]['latePrice']);
// comfortPrice = double.parse(json['message'][0]['comfortPrice']);
// speedPrice = double.parse(json['message'][0]['speedPrice']);
// deliveryPrice = double.parse(json['message'][0]['deliveryPrice']);
// mashwariPrice = double.parse(json['message'][0]['freePrice']);
// fuelPrice = double.parse(json['message'][0]['fuelPrice']);
}
update();
}
@override
void onInit() async {
// getKazanPercent();
await refreshCaptainWallet();
super.onInit();
}
}

View File

@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../functions/digit_obsecur_formate.dart';
import '../../functions/secure_storage.dart';
class CreditCardController extends GetxController {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController cardNumberController = TextEditingController();
final TextEditingController cardHolderNameController =
TextEditingController();
final TextEditingController expiryDateController = TextEditingController();
final TextEditingController cvvCodeController = TextEditingController();
openPayment() async {
String? cardNumber = await SecureStorage().readData(BoxName.cardNumber);
String? cardHolderName =
await SecureStorage().readData(BoxName.cardHolderName);
String? expiryDate = await SecureStorage().readData(BoxName.expiryDate);
String? cvvCode = await SecureStorage().readData(BoxName.cvvCode);
if (cvvCode != null && cvvCode.isNotEmpty) {
final maskedCardNumber = DigitObscuringFormatter()
.formatEditUpdate(
TextEditingValue.empty,
TextEditingValue(text: cardNumber ?? ''),
)
.text;
cardNumberController.text = maskedCardNumber;
cardHolderNameController.text = cardHolderName ?? '';
expiryDateController.text = expiryDate ?? '';
cvvCodeController.text = cvvCode;
}
}
@override
void onInit() async {
super.onInit();
openPayment();
// String? cardNumber = await SecureStorage().readData(BoxName.cardNumber);
// String? cardHolderName =
// await SecureStorage().readData(BoxName.cardHolderName);
// String? expiryDate = await SecureStorage().readData(BoxName.expiryDate);
// String? cvvCode = await SecureStorage().readData(BoxName.cvvCode);
// if (cvvCode != null && cvvCode.isNotEmpty) {
// final maskedCardNumber = DigitObscuringFormatter()
// .formatEditUpdate(
// TextEditingValue.empty,
// TextEditingValue(text: cardNumber ?? ''),
// )
// .text;
// cardNumberController.text = maskedCardNumber;
// cardHolderNameController.text = cardHolderName ?? '';
// expiryDateController.text = expiryDate ?? '';
// cvvCodeController.text = cvvCode;
// }
}
}
class CreditCardModel {
String cardNumber;
String cardHolderName;
String expiryDate;
String cvvCode;
CreditCardModel({
required this.cardNumber,
required this.cardHolderName,
required this.expiryDate,
required this.cvvCode,
});
}

Some files were not shown because too many files have changed in this diff Show More