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
@@ -0,0 +1,9 @@
import 'package:get/get.dart';
import '../controller/admin/dashboard_controller.dart';
class InitialBinding extends Bindings {
@override
void dependencies() {
Get.put(DashboardController(), permanent: true);
}
}
+72
View File
@@ -0,0 +1,72 @@
import 'package:secure_string_operations/secure_string_operations.dart';
import '../env/env.dart';
import 'char_map.dart';
class AK {
static final String allowed = Env.allowed;
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 secretKey = X.r(X.r(X.r(Env.secretKey, cn), cC), cs);
static final String basicAuthCredentials =
X.r(X.r(X.r(Env.basicAuthCredentials, 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 = X.r(X.r(X.r(Env.mapAPIKEY, cn), cC), cs);
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 payMobOutClientSecrret =
X.r(X.r(X.r(Env.payMobOutClientSecrret, cn), cC), cs);
static final String payMobOutClient_id =
X.r(X.r(X.r(Env.payMobOutClient_id, cn), cC), cs);
static final String payMobOutPassword =
X.r(X.r(X.r(Env.payMobOutPassword, cn), cC), cs);
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);
}
+84
View File
@@ -0,0 +1,84 @@
class BoxName {
static const String driverID = "driverID";
static const String countryCode = "countryCode";
static const String googlaMapApp = "googlaMapApp";
static const String lang = "lang";
static const String carType = "carType";
static const String carPlate = "carPlate";
static const String statusDriverLocation = "statusDriverLocation";
static const String password = "password";
static const String keyOfApp = 'keyOfApp';
static const String initializationVector = 'initializationVector';
static const String firstTimeLoadKey = 'firstTimeLoadKey';
static const String jwt = "jwt";
static const String phoneVerified = "phoneVerified";
static const String adminPhone = "adminPhone";
static const String hmac = "hmac";
static const String fingerPrint = "fingerPrint";
static const String payMobApikey = "payMobApikey";
static const String refreshToken = "refreshToken";
static const String arrivalTime = "arrivalTime";
static const String passwordDriver = "passwordDriver";
static const String agreeTerms = "agreeTerms";
static const String serverChosen = "serverChosen";
static const String addWork = 'addWork';
static const String addHome = 'addHome';
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 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 tokensDrivers = "tokensDrivers";
static const String tokensPassengers = "tokensPassengers";
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 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';
}
+68
View File
@@ -0,0 +1,68 @@
Map<String, String> cn = {
"0": "3",
"1": "7",
"2": "1",
"3": "9",
"4": "0",
"5": "5",
"6": "2",
"7": "6",
"8": "4",
"9": "8"
};
Map<String, String> cs = {
"a": "q",
"b": "x",
"c": "f",
"d": "y",
"e": "j",
"f": "u",
"g": "k",
"h": "w",
"i": "o",
"j": "e",
"k": "g",
"l": "r",
"m": "n",
"n": "b",
"o": "i",
"p": "v",
"q": "a",
"r": "l",
"s": "z",
"t": "c",
"u": "h",
"v": "p",
"w": "t",
"x": "d",
"y": "s",
"z": "m"
};
Map<String, String> cC = {
"A": "Q",
"B": "X",
"C": "F",
"D": "Y",
"E": "J",
"F": "U",
"G": "K",
"H": "W",
"I": "O",
"J": "E",
"K": "G",
"L": "R",
"M": "N",
"N": "B",
"O": "I",
"P": "V",
"Q": "A",
"R": "L",
"S": "Z",
"T": "C",
"U": "H",
"V": "P",
"W": "T",
"X": "D",
"Y": "S",
"Z": "M"
};
+44
View File
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
class AppColor {
// --- Core Design Tokens ---
// Background & Surfaces
static const Color bg = Color(0xFF0A0A0B);
static const Color surface = Color(0xFF161618);
static const Color surfaceElevated = Color(0xFF222225);
static const Color surfaceGlass = Color(0xCC161618);
// Accents & Branding
static const Color accent = Color(0xFF6366F1); // Indigo / Violet
static const Color accentSoft = Color(0x266366F1); // 15% Opacity
static const Color accentBorder = Color(0x4D6366F1); // 30% Opacity
static const Color glow = Color(0xFF818CF8);
// Semantic / State Colors
static const Color danger = Color(0xFFEF4444);
static const Color dangerSoft = Color(0x26EF4444);
static const Color success = Color(0xFF10B981);
static const Color successSoft = Color(0x2610B981);
static const Color warning = Color(0xFFF59E0B);
static const Color info = Color(0xFF3B82F6);
// Text & Content
static const Color textPrimary = Color(0xFFF3F4F6);
static const Color textSecondary = Color(0xFF9CA3AF);
static const Color textMuted = Color(0xFF6B7280);
// UI Elements
static const Color divider = Color(0xFF2D2D30);
static const Color cardShadow = Color(0x66000000);
// --- Legacy Mappings (for temporary compatibility) ---
static const Color primaryColor = bg;
static const Color secondaryColor = textPrimary;
static const Color accentColor = accent;
static const Color redColor = danger;
static const Color greenColor = success;
static const Color blueColor = info;
static const Color yellowColor = warning;
static const Color deepPurpleAccent = accent; // Map to accent
}
+144
View File
@@ -0,0 +1,144 @@
import 'dart:convert';
import 'package:crypto/crypto.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 = res is String ? jsonDecode(res) : 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 = r(r(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;
}
String c(String a, Map<String, String> b) {
StringBuffer c = StringBuffer();
c.write(a);
String d = "Bl";
c.write(b[d] ?? d);
StringBuffer e = StringBuffer();
String f = c.toString();
for (int g = 0; g < f.length; g++) {
String h = f[g];
e.write(b[h] ?? h);
}
return e.toString();
}
String r(String a, Map<String, String> b) {
StringBuffer c = StringBuffer();
String d = "Bl";
int e = d.length;
for (int f = 0; f < a.length; f++) {
String g = a[f];
String h = b.keys.firstWhere(
(i) => b[i] == g,
orElse: () => g,
);
c.write(h);
}
String j = c.toString();
if (j.endsWith(d)) {
j = j.substring(0, j.length - e);
}
return j;
}
}
File diff suppressed because one or more lines are too long
+363
View File
@@ -0,0 +1,363 @@
import '../env/env.dart';
class AppLink {
static String seferPaymentServer =
'https://walletintaleq.intaleq.xyz/v1/main';
static final String tripzPaymentServer0 = seferPaymentServer;
static final String tripzPaymentServer = '$seferPaymentServer/ride';
static final String tripzAlexandriaServer = Env.seferAlexandriaServer;
static final String tripzCairoServer = Env.seferCairoServer;
static final String tripzGizaServer = Env.seferGizaServer;
// static final String endPoint = box.read(BoxName.serverChosen);
// static final String server = Env.seferCairoServer;
static final String server = 'https://api.intaleq.xyz/intaleq_v3';
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v3';
static final String syria = 'https://syria.intaleq.xyz/intaleq';
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 String loginJwtDriver =
"https://api.intaleq.xyz/intaleq/loginAdmin.php";
//=============================
//=============================
static final getAllFingerprints =
'$server/migration/get_all_fingerprints.php';
static final updateFingerprintAdmin =
'$server/migration/update_fingerprint_admin.php';
static final getAllDriverFingerprints =
'$server/migration/get_all_driver_fingerprints.php';
static final updateDriverFingerprintAdmin =
'$server/migration/update_driver_fingerprint_admin.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 serverMonitor = "https://tripz-egypt.com/server/monitor.php";
static String test = "$server/test.php";
static String loginWalletAdmin = "$seferPaymentServer/loginWalletAdmin.php";
static String loginWalletAdminV3 = "$server/Admin/auth/loginWallet.php";
//===============firebase==========================
static String getTokens = "$server/ride/firebase/get.php";
static String getInvoices = "$server/Admin/adminUser/invoice_total.php";
static String addInvoice = "$server/Admin/adminUser/add_invoice.php";
static String getAllTokenDrivers =
"$server/ride/firebase/getALlTokenDrivers.php";
static String getAllTokenPassengers =
"$server/ride/firebase/getAllTokenPassengers.php";
static String addTokens = "$server/ride/firebase/add.php";
static String addTokensDriver = "$server/ride/firebase/addDriver.php";
//=======================Wallet===================
static String addPaymentTokenDriver =
"$tripzPaymentServer/driverWallet/addPaymentToken.php";
static String addSeferWallet = "$tripzPaymentServer/seferWallet/add.php";
static String getPaymentsDashboard =
"$seferPaymentServer/Admin/getPaymentsDashboard.php";
static String getSeferWallet = "$tripzPaymentServer/seferWallet/get.php";
static String addDrivePayment = "$tripzPaymentServer/payment/add.php";
static String addFromAdmin =
"$tripzPaymentServer/driverWallet/addFromAdmin.php";
static String add300ToDriver =
"$tripzPaymentServer/driverWallet/add300ToDriver.php";
static String updatePaymetToPaid =
"$tripzPaymentServer/payment/updatePaymetToPaid.php";
static String wallet = '$tripzPaymentServer/passengerWallet';
static String walletDriver = '$tripzPaymentServer/driverWallet';
static String getAllPassengerTransaction =
"$wallet/getAllPassengerTransaction.php";
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 getDriversWallet = "$walletDriver/get.php";
static String addDriversWalletPoints = "$walletDriver/add.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 ride = '$server/ride';
static String addCancelRideFromPassenger = "$server/ride/cancelRide/add.php";
static String cancelRide = "$server/ride/cancelRide/get.php";
//-----------------ridessss------------------
static String addRides = "$ride/rides/add.php";
static String getRides = "$ride/rides/get.php";
static String getRideOrderID = "$ride/rides/getRideOrderID.php";
static String getRideStatus = "$ride/rides/getRideStatus.php";
static String getRideStatusBegin = "$ride/rides/getRideStatusBegin.php";
static String getRideStatusFromStartApp =
"$ride/rides/getRideStatusFromStartApp.php";
static String updateRides = "$ride/rides/update.php";
static String updateStausFromSpeed = "$ride/rides/updateStausFromSpeed.php";
static String deleteRides = "$ride/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";
static String updateKazanPercent = "$ride/kazan/update.php";
////-----------------DriverPayment------------------
static String addDriverpayment = "$tripzPaymentServer/payment/add.php";
static String addDriverPaymentPoints =
"$tripzPaymentServer/driverPayment/add.php";
static String getDriverPaymentPoints =
"$tripzPaymentServer/driverWallet/get.php";
static String getDriverpaymentToday = "$tripzPaymentServer/payment/get.php";
static String getCountRide = "$tripzPaymentServer/payment/getCountRide.php";
static String getAllPaymentFromRide =
"$tripzPaymentServer/payment/getAllPayment.php";
static String getAllPaymentVisa =
"$tripzPaymentServer/payment/getAllPaymentVisa.php";
//-----------------Passenger NotificationCaptain------------------
static String addNotificationPassenger =
"$ride/notificationPassenger/add.php";
static String send_whatsapp_message =
"$server/Admin/send_whatsapp_message.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 getRideWaiting = "$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 updateApiKey = "$ride/apiKey/update.php";
static String deleteApiKey = "$ride/apiKey/delete.php";
//-----------------Feed Back------------------
static String addFeedBack = "$ride/feedBack/add.php";
static String getFeedBack = "$ride/feedBack/get.php";
static String updateFeedBack = "$ride/feedBack/updateFeedBack.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 = "$ride/RegisrationCar/get.php";
static String updateRegisrationCar = "$ride/RegisrationCar/update.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 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 uploadImageType = "$server/uploadImageType.php";
//=============egypt documents ==============
static String uploadEgyptidFront =
"$server/EgyptDocuments/uploadEgyptidFront.php";
static String uploadEgypt = "$server/uploadEgypt.php";
//==================certifcate==========
static String location = locationServer;
static String getCarsLocationByPassenger = "$location/get.php";
static String getFemalDriverLocationByPassenger =
"$location/getFemalDriver.php";
static String getDriverCarsLocationToPassengerAfterApplied =
"$location/getDriverCarsLocationToPassengerAfterApplied.php";
static String addCarsLocationByPassenger = "$location/add.php";
static String deleteCarsLocationByPassenger = "$location/delete.php";
static String updateCarsLocationByPassenger = "$location/update.php";
static String getTotalDriverDuration = "$location/getTotalDriverDuration.php";
static String getTotalDriverDurationToday =
"$location/getTotalDriverDurationToday.php";
//==================Blog=============
static String profile = '$server/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";
//===================Auth============
static String auth = '$server/auth';
static String login = "$auth/login.php";
static String signUp = "$auth/signup.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 signUpCaptin = "$authCaptin/register.php";
static String sendVerifyEmailCaptin = "$authCaptin/sendVerifyEmail.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";
//===================Admin Captin============
static String getDriversPhonesAndTokens =
"$server/Admin/AdminCaptain/getDriversPhonesAndTokens.php";
static String getPassengerDetailsByPassengerID =
"$server/Admin/getPassengerDetailsByPassengerID.php";
static String getPassengerDetails = "$server/Admin/getPassengerDetails.php";
static String admin_delete_and_blacklist_passenger =
"$server/Admin/passenger/admin_delete_and_blacklist_passenger.php";
static String admin_update_passenger =
"$server/Admin/passenger/admin_update_passenger.php";
static String admin_unblacklist =
"$server/Admin/passenger/admin_unblacklist.php";
static String admin_get_rides_by_phone =
"$server/Admin/rides/admin_get_rides_by_phone.php";
static String admin_update_ride_status =
"$server/Admin/rides/admin_update_ride_status.php";
static String getPassengerbyEmail = "$server/Admin/getPassengerbyEmail.php";
static String updateDriverFromAdmin =
"$server/Admin/driver/updateDriverFromAdmin.php";
static String find_driver_by_phone =
"$server/Admin/driver/find_driver_by_phone.php";
static String getDriversPending =
"$server/auth/syria/driver/drivers_pending_list.php";
static String getDriverDetails =
"$server/auth/syria/driver/driver_details.php";
static String deleteCaptain = "$server/Admin/driver/deleteCaptain.php";
static String addAdminUser = "$server/Admin/adminUser/add.php";
static String addStaff = "$server/Admin/Staff/add.php";
static String getdashbord = "$server/Admin/dashbord.php";
static String paymentServerV2 = 'https://walletintaleq.intaleq.xyz/v2/main';
static String realtimeDashboardV2 = "$server/Admin/v2/realtime_dashboard.php";
static String smartAlertsV2 = "$server/Admin/v2/smart_alerts.php";
static String growthV2 = "$server/Admin/v2/analytics/growth.php";
static String revenueV2 = "$server/Admin/v2/analytics/revenue.php";
static String driverRankingV2 = "$server/Admin/v2/analytics/driver_ranking.php";
static String settlementsV2 = "$paymentServerV2/Admin/v2/financial/settlements.php";
static String financialStatsV2 = "$paymentServerV2/Admin/v2/financial/stats.php";
static String dashboardWalletV2 = "$paymentServerV2/Admin/v2/financial/dashboard_wallet.php";
static String auditLogsV2 = "$server/Admin/v2/security/audit_logs.php";
static String blacklistManager = "$server/Admin/v2/quality/blacklist_manager.php";
static String driverScorecard = "$server/Admin/v2/quality/driver_scorecard.php";
static String getEmployee = "$server/Admin/employee/get.php";
static String getBestDriver = "$server/Admin/driver/getBestDriver.php";
static String getBestDriverGiza =
"https://gizasefer.online/sefer/Admin/driver/getBestDriver.php";
static String getBestDriverAlexandria =
"$tripzAlexandriaServer/Admin/driver/getBestDriver.php";
static String deleteRecord = "$server/Admin/driver/deleteRecord.php";
static String addEmployee = "$server/Admin/employee/add.php";
static String getdashbordPayment = "$server/Admin/dashbordPayment.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 getVisaForEachDriver =
"$seferPaymentServer/Admin/getVisaForEachDriver.php";
static String getRidesPerMonth =
"$server/Admin/AdminRide/getRidesPerMonth.php";
static String getRidesDetails = "$server/Admin/AdminRide/get.php";
static String getPassengersStatic = "$serviceApp/getPassengersStatic.php";
static String getRidesStatic = "$serviceApp/getRidesStatic.php";
static String getEmployeeStatic = "$serviceApp/getEmployeeStatic.php";
static String getNotesForEmployee = "$serviceApp/getNotesForEmployee.php";
static String getEmployeeDriverAfterCallingRegister =
"$serviceApp/getEmployeeDriverAfterCallingRegister.php";
static String getEditorStatsCalls = "$serviceApp/getEditorStatsCalls.php";
static String getdriverstotalMonthly =
"$serviceApp/getdriverstotalMonthly.php";
////////////////////////
static String serviceApp = "$server/serviceapp";
static String editCarPlate = "$serviceApp/editCarPlate.php";
static String getComplaintAllData = "$serviceApp/getComplaintAllData.php";
static String getComplaintAllDataForDriver =
"$serviceApp/getComplaintAllDataForDriver.php";
static String addCriminalDocuments = "$authCaptin/addCriminalDocuments.php";
static String getPassengersByPhone =
"$server/serviceApp/getPassengersByPhone.php";
static String getDriverByPhone = "$serviceApp/getDriverByPhone.php";
static String getNewDriverRegister = "$serviceApp/getNewDriverRegister.php";
static String addWelcomeDriverNote = "$serviceApp/addWelcomeDriverNote.php";
static String getDriverNotCompleteRegistration =
"$serviceApp/getDriverNotCompleteRegistration.php";
static String getPassengersNotCompleteRegistration =
"$serviceApp/getPassengersNotCompleteRegistration.php";
static String addNotesDriver = "$serviceApp/addNotesDriver.php";
static String getCarPlateNotEdit = "$serviceApp/getCarPlateNotEdit.php";
static String addNotesPassenger = "$serviceApp/addNotesPassenger.php";
static String getPackages = "$serviceApp/getPackages.php";
static String updatePackages = "$serviceApp/updatePackages.php";
//////
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 getSender = "$server/auth/sms/getSender.php";
static String checkStatus = "https://sms.kazumi.me/api/sms/check-status";
static String updatePhoneInvalidSMSPassenger =
"$server/auth/sms/updatePhoneInvalidSMSPassenger.php";
}
+83
View File
@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'colors.dart';
class AppStyle {
// --- Typography ---
static TextStyle display = GoogleFonts.inter(
fontWeight: FontWeight.w800,
fontSize: 32,
color: AppColor.textPrimary,
letterSpacing: -1,
);
static TextStyle headTitle = GoogleFonts.cairo(
fontWeight: FontWeight.bold,
fontSize: 24,
color: AppColor.textPrimary,
);
static TextStyle title = GoogleFonts.inter(
fontWeight: FontWeight.w600,
fontSize: 16,
color: AppColor.textPrimary,
);
static TextStyle subtitle = GoogleFonts.inter(
fontWeight: FontWeight.w500,
fontSize: 14,
color: AppColor.textSecondary,
);
static TextStyle body = GoogleFonts.inter(
fontWeight: FontWeight.normal,
fontSize: 14,
color: AppColor.textPrimary,
);
static TextStyle caption = GoogleFonts.inter(
fontWeight: FontWeight.w400,
fontSize: 12,
color: AppColor.textMuted,
);
static TextStyle number = GoogleFonts.jetBrainsMono(
fontWeight: FontWeight.bold,
fontSize: 15,
color: AppColor.accent,
);
// --- Decorations ---
static BoxDecoration cardDecoration = BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.divider, width: 1),
boxShadow: const [
BoxShadow(
color: AppColor.cardShadow,
blurRadius: 20,
offset: Offset(0, 8),
),
],
);
static BoxDecoration elevatedCard = BoxDecoration(
color: AppColor.surfaceElevated,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColor.accentBorder, width: 1),
);
static BoxDecoration glassDecoration = BoxDecoration(
color: AppColor.surfaceGlass,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColor.divider, width: 1),
);
// --- Legacy Mappings ---
static TextStyle headTitle2 = headTitle;
static BoxDecoration boxDecoration = cardDecoration;
static BoxDecoration boxDecoration1 = elevatedCard;
}
+9
View File
@@ -0,0 +1,9 @@
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 captainNotification = "captainNotification";
}
@@ -0,0 +1,75 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class AnalyticsV2Controller extends GetxController {
bool isLoading = true;
Map<String, dynamic> growthData = {};
Map<String, dynamic> revenueData = {};
List<dynamic> topDrivers = [];
@override
void onInit() {
super.onInit();
fetchAllAnalytics();
}
Future<void> fetchAllAnalytics() async {
isLoading = true;
update();
await Future.wait([
fetchGrowth(),
fetchRevenue(),
fetchDriverRanking(),
]);
isLoading = false;
update();
}
Future<void> fetchGrowth() async {
try {
var res = await CRUD().get(link: AppLink.growthV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
growthData = d['data'];
}
}
} catch (e) {
Log.print('Error fetching growth analytics: $e');
}
}
Future<void> fetchRevenue() async {
try {
var res = await CRUD().get(link: AppLink.revenueV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
revenueData = d['data'];
}
}
} catch (e) {
Log.print('Error fetching revenue analytics: $e');
}
}
Future<void> fetchDriverRanking() async {
try {
var res = await CRUD().get(link: AppLink.driverRankingV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
topDrivers = d['data'];
}
}
} catch (e) {
Log.print('Error fetching driver ranking: $e');
}
}
}
@@ -0,0 +1,141 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class CaptainAdminController extends GetxController {
bool isLoading = false;
Map captainData = {};
Map captain = {};
final captainController = TextEditingController();
final captainPrizeController = TextEditingController();
final titleNotify = TextEditingController();
final bodyNotify = TextEditingController();
final formCaptainKey = GlobalKey<FormState>();
final formCaptainPrizeKey = GlobalKey<FormState>();
Future getCaptainCount() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getCaptainDetails, payload: {});
var d = jsonDecode(res);
if (d['status'] == 'success') {
captainData = d;
}
isLoading = false;
update();
}
Future deletCaptain() async {
isLoading = true;
update();
var res = await CRUD().get(
link: AppLink.deleteCaptain,
payload: {},
);
var d = jsonDecode(res);
if (d['status'] == 'success') {
captainData = d;
}
isLoading = false;
update();
}
Future find_driver_by_phone(String phone) async {
isLoading = true;
update();
var res = await CRUD().post(
link: AppLink.find_driver_by_phone,
payload: {'phone': "963$phone"},
);
var d = (res);
if (d != 'failure') {
captainData = d;
} else {
captainData = {};
Get.snackbar('Error', 'No captain found with this phone number',
backgroundColor: AppColor.redColor);
}
isLoading = false;
update();
}
Future addCaptainPrizeToWallet() async {
String? paymentId;
//todo link to add wallet to captain
for (var i = 0; i < captainData['message'].length; i++) {
await CRUD().post(link: AppLink.addDriverPaymentPoints, payload: {
'driverID': captainData['message'][i]['id'],
'amount': captainPrizeController.text,
'paymentMethod': 'Prize',
}).then((value) {
paymentId = value['message'].toString();
});
await CRUD().post(link: AppLink.addPassengersWallet, payload: {
'driverID': captainData['message'][i]['id'],
'amount': captainPrizeController.text,
'paymentMethod': 'Prize',
'paymentID': paymentId.toString(),
});
}
Get.back();
}
void addCaptainsPrizeToWalletSecure() async {
try {
// Check if local authentication is available
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
);
if (didAuthenticate) {
// User authenticated successfully, proceed with payment
await addCaptainPrizeToWallet();
Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor);
} else {
// Authentication failed, handle accordingly
Get.snackbar('Authentication failed', '',
backgroundColor: AppColor.redColor);
// 'Authentication failed');
}
} else {
// Local authentication not available, proceed with payment without authentication
await addCaptainPrizeToWallet();
Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor);
}
} catch (e) {
rethrow;
}
}
Future getCaptains() async {
var res = await CRUD()
.get(link: AppLink.getCaptainDetailsByEmailOrIDOrPhone, payload: {
'driver_id': captainController.text,
'driverEmail': captainController.text,
'driverPhone': captainController.text,
});
var d = jsonDecode(res);
if (d['status'] == 'success') {
captain = d;
}
update();
}
@override
void onInit() {
getCaptainCount();
super.onInit();
}
}
@@ -0,0 +1,70 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class ComplaintController extends GetxController {
var complaintList = [].obs;
var isLoading = false.obs;
var showOnlyDelayed = false.obs;
final CRUD _crud = CRUD();
List<dynamic> get delayedComplaints {
final weekAgo = DateTime.now().subtract(const Duration(days: 7));
return complaintList.where((c) {
if (c['statusComplaint'] == 'Resolved') return false;
try {
final date = DateTime.parse(c['date_filed']);
return date.isBefore(weekAgo);
} catch (e) {
return false;
}
}).toList();
}
@override
void onInit() {
super.onInit();
getComplaints();
}
Future<void> getComplaints() async {
isLoading.value = true;
try {
var response = await _crud.get(link: AppLink.getComplaintAllData);
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
complaintList.assignAll(decoded['message']);
}
} else {
complaintList.clear();
}
} catch (e) {
Get.snackbar("خطأ", "فشل جلب الشكاوى: $e");
} finally {
isLoading.value = false;
}
}
Future<bool> updateComplaintStatus(String id, String status, String resolution) async {
isLoading.value = true;
try {
var response = await _crud.post(link: "${AppLink.server}/serviceapp/update_complaint.php", payload: {
"id": id,
"statusComplaint": status,
"resolution": resolution,
});
if (response != null && response is Map && response['status'] == "success") {
await getComplaints();
return true;
}
return false;
} catch (e) {
Get.snackbar("خطأ", "فشل تحديث الشكوى: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,111 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/controller/auth/otp_helper.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
class DashboardController extends GetxController {
bool isLoading = false;
List dashbord = [];
String creditSMS = '0';
final formKey = GlobalKey<FormState>();
final smsText = TextEditingController();
Future getDashBoard() async {
isLoading = true;
update();
// 🔹 Request main dashboard data
var res = await CRUD().get(link: AppLink.getdashbord, payload: {});
print('📡 Main dashboard response: $res');
if (res == 'token_expired') {
print('❌ Admin token expired. Attempting seamless auto-login.');
box.remove(BoxName.jwt);
try {
final otpHelper = Get.put(OtpHelper());
await otpHelper.checkAdminLogin();
} catch (e) {
Get.offAllNamed('/login');
}
return;
}
if (res != 'failure' && res != null) {
try {
var d = res is String ? jsonDecode(res) : res;
print('✅ Decoded main dashboard: ${jsonEncode(d)}');
if (d['status'] == 'success' && d['message'] != null) {
dashbord = d['message'] is List ? d['message'] : [d['message']];
}
} catch (e) {
print('❌ Error parsing main dashboard: $e');
}
} else {
print('❌ Failed to load main dashboard');
}
// 🔹 Request wallet dashboard data
var resPayments = await CRUD().postWallet(
link: AppLink.getPaymentsDashboard,
payload: {},
);
print('💳 Wallet dashboard response: $resPayments');
if (resPayments is Map && resPayments['status'] == 'success') {
var p = resPayments;
print('✅ Decoded wallet dashboard: ${jsonEncode(p)}');
if (dashbord.isNotEmpty &&
p['message'] is List &&
p['message'].isNotEmpty) {
dashbord[0].addAll(p['message'][0]);
} else if (dashbord.isNotEmpty && p['message'] is Map) {
dashbord[0].addAll(p['message']);
}
} else {
print('❌ Failed to load wallet dashboard (or verification required)');
}
// 🔹 Check SMS credit
// var res2 = await CRUD().kazumiSMS(
// link: 'https://sms.kazumi.me/api/sms/check-credit',
// payload: {"username": "Sefer", "password": AK.smsPasswordEgypt},
// );
// creditSMS = res2['credit'];
// print('📱 SMS Credit Response: ${jsonEncode(res2)}');
// print('💰 creditSMS: $creditSMS');
isLoading = false;
update();
}
sendSMSMethod() async {
if (formKey.currentState!.validate()) {
for (var phoneNumber in box.read(BoxName.tokensDrivers)['message']) {
// for (var i = 0; i < 2; i++) {
await CRUD().sendSmsEgypt(
phoneNumber['phone'].toString(),
// box.read(BoxName.tokensDrivers)['message'][i]['phone'].toString(),
smsText.text,
);
// print('CRUD().phoneDriversTest.: ${phoneNumber['phone']}');
Future.delayed(const Duration(microseconds: 20));
}
Get.back();
}
}
@override
void onInit() async {
getDashBoard();
super.onInit();
}
}
@@ -0,0 +1,62 @@
import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class DashboardV2Controller extends GetxController {
bool isLoading = true;
Map<String, dynamic> realtimeData = {};
List<dynamic> smartAlerts = [];
Timer? _timer;
@override
void onInit() {
super.onInit();
fetchRealtimeData();
fetchSmartAlerts();
// Auto refresh every 2 minutes
_timer = Timer.periodic(const Duration(minutes: 2), (timer) {
fetchRealtimeData();
fetchSmartAlerts();
});
}
@override
void onClose() {
_timer?.cancel();
super.onClose();
}
Future<void> fetchRealtimeData() async {
try {
var res = await CRUD().get(link: AppLink.realtimeDashboardV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
realtimeData = d['message'];
isLoading = false;
update();
}
}
} catch (e) {
Log.print('Error fetching realtime dashboard: $e');
}
}
Future<void> fetchSmartAlerts() async {
try {
var res = await CRUD().get(link: AppLink.smartAlertsV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
smartAlerts = d['message'];
update();
}
}
} catch (e) {
Log.print('Error fetching smart alerts: $e');
}
}
}
@@ -0,0 +1,97 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class DriverDocsController extends GetxController {
var pendingDrivers = [].obs;
var isLoading = false.obs;
var isMoreLoading = false.obs;
var hasMore = true.obs;
int _offset = 0;
final int _limit = 10;
final CRUD _crud = CRUD();
@override
void onInit() {
super.onInit();
getPendingDrivers();
}
Future<void> getPendingDrivers({bool refresh = true}) async {
if (refresh) {
isLoading.value = true;
_offset = 0;
hasMore.value = true;
} else {
if (isMoreLoading.value || !hasMore.value) return;
isMoreLoading.value = true;
}
try {
var response = await _crud.post(
link: AppLink.getDriversPending,
payload: {"limit": _limit.toString(), "offset": _offset.toString()},
);
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
List newItems = decoded['message'] ?? [];
if (refresh) {
pendingDrivers.assignAll(newItems);
} else {
pendingDrivers.addAll(newItems);
}
_offset += newItems.length;
if (newItems.length < _limit) {
hasMore.value = false;
}
}
}
} catch (e) {
Get.snackbar("خطأ", "فشل جلب السائقين: $e");
} finally {
isLoading.value = false;
isMoreLoading.value = false;
}
}
Future<void> loadMore() async {
await getPendingDrivers(refresh: false);
}
Future<Map<String, dynamic>?> getDriverFullDetails(String id) async {
try {
var response = await _crud.get(link: "${AppLink.getDriverDetails}?id=$id");
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
return decoded['data'];
}
}
} catch (e) {
Get.snackbar("خطأ", "فشل جلب تفاصيل السائق: $e");
}
return null;
}
Future<bool> approveDriver(String id) async {
isLoading.value = true;
try {
var response = await _crud.post(link: AppLink.updateDriverFromAdmin, payload: {
"id": id,
"status": "active",
});
if (response != null && response is Map && response['status'] == "success") {
await getPendingDrivers();
return true;
}
return false;
} catch (e) {
Get.snackbar("خطأ", "فشل اعتماد السائق: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,61 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class FinancialV2Controller extends GetxController {
bool isLoading = true;
Map<String, dynamic> stats = {};
List<dynamic> settlements = [];
@override
void onInit() {
super.onInit();
fetchAllFinancials();
}
Future<void> fetchAllFinancials() async {
isLoading = true;
update();
await Future.wait([
fetchStats(),
fetchSettlements(),
]);
isLoading = false;
update();
}
Future<void> fetchStats() async {
try {
var res =
await CRUD().getWallet(link: AppLink.financialStatsV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
stats = d['data'];
}
}
} catch (e) {
Log.print('Error fetching financial stats: $e');
}
}
Future<void> fetchSettlements() async {
try {
var res =
await CRUD().getWallet(link: AppLink.settlementsV2, payload: {});
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success') {
settlements = d['data'];
}
}
} catch (e) {
Log.print('Error fetching settlements: $e');
}
}
}
@@ -0,0 +1,25 @@
class InvoiceModel {
final String invoiceNumber;
final String amount;
final String date;
final String name;
final String? imageLink;
InvoiceModel({
required this.invoiceNumber,
required this.amount,
required this.date,
required this.name,
this.imageLink,
});
factory InvoiceModel.fromJson(Map<String, dynamic> json) {
return InvoiceModel(
invoiceNumber: json['invoice_number'],
amount: json['amount'].toString(),
date: json['date'],
name: json['name'],
imageLink: json['image_link'],
);
}
}
@@ -0,0 +1,60 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class KazanController extends GetxController {
var kazanData = {}.obs;
var isLoading = false.obs;
final CRUD _crud = CRUD();
@override
void onInit() {
super.onInit();
getKazan();
}
Future<void> getKazan() async {
isLoading.value = true;
try {
var response = await _crud.get(link: "${AppLink.getKazanPercent}?country=syria");
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
var message = decoded['message'];
if (message is List && message.isNotEmpty) {
kazanData.value = message[0];
}
}
}
} catch (e) {
Get.snackbar("خطأ", "فشل جلب بيانات التسعير: $e");
} finally {
isLoading.value = false;
}
}
Future<bool> updateKazan(Map<String, dynamic> data) async {
isLoading.value = true;
try {
final String link = data.containsKey('id') ? AppLink.updateKazanPercent : AppLink.addKazanPercent;
Map<String, String> payload = {};
data.forEach((key, value) {
payload[key] = value.toString();
});
var response = await _crud.post(link: link, payload: payload);
if (response != null && response is Map && response['status'] == "success") {
await getKazan();
return true;
}
return false;
} catch (e) {
Get.snackbar("خطأ", "فشل تحديث التسعير: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,172 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class PassengerAdminController extends GetxController {
bool isLoading = false;
Map passengersData = {};
Map passengers = {};
double height = 150;
final formPassKey = GlobalKey<FormState>();
final formPrizeKey = GlobalKey<FormState>();
final titleNotify = TextEditingController();
final bodyNotify = TextEditingController();
final passengerController = TextEditingController();
final passengerPrizeController = TextEditingController();
Future getPassengerCount() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getPassengerDetails, payload: {});
var d = jsonDecode(res);
if (d['status'] == 'success') {
passengersData = d;
}
isLoading = false;
update();
}
Future addPassengerPrizeToWallet() async {
for (var i = 0; i < passengersData['message'].length; i++) {
await CRUD().post(link: AppLink.addPassengersWallet, payload: {
'passenger_id': passengersData['message'][i]['id'],
'balance': passengerPrizeController.text,
});
}
Get.back();
}
// داخل الـController نفسه
Future<bool> updatePassenger({
required String id, // أو مرّر phoneLookup بدل id لو حاب
String? firstName,
String? lastName,
String? phone,
}) async {
// لا نرسل طلب إذا ما في أي تغيير
if ((firstName == null || firstName.trim().isEmpty) &&
(lastName == null || lastName.trim().isEmpty) &&
(phone == null || phone.trim().isEmpty)) {
return false;
}
// فلتر بسيط للأرقام فقط
// String _normalizePhone(String s) => s.replaceAll(RegExp(r'\D+'), '');
final Map<String, dynamic> payload = {
'id':
id, // لو بدك تستخدم phone_lookup بدل id: احذف هذا وأرسل {'phone_lookup': phoneLookup}
};
if (firstName != null && firstName.trim().isNotEmpty) {
payload['first_name'] = firstName.trim();
}
if (lastName != null && lastName.trim().isNotEmpty) {
payload['last_name'] = lastName.trim();
}
if (phone != null && phone.trim().isNotEmpty) {
payload['phone'] = (phone);
}
// حالة تحميل
isLoading = true;
update();
try {
final res = await CRUD().post(
link: AppLink.admin_update_passenger, // عدّل الرابط حسب اسم مسارك
payload: payload,
);
final d = (res);
final ok = (d['status'] == 'success');
if (ok) {
// (اختياري) حدّث الكاش/الواجهة — مثلاً أعد الجلب
Get.snackbar('Update successful',
d['message']?.toString() ?? 'Passenger updated successfully',
backgroundColor: AppColor.greenColor);
// await getPassengerCount(); // أو حدّث passengersData محليًا إذا متاح
} else {
// (اختياري) أظهر رسالة خطأ
// Get.snackbar('Update failed', d['message']?.toString() ?? 'Unknown error');
}
return ok;
} catch (e) {
// Get.snackbar('Error', e.toString());
return false;
} finally {
isLoading = false;
update();
}
}
void addPassengerPrizeToWalletSecure() async {
try {
// Check if local authentication is available
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
);
if (didAuthenticate) {
// User authenticated successfully, proceed with payment
await addPassengerPrizeToWallet();
Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor);
} else {
// Authentication failed, handle accordingly
Get.snackbar('Authentication failed', '',
backgroundColor: AppColor.redColor);
// 'Authentication failed');
}
} else {
// Local authentication not available, proceed with payment without authentication
await addPassengerPrizeToWallet();
Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor);
}
} catch (e) {
rethrow;
}
}
Future getPassengers() async {
var res = await CRUD().get(link: AppLink.getPassengerbyEmail, payload: {
'passengerEmail': passengerController.text,
'passengerId': passengerController.text,
'passengerphone': passengerController.text,
});
var d = jsonDecode(res);
if (d['status'] == 'success') {
passengers = d;
}
update();
}
changeHeight() {
if (passengers.isEmpty) {
height = 0;
update();
}
height = 150;
update();
}
void clearPlaces() {
passengers = {};
update();
}
@override
void onInit() {
getPassengerCount();
super.onInit();
}
}
@@ -0,0 +1,83 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class PromoController extends GetxController {
var promoList = [].obs;
var isLoading = false.obs;
final CRUD _crud = CRUD();
@override
void onInit() {
super.onInit();
getPromos();
}
Future<void> getPromos() async {
isLoading.value = true;
try {
var response = await _crud.get(link: AppLink.getPassengersPromo);
if (response != null && response != 'failure' && response != 'token_expired') {
var decoded = response is String ? jsonDecode(response) : response;
if (decoded['status'] == "success") {
promoList.assignAll(decoded['message']);
}
} else {
promoList.clear();
}
} catch (e) {
Get.snackbar("خطأ", "فشل جلب أكواد الخصم: $e");
} finally {
isLoading.value = false;
}
}
Future<bool> addPromo(Map<String, dynamic> data) async {
isLoading.value = true;
try {
var response = await _crud.post(link: AppLink.addPassengersPromo, payload: data);
if (response != null && response is Map && response['status'] == "success") {
await getPromos();
return true;
}
return false;
} catch (e) {
Get.snackbar("خطأ", "فشل إضافة كود الخصم: $e");
return false;
} finally {
isLoading.value = false;
}
}
Future<bool> deletePromo(String id) async {
try {
var response = await _crud.post(link: AppLink.deletePassengersPromo, payload: {"id": id});
if (response != null && response is Map && response['status'] == "success") {
await getPromos();
return true;
}
return false;
} catch (e) {
Get.snackbar("خطأ", "فشل حذف كود الخصم: $e");
return false;
}
}
Future<bool> updatePromo(Map<String, dynamic> data) async {
isLoading.value = true;
try {
var response = await _crud.post(link: AppLink.updatePassengersPromo, payload: data);
if (response != null && response is Map && response['status'] == "success") {
await getPromos();
return true;
}
return false;
} catch (e) {
Get.snackbar("خطأ", "فشل تحديث كود الخصم: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class QualityController extends GetxController {
bool isLoading = false;
List driversBlacklist = [];
List passengersBlacklist = [];
Map scorecardData = {};
Future<void> fetchBlacklist() async {
isLoading = true;
update();
try {
var res = await CRUD().post(
link: AppLink.blacklistManager,
payload: {"action_type": "get_all"},
);
if (res is Map && res['status'] == 'success') {
driversBlacklist = res['message']['drivers'] ?? [];
passengersBlacklist = res['message']['passengers'] ?? [];
} else {
Get.snackbar("Error", "Failed to fetch blacklist");
}
} catch (e) {
Get.snackbar("Error", "Network error");
} finally {
isLoading = false;
update();
}
}
Future<void> unblockDriver(String phone) async {
try {
var res = await CRUD().post(
link: AppLink.blacklistManager,
payload: {
"action_type": "unblock_driver",
"phone": phone,
},
);
if (res is Map && res['status'] == 'success') {
Get.snackbar("Success", "Driver unblocked successfully");
fetchBlacklist(); // Refresh
} else {
Get.snackbar("Error", res['message'] ?? "Failed to unblock driver");
}
} catch (e) {
Get.snackbar("Error", "Network error");
}
}
Future<void> unblockPassenger(String phoneNormalized) async {
try {
var res = await CRUD().post(
link: AppLink.blacklistManager,
payload: {
"action_type": "unblock_passenger",
"phone_normalized": phoneNormalized,
},
);
if (res is Map && res['status'] == 'success') {
Get.snackbar("Success", "Passenger unblocked successfully");
fetchBlacklist(); // Refresh
} else {
Get.snackbar("Error", res['message'] ?? "Failed to unblock passenger");
}
} catch (e) {
Get.snackbar("Error", "Network error");
}
}
Future<void> fetchDriverScorecard(String driverId) async {
isLoading = true;
update();
try {
var res = await CRUD().post(
link: AppLink.driverScorecard,
payload: {"driver_id": driverId},
);
if (res is Map && res['status'] == 'success') {
scorecardData = res['message'];
} else {
Get.snackbar("Error", "Failed to fetch scorecard");
scorecardData = {};
}
} catch (e) {
Get.snackbar("Error", "Network error");
scorecardData = {};
} finally {
isLoading = false;
update();
}
}
@override
void onInit() {
super.onInit();
// fetchBlacklist() can be called when opening the page
}
}
@@ -0,0 +1,590 @@
import 'dart:convert';
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../functions/crud.dart';
class RegisterCaptainController extends GetxController {
bool isDriverSaved = false;
bool isCarSaved = false;
Map<String, dynamic>? arguments;
String? driverId;
String? email;
String? phone;
@override
void onInit() {
super.onInit();
arguments = Get.arguments;
initArguments();
}
void driveInit() {
arguments = Get.arguments;
initArguments();
}
void initArguments() {
if (arguments != null) {
driverId = arguments!['driverId'];
email = arguments!['email'];
phone = arguments!['phone_number'];
} else {
print('Arguments are null');
}
}
Map<String, dynamic> responseMap = {};
Map<String, dynamic> responseCarLicenseMapJordan = {};
Map<String, dynamic> responseBackCarLicenseMap = {};
Map<String, dynamic> responseIdCardMap = {};
Map<String, dynamic> responseIdCardDriverEgyptBack = {};
Map<String, dynamic> responseForComplaint = {};
Map<String, dynamic> responseIdCardDriverEgyptFront = {};
Map<String, dynamic> responseIdEgyptFront = {};
Map<String, dynamic> responseCriminalRecordEgypt = {};
Map<String, dynamic> responseIdEgyptBack = {};
Map<String, dynamic> responseIdEgyptDriverLicense = {};
String? responseIdCardDriverEgypt1;
bool isloading = false;
var image;
DateTime now = DateTime.now();
bool isLoading = false;
Future allMethodForAI(String prompt, imagePath, driverID) async {
isLoading = true;
update();
var extractedString = await CRUD().arabicTextExtractByVisionAndAI(
imagePath: imagePath, driverID: driverID);
var json = jsonDecode(extractedString);
var textValues = extractTextFromLines(json);
// await Get.put(AI()).geminiAiExtraction(prompt, textValues, imagePath);
await Get.put(RegisterCaptainController())
.anthropicAI(textValues, prompt, imagePath);
isLoading = false;
update();
}
String extractTextFromLines(Map<String, dynamic> jsonData) {
final readResult = jsonData['readResult'];
final blocks = readResult['blocks'];
final StringBuffer buffer = StringBuffer();
for (final block in blocks) {
final lines = block['lines'];
for (final line in lines) {
final text = line['text'];
buffer.write(text);
buffer.write('\n');
}
}
return buffer.toString().trim();
}
List driverNotCompleteRegistration = [];
getDriverNotCompleteRegistration() async {
var res = await CRUD()
.get(link: AppLink.getDriverNotCompleteRegistration, payload: {});
if (res != 'failure') {
var d = jsonDecode(res)['message'];
driverNotCompleteRegistration = d;
update();
} else {
Get.snackbar(res, '');
}
}
final today = DateTime.now();
Future<void> addDriverAndCarEgypt() async {
final expiryDate = responseIdEgyptDriverLicense['expiry_date'];
final expiryDateTime = DateTime.tryParse(expiryDate);
final isExpired = expiryDateTime != null && expiryDateTime.isBefore(today);
final taxExpiryDate = responseIdCardDriverEgyptBack['tax_expiry'];
// Get the inspection date from the response
final inspectionDate = responseIdCardDriverEgyptBack['inspection_date'];
final year = int.parse(inspectionDate.split('-')[0]);
// Try parsing the tax expiry date. If it fails, set it to null.
final taxExpiryDateTime = DateTime.tryParse(taxExpiryDate ?? '');
final isExpiredCar =
taxExpiryDateTime != null && taxExpiryDateTime.isBefore(today);
// Check if the inspection date is before today
final inspectionDateTime = DateTime(year, 1, 1);
final isInspectionExpired = inspectionDateTime.isBefore(today);
if (isExpiredCar || isInspectionExpired) {
Get.defaultDialog(
title: 'Expired Driver’s License'.tr,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.warning, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(
'Your driver’s license and/or car tax has expired. Please renew them before proceeding.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
const SizedBox(height: 16),
IconButton(
onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'Your driver’s license and/or car tax has expired. Please renew them before proceeding.'
// .tr,
// );
},
icon: const Icon(Icons.volume_up),
),
],
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: const Text('OK'),
),
],
);
} else if (isExpired) {
Get.defaultDialog(
title: 'Expired Driver’s License'.tr,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.warning, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(
'Your driver’s license has expired. Please renew it before proceeding.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
const SizedBox(height: 16),
IconButton(
onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'Your driver’s license has expired. Please renew it before proceeding.'
// .tr,
// );
},
icon: const Icon(Icons.volume_up),
),
],
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: const Text('OK'),
),
],
);
} else if (responseIdEgyptDriverLicense['national_number']
.toString()
.substring(0, 12) !=
responseIdEgyptBack['nationalID'].toString().substring(0, 12)) {
Get.defaultDialog(
barrierDismissible: false,
title: 'ID Mismatch',
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.warning, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(
'The national number on your driver’s license does not match the one on your ID document. Please verify and provide the correct documents.'
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),
const SizedBox(height: 16),
IconButton(
onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'The national number on your driver’s license does not match the one on your ID document. Please verify and provide the correct documents.',
// );
},
icon: const Icon(Icons.volume_up),
),
],
),
actions: [
TextButton(
onPressed: () {
Get.back();
},
child: const Text('OK'),
),
],
);
}
// else if (responseCriminalRecordEgypt['FullName'] !=
// responseIdEgyptDriverLicense['name_arabic']) {
// Get.defaultDialog(
// barrierDismissible: false,
// title: 'Criminal Record Mismatch',
// content: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// const Icon(Icons.warning, size: 48, color: Colors.red),
// const SizedBox(height: 16),
// Text(
// 'The full name on your criminal record does not match the one on your driver’s license. Please verify and provide the correct documents.'
// .tr,
// textAlign: TextAlign.center,
// style: AppStyle.title,
// ),
// const SizedBox(height: 16),
// IconButton(
// onPressed: () async {
// await Get.find<TextToSpeechController>().speakText(
// 'The full name on your criminal record does not match the one on your driver’s license. Please verify and provide the correct documents.'
// .tr,
// );
// },
// icon: const Icon(Icons.volume_up),
// ),
// ],
// ),
// actions: [
// TextButton(
// onPressed: () {
// Get.back();
// },
// child: const Text('OK'),
// ),
// ],
// );
// }
else {
await addDriverEgypt();
await addRegistrationCarEgypt();
if (isCarSaved && isDriverSaved) {
Get.snackbar('added', '',
backgroundColor:
AppColor.greenColor); // Get.offAll(() => HomeCaptain());
// Get.offAll(() => HomeCaptain());
}
}
}
Future<void> addDriverEgypt() async {
isLoading = true;
update();
var payload = {
'first_name': responseIdEgyptDriverLicense['firstName']?.toString() ??
'Not specified',
'last_name': responseIdEgyptDriverLicense['lastName']?.toString() ??
'Not specified',
'email': email?.toString() ?? 'Not specified',
'phone': phone?.toString() ?? 'Not specified',
'id': driverId?.toString() ?? 'Not specified',
'password': '123456',
'gender': responseIdEgyptBack['gender']?.toString() ?? 'Not specified',
'license_type':
responseIdEgyptDriverLicense['license_type']?.toString() ??
'Not specified',
'national_number':
responseIdEgyptBack['nationalID']?.toString() ?? 'Not specified',
'name_arabic': responseIdEgyptDriverLicense['name_arabic']?.toString() ??
'Not specified',
'name_english':
responseIdEgyptDriverLicense['name_english']?.toString() ??
'Not specified',
'issue_date': responseIdEgyptDriverLicense['issue_date']?.toString() ??
'Not specified',
'expiry_date': responseIdEgyptDriverLicense['expiry_date']?.toString() ??
'Not specified',
'license_categories': responseIdEgyptDriverLicense['license_categories']
is List
? responseIdEgyptDriverLicense['license_categories'].join(', ')
: responseIdEgyptDriverLicense['license_categories']?.toString() ??
'Not specified',
'address': responseIdEgyptFront['address']?.toString() ?? 'Not specified',
'card_id': responseIdEgyptFront['card_id']?.toString() ?? 'Not specified',
'occupation':
responseIdEgyptBack['occupation']?.toString() ?? 'Not specified',
'education':
responseIdEgyptBack['occupation']?.toString() ?? 'Not specified',
'licenseIssueDate':
responseIdEgyptDriverLicense['issue_date']?.toString() ??
'Not specified',
'religion':
responseIdEgyptBack['religion']?.toString() ?? 'Not specified',
'status': 'yet',
'birthdate': responseIdEgyptFront['dob']?.toString() ?? 'Not specified',
'maritalStatus':
responseIdEgyptBack['maritalStatus']?.toString() ?? 'Not specified',
'site': responseIdEgyptDriverLicense['address']?.toString() ??
'Not specified',
'employmentType':
responseIdEgyptDriverLicense['employmentType']?.toString() ??
'Not specified',
};
var res = await CRUD().post(link: AppLink.signUpCaptin, payload: payload);
var status1 = jsonDecode(res);
isLoading = false;
update();
// Handle response
if (status1['status'] == 'success') {
isDriverSaved = true;
Get.snackbar('Success', 'Driver data saved successfully',
backgroundColor: AppColor.greenColor);
} else {
Get.snackbar('Error', 'Failed to save driver data',
backgroundColor: Colors.red);
}
}
addCriminalDeocuments() async {
var res = await CRUD().post(link: AppLink.addCriminalDocuments, payload: {
"driverId": box.read(BoxName.driverID),
"IssueDate": responseCriminalRecordEgypt['IssueDate'],
"InspectionResult": responseCriminalRecordEgypt['InspectionResult'],
});
if (res != 'failure') {
Get.snackbar('uploaded sucssefuly'.tr, '');
}
}
Future addRegistrationCarEgypt() async {
try {
isLoading = true;
update();
var res = await CRUD().post(link: AppLink.addRegisrationCar, payload: {
'driverID': driverId,
'vin': responseIdCardDriverEgyptBack['chassis'].toString(),
'car_plate': responseIdCardDriverEgyptFront['car_plate'].toString(),
'make': responseIdCardDriverEgyptBack['make'].toString(),
'model': responseIdCardDriverEgyptBack['model'],
'year': responseIdCardDriverEgyptBack['year'].toString(),
'expiration_date':
responseIdCardDriverEgyptFront['LicenseExpirationDate'].toString(),
'color': responseIdCardDriverEgyptBack['color'],
'owner': responseIdCardDriverEgyptFront['owner'],
'color_hex': responseIdCardDriverEgyptBack['color_hex'].toString(),
'address': responseIdCardDriverEgyptFront['address'].toString(),
'displacement': responseIdCardDriverEgyptBack['engine'].toString(),
'fuel': responseIdCardDriverEgyptBack['fuel'].toString(),
'registration_date':
'${responseIdCardDriverEgyptBack['inspection_date']}',
});
isLoading = false;
update();
var status = jsonDecode(res);
if (status['status'] == 'success') {
isCarSaved = true;
Get.snackbar('Success', 'message',
backgroundColor: AppColor.greenColor);
}
} catch (e) {}
}
Future getComplaintDataToAI() async {
var res = await CRUD().get(
link: AppLink.getComplaintAllDataForDriver,
payload: {'driver_id': driverId.toString()},
);
if (res != 'failure') {
var d = jsonDecode(res)['message'];
return d;
} else {
return [
{'data': 'no data'}
];
}
}
Future<dynamic> anthropicAIForComplaint() async {
var dataComplaint = await getComplaintDataToAI();
var messagesData = [
{
"role": "user",
"content": [
{
"type": "text",
"text": "$dataComplaint ${AppInformation.complaintPrompt} "
}
]
}
];
var requestBody = jsonEncode({
"model": "claude-3-haiku-20240307",
"max_tokens": 1024,
"temperature": 0,
"system": "Json output only without any additional ",
"messages": messagesData,
});
final response = await http.post(
Uri.parse('https://api.anthropic.com/v1/messages'),
headers: {
'x-api-key': AK.anthropicAIkeySeferNew,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(utf8.decode(response.bodyBytes));
// Process the responseData as needed
responseForComplaint = jsonDecode(responseData['content'][0]['text']);
}
}
Future<dynamic> anthropicAI(
String payload, String prompt, String idType) async {
var messagesData = [
{
"role": "user",
"content": [
{"type": "text", "text": "$payload $prompt"}
]
}
];
var requestBody = jsonEncode({
"model": "claude-3-haiku-20240307",
"max_tokens": 1024,
"temperature": 0,
"system": "Json output only without any additional ",
"messages": messagesData,
});
final response = await http.post(
Uri.parse('https://api.anthropic.com/v1/messages'),
headers: {
'x-api-key': AK.anthropicAIkeySeferNew,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(utf8.decode(response.bodyBytes));
// Process the responseData as needed
if (idType == 'car_back') {
responseIdCardDriverEgyptBack =
jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'car_front') {
responseIdCardDriverEgyptFront =
jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'id_front') {
responseIdEgyptFront = jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'id_back') {
responseIdEgyptBack = jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'driver_license') {
responseIdEgyptDriverLicense =
jsonDecode(responseData['content'][0]['text']);
} else if (idType == 'criminalRecord') {
responseCriminalRecordEgypt =
jsonDecode(responseData['content'][0]['text']);
}
update();
return responseData.toString();
}
return responseIdCardDriverEgyptBack.toString();
}
Future<void> geminiAiExtraction(String prompt, payload, String idType) async {
var requestBody = jsonEncode({
"contents": [
{
"parts": [
{"text": "$payload $prompt"}
]
}
],
"generationConfig": {
"temperature": 1,
"topK": 64,
"topP": 0.95,
"maxOutputTokens": 8192,
"stopSequences": []
},
"safetySettings": [
{
"category": "HARM_CATEGORY_HARASSMENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_HATE_SPEECH",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
},
{
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
"threshold": "BLOCK_MEDIUM_AND_ABOVE"
}
]
});
final response = await http.post(
Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?key=${AK.geminiApi}'),
'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.0-pro:generateContent?key=${AK.geminiApi}'),
headers: {'Content-Type': 'application/json'},
body: requestBody,
);
if (response.statusCode == 200) {
var responseData = jsonDecode(response.body);
// Process the responseData as needed
var result = responseData['candidates'][0]['content']['parts'][0]['text'];
RegExp regex = RegExp(r"```json([^`]*)```");
String? jsonString =
regex.firstMatch(responseData.toString())?.group(1)?.trim();
if (jsonString != null) {
// Convert the JSON object to a String
jsonString = jsonEncode(json.decode(jsonString));
if (idType == 'car_back') {
responseIdCardDriverEgyptBack = jsonDecode(jsonString);
} else if (idType == 'car_front') {
responseIdCardDriverEgyptFront = jsonDecode(jsonString);
} else if (idType == 'id_front') {
responseIdEgyptFront = jsonDecode(jsonString);
} else if (idType == 'id_back') {
responseIdEgyptBack = jsonDecode(jsonString);
} else if (idType == 'driver_license') {
responseIdEgyptDriverLicense = jsonDecode(jsonString);
}
update();
} else {
Get.snackbar('Error', "JSON string not found",
backgroundColor: AppColor.redColor);
}
// Rest of your code...
} else {}
}
}
@@ -0,0 +1,73 @@
import 'dart:convert';
import 'package:fl_chart/fl_chart.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../models/model/admin/monthly_ride.dart';
import '../functions/crud.dart';
class RideAdminController extends GetxController {
bool isLoading = false;
late List<MonthlyDataModel> rideData;
late Map<String, dynamic> jsonResponse;
List<dynamic> ridesDetails = [];
var chartData;
// late List<ChartDataS> chartDatasync;
Future getRidesAdminDash() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getRidesPerMonth, payload: {});
jsonResponse = jsonDecode(res);
rideData = (jsonResponse['message'] as List)
.map((item) => MonthlyDataModel.fromJson(item))
.toList();
chartData = rideData
.map((data) => FlSpot(data.day.toDouble(), data.ridesCount.toDouble()))
.toList();
// chartDatasync = (jsonResponse['message'] as List)
// .map((item) => ChartDataS(
// item['year'],
// item['month'],
// item['day'],
// item['rides_count'],
// ))
// .toList();
isLoading = false;
update();
}
Future getRidesDetails() async {
// isLoading = true;
// update();
var res = await CRUD().get(link: AppLink.getRidesDetails, payload: {});
var d = jsonDecode(res);
ridesDetails = d['message'];
// isLoading = false;
// update();
}
@override
void onInit() async {
List<Future> initializationTasks = [
getRidesAdminDash(),
getRidesDetails(),
];
// cameras = await availableCameras();
await Future.wait(initializationTasks);
super.onInit();
}
}
// class ChartDataS {
// ChartDataS(this.year, this.month, this.day, this.ridesCount);
// final int year;
// final int month;
// final int day;
// final int ridesCount;
// }
@@ -0,0 +1,62 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../print.dart';
class SecurityV2Controller extends GetxController {
bool isLoading = true;
List<dynamic> auditLogs = [];
@override
void onInit() {
super.onInit();
fetchAuditLogs();
}
Future<void> fetchAuditLogs() async {
isLoading = true;
update();
try {
Log.print('Fetching from: ${AppLink.auditLogsV2}');
var res = await CRUD().get(link: AppLink.auditLogsV2, payload: {});
Log.print('Raw audit res type: ${res.runtimeType} | value: $res');
if (res == 'failure' || res == 'token_expired') {
Log.print('CRUD returned: $res');
Get.snackbar("خطأ بالاتصال", "السيرفر أرجع: $res",
backgroundColor: const Color(0x88FF0000),
colorText: const Color(0xFFFFFFFF));
auditLogs = [];
} else if (res != null) {
var d = res is String ? jsonDecode(res) : res;
Log.print('Decoded audit response: $d');
if (d['status'] == 'success') {
var message = d['message'];
if (message is List) {
auditLogs = message;
Log.print('Loaded ${auditLogs.length} audit logs');
} else {
auditLogs = [];
Log.print('message is not List: ${message.runtimeType}');
}
} else {
Log.print('Status not success: ${d['status']}');
Get.snackbar("خطأ من السيرفر", "${d['message'] ?? d['status']}",
backgroundColor: const Color(0x88FF0000),
colorText: const Color(0xFFFFFFFF));
}
}
} catch (e) {
Log.print('Error fetching audit logs: $e');
Get.snackbar("خطأ برمجي", "$e",
backgroundColor: const Color(0x88FF0000),
colorText: const Color(0xFFFFFFFF));
}
isLoading = false;
update();
}
}
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
import '../functions/device_info.dart';
import '../../views/widgets/snackbar.dart';
class StaffController extends GetxController {
final CRUD _crud = CRUD();
final formKey = GlobalKey<FormState>();
// التكست كنترولرز
final nameController = TextEditingController();
final phoneController = TextEditingController();
final emailController = TextEditingController();
final passwordController = TextEditingController();
final birthdateController = TextEditingController();
String selectedGender = 'Male';
String selectedRole = 'service'; // 'admin' or 'service'
bool isLoading = false;
Future<void> registerStaff() async {
if (!formKey.currentState!.validate()) return;
isLoading = true;
update();
try {
// ملاحظة: لا نأخذ بصمة جهاز الأدمن هنا، بل نتركها فارغة ليقوم الموظف بربطها عند أول دخول له
String fingerprint = "";
var response = await _crud.post(
link: AppLink.addStaff,
payload: {
"name": nameController.text.trim(),
"phone": phoneController.text.trim(),
"email": emailController.text.trim(),
"password": passwordController.text.trim(),
"role": selectedRole,
"gender": selectedGender,
"birthdate": birthdateController.text.trim(),
"fingerprint": fingerprint,
"site": "main", // القيمة الافتراضية للفرع
},
);
if (response != "failure") {
mySnackbarSuccess('تمت إضافة الموظف بنجاح');
_clearFields();
Get.back();
} else {
mySnackeBarError('فشل في إضافة الموظف. يرجى المحاولة لاحقاً');
}
} catch (e) {
mySnackeBarError('حدث خطأ: $e');
} finally {
isLoading = false;
update();
}
}
void _clearFields() {
nameController.clear();
phoneController.clear();
emailController.clear();
passwordController.clear();
birthdateController.clear();
}
@override
void onClose() {
nameController.dispose();
phoneController.dispose();
emailController.dispose();
passwordController.dispose();
birthdateController.dispose();
super.onClose();
}
}
@@ -0,0 +1,512 @@
import 'dart:convert';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../constant/links.dart';
import '../../print.dart';
import '../functions/crud.dart';
// ══════════════════════════════════════════════════════════════
// MODEL: Represents one employee's full data for a period
// ══════════════════════════════════════════════════════════════
class EmployeeChartData {
final String name;
final Color color;
final List<FlSpot> notesSpots;
final List<FlSpot> callsSpots;
const EmployeeChartData({
required this.name,
required this.color,
required this.notesSpots,
required this.callsSpots,
});
int get totalNotes => notesSpots.fold(0, (sum, s) => sum + s.y.toInt());
int get totalCalls => callsSpots.fold(0, (sum, s) => sum + s.y.toInt());
EmployeeChartData copyWith({
List<FlSpot>? notesSpots,
List<FlSpot>? callsSpots,
}) {
return EmployeeChartData(
name: name,
color: color,
notesSpots: notesSpots ?? this.notesSpots,
callsSpots: callsSpots ?? this.callsSpots,
);
}
}
// ══════════════════════════════════════════════════════════════
// MODEL: Employment activation stats per employee
// ══════════════════════════════════════════════════════════════
class EmploymentStat {
final String name;
final int count;
final Color color;
const EmploymentStat({
required this.name,
required this.count,
required this.color,
});
}
// ══════════════════════════════════════════════════════════════
// CONTROLLER
// ══════════════════════════════════════════════════════════════
class StaticController extends GetxController {
// ─── Color Palette for Dynamic Employees ───────────────────
static const List<Color> _employeeColors = [
Color(0xFF00D4AA), // teal
Color(0xFF82AAFF), // blue
Color(0xFFFFCB6B), // amber
Color(0xFFC792EA), // purple
Color(0xFFFF5370), // red
Color(0xFFC3E88D), // green
Color(0xFFF07178), // coral
Color(0xFF89DDFF), // cyan
];
Color _colorForIndex(int i) => _employeeColors[i % _employeeColors.length];
// ─── Date & State ───────────────────────────────────────────
DateTime? startDate = DateTime(DateTime.now().year, DateTime.now().month, 1);
DateTime? endDate =
DateTime(DateTime.now().year, DateTime.now().month + 1, 0);
DateTime? compareStartDate;
DateTime? compareEndDate;
bool isComparing = false;
bool isLoading = false;
// ─── Daily Notes State ─────────────────────────────────────
bool isLoadingNotes = false;
List<dynamic> dailyNotesList = [];
// ─── Main Chart Data ───────────────────────────────────────
List<FlSpot> chartDataPassengers = [];
List<FlSpot> chartDataDrivers = [];
List<FlSpot> chartDataRides = [];
List<FlSpot> chartDataDriversMatchingNotes = [];
List<FlSpot> chartDataPassengersCompare = [];
List<FlSpot> chartDataDriversCompare = [];
List<FlSpot> chartDataRidesCompare = [];
List<FlSpot> chartDataDriversMatchingNotesCompare = [];
// ─── 🔥 DYNAMIC Employee Data ─────────────────────────────
// Key = employee name (from server), Value = their chart data
Map<String, EmployeeChartData> employeeData = {};
Map<String, EmployeeChartData> employeeDataCompare = {};
// Set of all known employee names (union of current + compare)
Set<String> get allEmployeeNames => {
...employeeData.keys,
...employeeDataCompare.keys,
};
// ─── Employment Stats ──────────────────────────────────────
List<EmploymentStat> employmentStatsList = [];
// ─── Totals ────────────────────────────────────────────────
String totalMonthlyPassengers = '0';
String totalMonthlyRides = '0';
String totalMonthlyDrivers = '0';
// ─── Raw Lists ─────────────────────────────────────────────
List staticList = [];
// ─── Color Registry (stable across rebuilds) ───────────────
final Map<String, Color> _employeeColorRegistry = {};
Color _getOrAssignColor(String name) {
if (!_employeeColorRegistry.containsKey(name)) {
_employeeColorRegistry[name] =
_colorForIndex(_employeeColorRegistry.length);
}
return _employeeColorRegistry[name]!;
}
@override
void onInit() {
super.onInit();
getAll();
}
// ─── Helpers ───────────────────────────────────────────────
double get daysInPeriod {
if (startDate == null || endDate == null) return 31;
return endDate!.difference(startDate!).inDays + 1.0;
}
String get currentDateString {
if (startDate == null || endDate == null) return "";
return "${DateFormat('yyyy-MM-dd').format(startDate!)} : "
"${DateFormat('yyyy-MM-dd').format(endDate!)}";
}
String get compareDateString {
if (compareStartDate == null || compareEndDate == null) return "";
return "${DateFormat('yyyy-MM-dd').format(compareStartDate!)} : "
"${DateFormat('yyyy-MM-dd').format(compareEndDate!)}";
}
// ─── Date Actions ──────────────────────────────────────────
void updateDateRange(DateTime start, DateTime end) {
startDate = start;
endDate = end;
if (isComparing) _calculateCompareDates();
getAll();
update();
}
void _calculateCompareDates() {
if (startDate == null || endDate == null) return;
Duration duration = endDate!.difference(startDate!);
compareEndDate = startDate!.subtract(const Duration(days: 1));
compareStartDate = compareEndDate!.subtract(duration);
}
Future<void> toggleComparison() async {
isComparing = !isComparing;
if (isComparing) {
_calculateCompareDates();
} else {
compareStartDate = null;
compareEndDate = null;
_clearComparisonData();
}
await getAll();
}
void _clearComparisonData() {
chartDataPassengersCompare.clear();
chartDataDriversCompare.clear();
chartDataRidesCompare.clear();
chartDataDriversMatchingNotesCompare.clear();
employeeDataCompare.clear();
}
Map<String, dynamic> _getPayload(DateTime start, DateTime end) => {
"start_date": DateFormat('yyyy-MM-dd').format(start),
"end_date": DateFormat('yyyy-MM-dd').format(end),
"month": start.month.toString(),
"year": start.year.toString(),
};
// ─── Main Fetch ────────────────────────────────────────────
Future<void> getAll() async {
if (startDate == null || endDate == null) return;
isLoading = true;
update();
await Future.wait([
fetchPassengers(isCompare: false),
fetchRides(isCompare: false),
fetchDrivers(isCompare: false),
fetchEmployeeDynamic(isCompare: false),
fetchEditorCallsDynamic(isCompare: false),
fetchEmploymentStats(),
]);
if (isComparing && compareStartDate != null && compareEndDate != null) {
await Future.wait([
fetchPassengers(isCompare: true),
fetchRides(isCompare: true),
fetchDrivers(isCompare: true),
fetchEmployeeDynamic(isCompare: true),
fetchEditorCallsDynamic(isCompare: true),
]);
}
isLoading = false;
update();
}
// ─── Spot Generator ───────────────────────────────────────
List<FlSpot> _generateSpots(
List<dynamic> data,
String dateKey,
String valueKey,
DateTime startOfRange,
DateTime endOfRange,
) {
Map<String, double> dataMap = {
for (var item in data)
item[dateKey].toString():
double.tryParse(item[valueKey].toString()) ?? 0.0
};
int totalDays = endOfRange.difference(startOfRange).inDays + 1;
return List.generate(totalDays, (i) {
final date = startOfRange.add(Duration(days: i));
final key = DateFormat('yyyy-MM-dd').format(date);
return FlSpot((i + 1).toDouble(), dataMap[key] ?? 0.0);
});
}
/// Generates spots map keyed by employee name from a date→name→value structure
Map<String, List<FlSpot>> _generateEmployeeSpots(
Map<String, Map<String, double>> dateNameMap,
DateTime start,
DateTime end,
) {
// Discover all employee names dynamically
final Set<String> names = {};
for (var dayData in dateNameMap.values) {
names.addAll(dayData.keys);
}
int totalDays = end.difference(start).inDays + 1;
final Map<String, List<FlSpot>> result = {};
for (final name in names) {
result[name] = List.generate(totalDays, (i) {
final date = start.add(Duration(days: i));
final dateStr = DateFormat('yyyy-MM-dd').format(date);
final value = dateNameMap[dateStr]?[name] ?? 0.0;
return FlSpot((i + 1).toDouble(), value);
});
}
return result;
}
/// Parses a list of {date/day, NAME, count} records into a dateNameMap
Map<String, Map<String, double>> _parseDateNameMap(List<dynamic> jsonData) {
final Map<String, Map<String, double>> result = {};
for (var item in jsonData) {
final dateStr = (item['date'] ?? item['day']).toString();
final name = item['NAME'].toString().toLowerCase().trim();
final count = double.tryParse(item['count'].toString()) ?? 0.0;
result.putIfAbsent(dateStr, () => {})[name] =
(result[dateStr]?[name] ?? 0) + count;
}
return result;
}
// ─── Passengers ───────────────────────────────────────────
Future<void> fetchPassengers({bool isCompare = false}) async {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getPassengersStatic, payload: _getPayload(start, end));
final json = jsonDecode(res);
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (!isCompare && data.isNotEmpty && data[0]['totalMonthly'] != null) {
totalMonthlyPassengers = data[0]['totalMonthly'].toString();
}
final spots = _generateSpots(data, 'day', 'totalPassengers', start, end);
if (isCompare)
chartDataPassengersCompare = spots;
else
chartDataPassengers = spots;
}
// ─── Rides ────────────────────────────────────────────────
Future<void> fetchRides({bool isCompare = false}) async {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD()
.get(link: AppLink.getRidesStatic, payload: _getPayload(start, end));
final json = jsonDecode(res);
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (!isCompare && data.isNotEmpty && data[0]['totalMonthly'] != null) {
totalMonthlyRides = data[0]['totalMonthly'].toString();
}
final spots = _generateSpots(data, 'day', 'totalRides', start, end);
if (isCompare)
chartDataRidesCompare = spots;
else
chartDataRides = spots;
}
// ─── Drivers ──────────────────────────────────────────────
Future<void> fetchDrivers({bool isCompare = false}) async {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getdriverstotalMonthly, payload: _getPayload(start, end));
final json = jsonDecode(res);
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (!isCompare &&
data.isNotEmpty &&
data[0]['totalMonthlyDrivers'] != null) {
totalMonthlyDrivers = data[0]['totalMonthlyDrivers'].toString();
staticList = data;
}
final spotsDrivers =
_generateSpots(data, 'day', 'dailyTotalDrivers', start, end);
final spotsNotes =
_generateSpots(data, 'day', 'dailyMatchingNotes', start, end);
if (isCompare) {
chartDataDriversCompare = spotsDrivers;
chartDataDriversMatchingNotesCompare = spotsNotes;
} else {
chartDataDrivers = spotsDrivers;
chartDataDriversMatchingNotes = spotsNotes;
}
}
// ─── 🔥 DYNAMIC: Employee Notes ───────────────────────────
Future<void> fetchEmployeeDynamic({bool isCompare = false}) async {
try {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getEmployeeStatic, payload: _getPayload(start, end));
if (res == 'failure') return;
final json = jsonDecode(res) as Map<String, dynamic>;
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (data.isEmpty) return;
final dateNameMap = _parseDateNameMap(data);
final spotsMap = _generateEmployeeSpots(dateNameMap, start, end);
// Merge into employee data map
final target = isCompare ? employeeDataCompare : employeeData;
spotsMap.forEach((name, spots) {
final color = _getOrAssignColor(name);
if (target.containsKey(name)) {
target[name] = target[name]!.copyWith(notesSpots: spots);
} else {
target[name] = EmployeeChartData(
name: name,
color: color,
notesSpots: spots,
callsSpots: [],
);
}
});
} catch (e) {
Log.print('Error in fetchEmployeeDynamic: $e');
}
}
// ─── 🔥 DYNAMIC: Employee Calls ───────────────────────────
Future<void> fetchEditorCallsDynamic({bool isCompare = false}) async {
try {
final start = isCompare ? compareStartDate! : startDate!;
final end = isCompare ? compareEndDate! : endDate!;
final res = await CRUD().get(
link: AppLink.getEditorStatsCalls, payload: _getPayload(start, end));
if (res == 'failure') return;
final json = jsonDecode(res) as Map<String, dynamic>;
if (json['status'] == 'failure') return;
final List<dynamic> data = json['message'];
if (data.isEmpty) return;
final dateNameMap = _parseDateNameMap(data);
final spotsMap = _generateEmployeeSpots(dateNameMap, start, end);
final target = isCompare ? employeeDataCompare : employeeData;
spotsMap.forEach((name, spots) {
final color = _getOrAssignColor(name);
if (target.containsKey(name)) {
target[name] = target[name]!.copyWith(callsSpots: spots);
} else {
target[name] = EmployeeChartData(
name: name,
color: color,
notesSpots: [],
callsSpots: spots,
);
}
});
} catch (e) {
Log.print('Error in fetchEditorCallsDynamic: $e');
}
}
// ─── Employment Stats ─────────────────────────────────────
Future<void> fetchEmploymentStats() async {
try {
final res = await CRUD().get(
link: AppLink.getEmployeeDriverAfterCallingRegister,
payload: _getPayload(startDate!, endDate!));
if (res == 'failure') return;
final json = jsonDecode(res);
if (json['status'] != 'success') return;
final List<dynamic> data = json['message']?['data'] ?? [];
// Aggregate by name (dynamic — no hardcoded allowed list)
final Map<String, int> aggregated = {};
for (var item in data) {
final name = item['employmentType'].toString().toLowerCase().trim();
final count = int.tryParse(item['count'].toString()) ?? 0;
aggregated[name] = (aggregated[name] ?? 0) + count;
}
employmentStatsList = aggregated.entries.map((e) {
return EmploymentStat(
name: e.key,
count: e.value,
color: _getOrAssignColor(e.key),
);
}).toList()
..sort((a, b) => b.count.compareTo(a.count)); // sort descending
} catch (e) {
Log.print("Error fetchEmploymentStats: $e");
}
}
// ─── Daily Notes ──────────────────────────────────────────
Future<void> fetchDailyNotes(DateTime date) async {
try {
isLoadingNotes = true;
dailyNotesList.clear();
update();
final res = await CRUD().post(
link: AppLink.getNotesForEmployee,
payload: {"date": DateFormat('yyyy-MM-dd').format(date)});
if (res != 'failure') {
final json = res;
if (json['status'] == 'success') {
dailyNotesList = json['message'];
}
}
} catch (e) {
Log.print("Error fetchDailyNotes: $e");
} finally {
isLoadingNotes = false;
update();
}
}
// ─── Computed Summaries for UI ────────────────────────────
/// Returns sorted list of employees by total notes descending
List<EmployeeChartData> get employeesSortedByNotes {
final list = employeeData.values.toList();
list.sort((a, b) => b.totalNotes.compareTo(a.totalNotes));
return list;
}
/// Returns sorted list of employees by total calls descending
List<EmployeeChartData> get employeesSortedByCalls {
final list = employeeData.values.toList();
list.sort((a, b) => b.totalCalls.compareTo(a.totalCalls));
return list;
}
/// Grand total notes across all employees
int get grandTotalNotes =>
employeeData.values.fold(0, (s, e) => s + e.totalNotes);
/// Grand total calls across all employees
int get grandTotalCalls =>
employeeData.values.fold(0, (s, e) => s + e.totalCalls);
}
@@ -0,0 +1,195 @@
import 'dart:async';
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../../print.dart';
import '../functions/crud.dart';
class WalletAdminController extends GetxController {
bool isLoading = false;
late Map<String, dynamic> jsonResponse;
List<dynamic> walletDetails = [];
List driversWalletPoints = [];
@override
void onInit() {
getWalletForEachDriverToPay();
super.onInit();
}
Future getWalletAdminDash() async {
isLoading = true;
update();
var res = await CRUD().get(link: AppLink.getRidesPerMonth, payload: {});
jsonResponse = jsonDecode(res);
}
Future payToBankDriverAll() async {
for (var i = 0; i < driversWalletPoints.length; i++) {
String token = await getToken();
await Future.delayed(const Duration(seconds: 1));
try {
await payToDriverBankAccount(
token,
driversWalletPoints[i]['total_amount'].toString(),
driversWalletPoints[i]['accountBank'].toString(),
driversWalletPoints[i]['bankCode'].toString(),
driversWalletPoints[i]['name_arabic'].toString(),
driversWalletPoints[i]['driverID'].toString(),
driversWalletPoints[i]['phone'].toString(),
driversWalletPoints[i]['email'].toString(),
);
await Future.delayed(const Duration(seconds: 3));
} on FormatException catch (e) {
// Handle the error or rethrow the exception as needed
}
}
}
Future<void> payToDriverBankAccount(
String token,
String amount,
String bankCardNumber,
String bankCode,
String name,
String driverId,
String phone,
String email) async {
var headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
var body = jsonEncode({
"issuer": "bank_card",
"amount": amount,
"full_name": name,
"bank_card_number": bankCardNumber,
"bank_code": bankCode,
"bank_transaction_type": "cash_transfer"
});
var response = await http.post(
Uri.parse(
'https://stagingpayouts.paymobsolutions.com/api/secure/disburse/'),
headers: headers,
body: body);
if (response.statusCode == 200) {
var d = jsonDecode(response.body);
if (d['status_description'] ==
"Transaction received and validated successfully. Dispatched for being processed by the bank") {
await addPayment('payFromSeferToDriver', driverId,
((-1) * double.parse(amount)).toString());
await addSeferWallet('payFromSeferToDriver', driverId,
((-1) * double.parse(amount)).toString());
await updatePaymentToPaid(driverId);
await sendEmail(driverId, amount, phone, name, bankCardNumber, email);
}
} else {}
}
// String paymentToken = '';
Future<String> generateToken(String amount) async {
var res = await CRUD().post(link: AppLink.addPaymentTokenDriver, payload: {
'driverID': box.read(BoxName.driverID).toString(),
'amount': amount.toString(),
});
var d = jsonDecode(res);
return d['message'];
}
Future sendEmail(
String driverId, amount, phone, name, bankCardNumber, email) async {
await CRUD().sendEmail(AppLink.sendEmailToDrivertransaction, {
"driverID": driverId,
"total_amount": amount,
"phone": phone,
"name_arabic": name,
"accountBank": bankCardNumber,
"email": email
});
}
Future addSeferWallet(
String paymentMethod, String driverID, String point) async {
var seferToken = await generateToken(point.toString());
await CRUD().post(link: AppLink.addSeferWallet, payload: {
'amount': point.toString(),
'paymentMethod': paymentMethod,
'passengerId': 'driver',
'token': seferToken,
'driverId': driverID.toString(),
});
}
Future addPayment(
String paymentMethod, String driverID, String amount) async {
var paymentToken =
await generateToken(((double.parse(amount))).toStringAsFixed(0));
await CRUD().post(link: AppLink.addDrivePayment, payload: {
'rideId': DateTime.now().toIso8601String(),
'amount': ((double.parse(amount))).toStringAsFixed(0),
'payment_method': paymentMethod,
'passengerID': 'myself',
'token': paymentToken,
'driverID': driverID.toString(),
});
}
Future updatePaymentToPaid(String driverID) async {
await CRUD().post(link: AppLink.updatePaymetToPaid, payload: {
'driverID': driverID.toString(),
});
}
Future<String> getToken() async {
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
// 'Cookie':
// 'csrftoken=74iZJ8XYyuTm5WRq2W4tpWX5eqoJLZVK5QhuDrChWpDtzpgGA269bbCWuEcW85t4'
};
var body = {
'grant_type': 'password',
'username': AK.payMobOutUserName,
'password': AK.payMobOutPassword,
'client_id': AK.payMobOutClient_id,
'client_secret': AK.payMobOutClientSecrret
};
var res = await http.post(
Uri.parse(
'https://stagingpayouts.paymobsolutions.com/api/secure/o/token/'),
headers: headers,
body: body,
);
String token = '';
if (res.statusCode == 200) {
var decode = jsonDecode(res.body);
token = decode['access_token'];
}
return token;
}
Future getWalletForEachDriverToPay() async {
isLoading = true;
update();
var res = await CRUD()
.postWallet(link: AppLink.getVisaForEachDriver, payload: {});
var d = (res);
if (d != 'failure') {
driversWalletPoints = d['message'];
isLoading = false;
update();
}
driversWalletPoints = [];
isLoading = false;
update();
}
}
@@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'otp_helper.dart';
class OtpVerificationAdmin extends StatefulWidget {
final String phone;
const OtpVerificationAdmin({required this.phone});
@override
State<OtpVerificationAdmin> createState() => _OtpVerificationAdminState();
}
class _OtpVerificationAdminState extends State<OtpVerificationAdmin> {
final _otpController = TextEditingController();
bool _isLoading = false;
Future<void> _verifyOtp() async {
setState(() => _isLoading = true);
final otpHelper = OtpHelper();
await otpHelper.verifyOtp(widget.phone, _otpController.text.trim());
// if (success) {
// Get.offAllNamed('/admin-dashboard');
// } else {
// Get.snackbar('خطأ', 'رمز التحقق غير صحيح');
// }
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('التحقق من الرمز')),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
TextFormField(
controller: _otpController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'رمز التحقق'),
),
const SizedBox(height: 20),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _verifyOtp,
child: const Text('تحقق وأدخل'),
)
],
),
),
);
}
}
@@ -0,0 +1,296 @@
import 'package:flutter/material.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:get/get.dart';
import 'package:siro_admin/controller/functions/device_info.dart';
import 'package:siro_admin/views/auth/login_page.dart';
import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../constant/info.dart';
import '../../main.dart';
import '../../print.dart';
import '../../views/admin/admin_home_page.dart';
import '../../views/widgets/snackbar.dart';
import '../admin/dashboard_controller.dart';
import '../functions/crud.dart';
import '../functions/encrypt_decrypt.dart';
class OtpHelper extends GetxController {
static final String _sendOtpUrl =
'${AppLink.server}/Admin/auth/send_otp_admin.php';
static final String _verifyOtpUrl =
'${AppLink.server}/Admin/auth/verify_otp_admin.php';
static final String _checkAdminLogin =
'${AppLink.server}/Admin/auth/login.php';
/// إرسال OTP
static Future<bool> sendOtp(String phoneNumber) async {
try {
// await CRUD().getJWT();
final response = await CRUD().post(
link: _sendOtpUrl,
payload: {'receiver': phoneNumber},
);
// Log.print('_sendOtpUrl: ${_sendOtpUrl}');
// Log.print('response: ${response}');
if (response != 'failure') {
mySnackbarSuccess('تم إرسال رمز التحقق إلى رقمك عبر WhatsApp');
return true;
} else {
mySnackeBarError('حدث خطأ من الخادم. حاول مجددًا.');
return false;
}
} catch (e) {
Log.print('OTP SEND ERROR: $e');
mySnackeBarError('حدث خطأ أثناء الإرسال: $e');
return false;
}
}
/// التحقق من OTP
Future<void> verifyOtp(String phoneNumber, String otp) async {
try {
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {
'phone_number': phoneNumber,
'otp': otp,
'device_number': box.read(BoxName.fingerPrint)
},
);
if (response != 'failure') {
if (response['status'] == 'success') {
box.write(BoxName.phoneVerified, true);
box.write(BoxName.adminPhone, phoneNumber);
mySnackbarSuccess('تم التحقق من الرقم بنجاح');
await checkAdminLogin();
} else {
mySnackeBarError(response['message'] ?? 'فشل في التحقق.');
}
} else {
mySnackeBarError('فشل من الخادم. حاول مرة أخرى.');
}
} catch (e) {
Log.print('OTP VERIFY ERROR: $e');
mySnackeBarError('خطأ في التحقق: $e');
}
}
/// تسجيل الدخول بكلمة المرور والبصمة
Future<bool> loginWithPassword(String password) async {
try {
final fingerprint = box.read(BoxName.fingerPrint);
final response = await CRUD().post(
link: _checkAdminLogin,
payload: {
'fingerprint': fingerprint,
'password': password,
},
);
if (response != 'failure') {
// إذا كان الرد يتطلب OTP (السيرفر يرجعها بداخل message)
final msg = response['message'];
if (response['status'] == 'otp_required' ||
(msg is Map && msg['status'] == 'otp_required')) {
String phone = (msg is Map ? msg['phone'] : response['phone']) ?? '';
_showOtpDialog(phone, password, fingerprint);
return false; // ننتظر إكمال الـ OTP
}
// إذا نجح الدخول مباشرة
return _handleLoginSuccess(response, password);
} else {
// سيقوم CRUD بإظهار الخطأ المناسب (مثل "حسابك قيد المراجعة")
return false;
}
} catch (e) {
Log.print('LOGIN ERROR: $e');
mySnackeBarError('حدث خطأ أثناء تسجيل الدخول: $e');
return false;
}
}
/// التحقق من OTP الخاص بتسجيل الدخول
Future<void> verifyLoginOtp(
String phone, String otp, String password, String fingerprint) async {
try {
final response = await CRUD().post(
link: '${AppLink.server}/Admin/auth/verify_login.php',
payload: {
'phone': phone,
'otp': otp,
'fingerprint': fingerprint,
},
);
if (response != 'failure') {
bool success = await _handleLoginSuccess(response, password);
if (success) {
try {
if (Get.isRegistered<DashboardController>()) {
Get.find<DashboardController>().getDashBoard();
}
} catch (e) {}
Get.offAll(() => const AdminHomePage());
}
}
} catch (e) {
Log.print('OTP VERIFY LOGIN ERROR: $e');
mySnackeBarError('خطأ في التحقق من الرمز: $e');
}
}
Future<bool> _handleLoginSuccess(dynamic response, String password) async {
final msg = response['message'];
final data = response['admin'] ?? (msg is Map ? msg['admin'] : null);
final jwt = response['jwt'] ?? (msg is Map ? msg['jwt'] : null);
if (jwt != null) {
await box.write(BoxName.jwt, c(jwt));
}
if (data != null) {
if (data['id'] != null) await box.write(BoxName.driverID, data['id']);
if (data['role'] != null) {
String role = data['role'].toString().trim();
await box.write('admin_role', role);
Log.print('Admin role saved: $role');
}
if (data['phone'] != null)
await box.write(BoxName.adminPhone, data['phone']);
}
await box.write(BoxName.phoneVerified, true);
await box.write('admin_password', password);
mySnackbarSuccess('تم تسجيل الدخول بنجاح');
return true;
}
void _showOtpDialog(String phone, String password, String fingerprint) {
String otpCode = '';
Get.defaultDialog(
title: 'رمز التحقق',
content: Column(
children: [
Text('تم إرسال رمز التحقق إلى WhatsApp الخاص بك ($phone)'),
const SizedBox(height: 16),
TextField(
onChanged: (val) => otpCode = val,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: 'أدخل الرمز هنا',
border: OutlineInputBorder(),
),
),
],
),
textConfirm: 'تحقق',
confirmTextColor: Colors.white,
onConfirm: () {
if (otpCode.length >= 3) {
Get.back();
verifyLoginOtp(phone, otpCode, password, fingerprint);
} else {
mySnackeBarError('الرجاء إدخال رمز صحيح');
}
},
);
}
static bool _isAutoLoginAttempted = false;
Future<void> checkAdminLogin() async {
final fingerprint = box.read(BoxName.fingerPrint);
final password = box.read('admin_password');
if (fingerprint == null || password == null) {
Get.offAll(() => const AdminLoginPage());
return;
}
// ─── أولاً: التحقق من وجود توكن JWT صالح ─────────
// إذا وُجد توكن غير منتهي الصلاحية → ندخل مباشرة بدون استدعاء login.php
final rawJwt = box.read(BoxName.jwt);
if (rawJwt != null) {
try {
String token = r(rawJwt.toString()).split(AppInformation.addd)[0];
if (!JwtDecoder.isExpired(token)) {
Log.print('Valid JWT found, skipping login.php (no OTP needed)');
Get.offAll(() => const AdminHomePage());
return;
}
Log.print('JWT expired, need fresh login');
} catch (e) {
Log.print('JWT decode failed: \$e, need fresh login');
}
}
// ─── ثانياً: لا يوجد توكن صالح → استدعاء login.php ───
final response = await CRUD().post(
link: _checkAdminLogin,
payload: {
'fingerprint': fingerprint,
'password': password,
'is_renewal': '1',
},
);
if (response != 'failure') {
final msg = response['message'];
if (response['status'] == 'otp_required' ||
(msg is Map && msg['status'] == 'otp_required')) {
String phone = (msg is Map ? msg['phone'] : response['phone']) ?? '';
_showOtpDialog(phone, password, fingerprint);
return; // ننتظر إدخال رمز التحقق
}
if (msg is Map && msg['jwt'] != null) {
box.write(BoxName.jwt, c(msg['jwt']));
if (msg['admin'] != null && msg['admin']['id'] != null) {
box.write(BoxName.driverID, msg['admin']['id']);
}
} else if (response['jwt'] != null) {
box.write(BoxName.jwt, c(response['jwt']));
}
try {
if (Get.isRegistered<DashboardController>()) {
Get.find<DashboardController>().getDashBoard();
}
} catch (e) {}
Get.offAll(() => const AdminHomePage());
} else {
Log.print('Auto-login failed, redirecting to login page');
Get.offAll(() => const AdminLoginPage());
}
}
@override
void onInit() {
super.onInit();
DeviceHelper.getDeviceFingerprint().then((deviceFingerprint) {
box.write(BoxName.fingerPrint, deviceFingerprint);
});
// تأجيل تنفيذ التنقل حتى تنتهي مرحلة البناء
// ⚠️ محاولة واحدة فقط للتسجيل التلقائي لمنع الحلقة اللانهائية
Future.microtask(() {
if (_isAutoLoginAttempted) {
// نحن في حلقة — نتوقف ونذهب لصفحة الدخول مباشرة
Log.print('Auto-login already attempted, skipping to login page');
return;
}
_isAutoLoginAttempted = true;
if (box.read(BoxName.phoneVerified) == true &&
box.read(BoxName.adminPhone) != null) {
checkAdminLogin();
} else {
Get.offAll(() => AdminLoginPage());
}
});
}
}
@@ -0,0 +1,113 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import '../../../constant/links.dart';
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../main.dart';
import '../functions/crud.dart';
class PaymobPayout extends GetxController {
bool isLoading = false;
String dropdownValue = 'etisalat';
Future<String> getToken() async {
var headers = {
'Content-Type': 'application/x-www-form-urlencoded',
// 'Cookie':
// 'csrftoken=74iZJ8XYyuTm5WRq2W4tpWX5eqoJLZVK5QhuDrChWpDtzpgGA269bbCWuEcW85t4'
};
var body = {
'grant_type': 'password',
'username': AK.payMobOutUserName,
'password': AK.payMobOutPassword,
'client_id': AK.payMobOutClient_id,
'client_secret': AK.payMobOutClientSecrret
};
var res = await http.post(
Uri.parse('https://payouts.paymobsolutions.com/api/secure/o/token/'),
headers: headers,
body: body,
);
String token = '';
if (res.statusCode == 200) {
var decode = jsonDecode(res.body);
token = decode['access_token'];
}
return token;
}
payToDriverWallet(
String token, String amount, String issuer, String msisdn) async {
var headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
var body = json.encode({
"amount": amount, //"10.00",
"issuer": issuer, //"vodafone",
"msisdn": msisdn, // "01023456789"
});
var res = await http.post(
Uri.parse('https://payouts.paymobsolutions.com/api/secure/disburse/'),
headers: headers,
body: body,
);
var dec = jsonDecode(res.body);
if (dec['disbursement_status'] == 'successful') {
await CRUD().post(link: AppLink.addDriverpayment, payload: {
'rideId': DateTime.now().toIso8601String(),
'amount': ((-1) * (double.parse(dec['amount'])) + 5).toStringAsFixed(0),
'payment_method': 'payout',
'passengerID': 'admin',
'driverID': box.read(BoxName.driverID).toString(),
});
Get.snackbar('Transaction successful'.tr,
'${'Transaction successful'.tr} ${dec['amount']}',
backgroundColor: AppColor.greenColor);
// Get.find<CaptainWalletController>().getCaptainWalletFromRide();
} else if (dec['disbursement_status'] == 'failed') {
Get.snackbar('Transaction failed'.tr, 'Transaction failed'.tr,
backgroundColor: AppColor.redColor);
}
}
payToDriverBankAccount(String token, String amount, String bankCardNumber,
String bankCode) async {
var headers = {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
};
var body = {
"issuer": "bank_card",
"amount": amount, //9.0,
"full_name":
'${box.read(BoxName.nameDriver)} ${box.read(BoxName.lastNameDriver)}',
"bank_card_number": bankCardNumber, //"1111-2222-3333-4444",
"bank_code": bankCode, //"CIB",
"bank_transaction_type": "cash_transfer"
};
var res = await http
.post(
Uri.parse('https://payouts.paymobsolutions.com/api/secure/disburse/'),
headers: headers,
body: body,
)
.then((value) {});
}
Future payToWalletDriverAll(
String amount, String issuer, String msisdn) async {
String token = await getToken();
await payToDriverWallet(token, amount, issuer, msisdn);
}
Future payToBankDriverAll(
String amount, String bankCardNumber, String bankCode) async {
String token = await getToken();
await payToDriverBankAccount(token, amount, bankCardNumber, bankCode);
}
}
@@ -0,0 +1,37 @@
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class DriverController extends GetxController {
List drivers = [];
Map driverDetails = {};
// جلب السائقين pending
getDriversPending() async {
var res = await CRUD().post(
link: AppLink.getDriversPending, // رابط drivers_pending_list.php
payload: {},
);
if (res != 'failure') {
drivers = (res)['message'];
update(['drivers']); // تحديث الـ UI
} else {
Get.snackbar('Error', 'Failed to load drivers');
}
}
// جلب تفاصيل سائق واحد
getDriverDetails(String driverId) async {
var res = await CRUD().post(
link: AppLink.getDriverDetails, // رابط driver_details.php
payload: {"id": driverId},
);
if (res != 'failure') {
driverDetails = (res)['message'];
update(['driverDetails']); // تحديث صفحة التفاصيل
} else {
Get.snackbar('Error', 'Failed to load driver details');
}
}
}
@@ -0,0 +1,68 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class Driverthebest extends GetxController {
bool isLoading = false;
List driver = [];
getBestDriver() async {
var res = await CRUD().get(link: AppLink.getBestDriver, payload: {});
if (res != 'failure') {
driver = jsonDecode(res)['message'];
update();
} else {
Get.snackbar('error', '', backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}
class DriverTheBestGizaController extends GetxController {
bool isLoading = false;
List driver = [];
getBestDriver() async {
var res = await CRUD().get(link: AppLink.getBestDriverGiza, payload: {});
if (res != 'failure') {
driver = jsonDecode(res)['message'];
update();
} else {
Get.snackbar('error', '', backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}
class DriverTheBestAlexandriaController extends GetxController {
bool isLoading = false;
List driver = [];
getBestDriver() async {
var res =
await CRUD().get(link: AppLink.getBestDriverAlexandria, payload: {});
if (res != 'failure') {
driver = jsonDecode(res)['message'];
update();
} else {
Get.snackbar('error', '', backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}
@@ -0,0 +1,82 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
class EmployeeController extends GetxController {
List employee = [];
final name = TextEditingController();
final education = TextEditingController();
final site = TextEditingController();
final phone = TextEditingController();
final status = TextEditingController();
final formKey = GlobalKey<FormState>();
fetchEmployee() async {
var res = await CRUD().get(link: AppLink.getEmployee, payload: {});
if (res is String && (res == 'failure' || res == 'token_expired')) {
Get.snackbar('error', 'Failed to load employees', backgroundColor: AppColor.redColor);
return;
}
try {
var jsonData = res is String ? jsonDecode(res) : res;
employee = jsonData['message'];
update();
} catch (e) {
Get.snackbar('error', 'Invalid server response', backgroundColor: AppColor.redColor);
}
}
late String id;
String generateRandomId(int length) {
const String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
Random random = Random();
return String.fromCharCodes(Iterable.generate(
length,
(_) => chars.codeUnitAt(random.nextInt(chars.length)),
));
}
addEmployee() async {
// Create the payload with the employee data
var res = await CRUD().post(link: AppLink.addEmployee, payload: {
"id": id,
"name": name.text,
"education": education.text,
"site": site.text,
"phone": phone.text,
"status": status.text,
});
// Check the response from the API
if (res != 'failure') {
// You can handle the success case here, such as showing a success message
Get.back();
Get.snackbar('Success', 'Employee added successfully',
backgroundColor: AppColor.greenColor);
name.clear();
education.clear();
site.clear();
phone.clear();
status.clear();
fetchEmployee();
} else {
// Handle the error case by showing a snackbar with an error message
Get.snackbar('Error', 'Failed to add employee',
backgroundColor: AppColor.redColor);
}
}
@override
void onInit() {
fetchEmployee();
super.onInit();
}
}
@@ -0,0 +1,517 @@
import 'dart:convert';
import 'dart:io';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:siro_admin/constant/info.dart';
import 'package:siro_admin/env/env.dart';
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/char_map.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../../print.dart';
import '../../views/widgets/elevated_btn.dart';
import '../functions/encrypt_decrypt.dart';
import '../notification_controller.dart';
import 'local_notification.dart';
import 'token_access.dart';
class FirebaseMessagesController extends GetxController {
final fcmToken = FirebaseMessaging.instance;
List<String> tokens = [];
List<String> tokensPassengers = [];
List dataTokens = [];
List dataTokensPassenger = [];
late String driverID;
late String driverToken;
NotificationSettings? notificationSettings;
bool isLoading = false;
Future<void> getNotificationSettings() async {
// Get the current notification settings
NotificationSettings? notificationSettings =
await FirebaseMessaging.instance.getNotificationSettings();
'Notification authorization status: ${notificationSettings.authorizationStatus}';
// Call the update function if needed
update();
}
Future<void> requestFirebaseMessagingPermission() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;
// Check if the platform is Android
if (Platform.isAndroid) {
// Request permission for Android
await messaging.requestPermission();
} else if (Platform.isIOS) {
// Request permission for iOS
NotificationSettings settings = await messaging.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: true,
criticalAlert: true,
provisional: false,
sound: true,
);
messaging.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
}
}
Future getTokens() async {
var res = await http.post(
Uri.parse(AppLink.getTokens),
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
},
body: {},
);
var jsonResponse = jsonDecode(res.body);
if (jsonResponse['status'] == 'success') {
dataTokens = jsonResponse['data'];
for (var i = 0; i < dataTokens.length; i++) {
tokens.add(jsonResponse['data'][i]['token']);
}
box.write(BoxName.tokens, tokens);
} else {
Get.defaultDialog(title: "Warning", middleText: "Server Error");
}
}
var currentPage = 1;
var totalPages = 1;
Future<void> getAllTokenDrivers() async {
isLoading = true;
try {
var res = await http.post(
Uri.parse(AppLink.getDriversPhonesAndTokens),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
// 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
},
body: {
// 'page': page.toString(),
},
);
var jsonResponse = jsonDecode(res.body);
Log.print('jsonResponse: ${jsonResponse}');
if (jsonResponse['status'] == 'success') {
// var newData = jsonResponse['data'] as List;
// Log.print('newData: ${newData}');
// // if (page == 1) {
// // dataTokens.clear();
// // tokens.clear();
// // }
// dataTokens.addAll(newData);
// for (var item in newData) {
// tokens.add(item['token']);
// }
// currentPage = int.parse(jsonResponse['currentPage']);
// totalPages = jsonResponse['totalPages'];
box.write(BoxName.tokensDrivers, jsonResponse);
Log.print(
'box.write(BoxName.tokensDrivers: ${box.read(BoxName.tokensDrivers)}');
} else {
Get.defaultDialog(
title: "Warning", middleText: "No more data available");
}
} catch (e) {
Get.defaultDialog(title: "Error", middleText: "Server Error: $e");
} finally {
isLoading = false;
}
}
var currentPagePassenger = 1;
var totalPagesPassenger = 1;
Future<void> getAllTokenPassenger({int page = 1}) async {
isLoading = true;
try {
var res = await http.post(
Uri.parse(AppLink.getAllTokenPassengers),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
},
body: {},
);
var jsonResponse = jsonDecode(res.body);
if (jsonResponse['status'] == 'success') {
box.write(BoxName.tokensPassengers, jsonResponse);
Log.print(
'box.write(BoxName.tokensPassenger: ${box.read(BoxName.tokensPassengers)}');
} else {
Get.defaultDialog(
title: "Warning", middleText: "No more data available");
}
} catch (e) {
Get.defaultDialog(title: "Error", middleText: "Server Error: $e");
} finally {
isLoading = false;
}
}
bool isSendingNotifications = false;
Future<void> loadAllPagesAndSendNotifications() async {
isSendingNotifications = true;
// currentPage = 1;
// while (currentPage <= totalPages) {
// await getAllTokenDrivers(page: currentPage);
await getAllTokenDrivers();
// Log.print('tokens: ${tokens}');
await NotificationController().sendNotificationDrivers();
// print(tokens);
// if (currentPage < totalPages) {
// await Future.delayed(const Duration(seconds: 3));
// }
// currentPage++;
// }
isSendingNotifications = false;
Get.snackbar("Success", "All notifications sent!");
}
bool isSendingNotificationsPassenger = false;
Future<void> loadAllPagesAndSendNotificationsPassengers() async {
isSendingNotificationsPassenger = true;
currentPage = 1;
// while (currentPagePassenger <= totalPagesPassenger) {
await getAllTokenPassenger();
await NotificationController().sendNotificationPassengers();
// print(tokensPassengers);
// if (currentPagePassenger < totalPagesPassenger) {
// await Future.delayed(const Duration(seconds: 3));
// }
// currentPagePassenger++;
// }
isSendingNotificationsPassenger = false;
Get.snackbar("Success", "All notifications sent!");
}
Future getAllTokenPassengers() async {
var res = await http.post(
Uri.parse(AppLink.getAllTokenPassengers),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Bearer ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'
// 'Authorization': 'Bearer ${box.read(BoxName.jwt)}'
},
body: {},
);
var jsonResponse = jsonDecode(res.body);
if (jsonResponse['status'] == 'success') {
dataTokens = jsonResponse['data'];
for (var i = 0; i < dataTokens.length; i++) {
tokensPassengers.add(jsonResponse['data'][i]['token']);
}
box.write(BoxName.tokensPassengers, jsonResponse['data']);
} else {
Get.defaultDialog(title: "Warning", middleText: "Server Error");
}
}
Future getToken() async {
fcmToken.getToken().then((token) {
if (box.read(BoxName.email) == null) {
box.write(BoxName.tokenDriver, token);
} else {
box.write(BoxName.tokenFCM, token);
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
// If the app is in the background or terminated, show a system tray message
RemoteNotification? notification = message.notification;
AndroidNotification? android = notification?.android;
// if (notification != null && android != null) {
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
}
void fireBaseTitles(RemoteMessage message) {
if (message.notification!.title! == 'Order') {
if (Platform.isAndroid) {
// NotificationController().showNotification('Order', '', 'order');
}
var myListString = message.data['DriverList'];
// var points = message.data['PolylineJson'];
var myList = jsonDecode(myListString) as List<dynamic>;
// var myPoints = jsonDecode(points) as List<dynamic>;
driverToken = myList[14].toString();
// This is for location using and uploading status
update();
}
}
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> passengerDialog(String message) {
return Get.defaultDialog(
barrierDismissible: false,
title: 'message From passenger'.tr,
titleStyle: AppStyle.title,
middleTextStyle: AppStyle.title,
middleText: message.tr,
confirm: MyElevatedButton(
title: 'Ok'.tr,
onPressed: () {
// FirebaseMessagesController().sendNotificationToPassengerToken(
// 'Hi ,I will go now'.tr,
// 'I will go now'.tr,
// Get.find<MapPassengerController>().driverToken, []);
// Get.find<MapPassengerController>()
// .startTimerDriverWaitPassenger5Minute();
Get.back();
}));
}
// Future<dynamic> driverFinishTripDialoge(List<dynamic> driverList) {
// return Get.defaultDialog(
// title: 'Driver Finish Trip'.tr,
// content: const DriverTipWidget(),
// confirm: MyElevatedButton(
// title: 'Yes'.tr,
// onPressed: () async {
// var tip = (Get.find<MapPassengerController>().totalPassenger) *
// (double.parse(box.read(BoxName.tipPercentage.toString())));
// var res = await CRUD().post(link: AppLink.addTips, payload: {
// 'passengerID': box.read(BoxName.passengerID),
// 'driverID': driverList[0].toString(),
// 'rideID': driverList[1].toString(),
// 'tipAmount': tip.toString(),
// });
// await CRUD().post(link: AppLink.addPassengersWallet, payload: {
// 'passenger_id': box.read(BoxName.passengerID).toString(),
// 'balance': ((-1) * tip).toString()
// });
// await CRUD().post(link: AppLink.addDriversWalletPoints, payload: {
// 'driverID': driverList[0].toString(),
// 'paymentID': '${Get.find<MapPassengerController>().rideId}tip',
// 'amount': (tip * 100).toString(),
// 'paymentMethod': 'visa-tip',
// });
// if (res != 'failure') {
// FirebaseMessagesController().sendNotificationToAnyWithoutData(
// 'You Have Tips',
// '${'${tip.toString()}\$${' tips\nTotal is'.tr}'} ${tip + (Get.find<MapPassengerController>().totalPassenger)}',
// driverList[2].toString(),
// );
// }
// Get.to(() => RateDriverFromPassenger(), arguments: {
// 'driverId': driverList[0].toString(),
// 'rideId': driverList[1].toString(),
// 'price': driverList[3].toString()
// });
// },
// kolor: AppColor.greenColor,
// ),
// cancel: MyElevatedButton(
// title: 'No,I want'.tr,
// onPressed: () {
// Get.to(() => RateDriverFromPassenger(), arguments: {
// 'driverId': driverList[0].toString(),
// 'rideId': driverList[1].toString(),
// 'price': driverList[3].toString()
// });
// },
// kolor: AppColor.redColor,
// ));
// }
void sendNotificationAll(String title, body) async {
// Get the token you want to subtract.
String token = box.read(BoxName.tokenFCM);
tokens = box.read(BoxName.tokens);
// Subtract the token from the list of tokens.
tokens.remove(token);
// Save the list of tokens back to the box.
// box.write(BoxName.tokens, tokens);
tokens = box.read(BoxName.tokens);
for (var i = 0; i < tokens.length; i++) {
String serviceAccountKeyJson = '''{
"type": "service_account",
"project_id": "ride-b1bd8",
"private_key_id": "75e817c0b902db2ef35edf2c2bd159dec1f13249",
"private_key": "-----BEGIN PRIVATE KEY-----\\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD0zH9TQGDQHUv3\\na3/JAD1UKPwAp3wNKT0a6fxiIzjI3JxQWI30QvZCcfl6CdMhIcydX1ncSaYTcEeC\\n/AdPVCPkqyJx1YIGGg6P/mRzCWeaN8fsp6z250m5vcObDCZc3dbJEkepbep+6FPY\\n21m3KO+AHh1glgsTGZOTm5xiU8NGXpdk2QEh8wpiIIlR/HuKwVw9g8urNe3Sno+U\\nDm3z37iFqvZdmpqO8aWTJu6beb3hsREK9XK2I9JqC2JUwiGQRo3idOvPP6hkqrWx\\nKSX96vglQFYfakvJdDp2ZATOlpBYPMtS/IWhJ985u58TSS+Kl8qpnpaZBSxgJirf\\nhWzhnKLfAgMBAAECggEAJP785SePGhS7ZN6ltspm+l+hSjYFrPWFCxq+rlQ1YkHZ\\nC9l+RqKSFhOkiPmQI2s4wbXl3kFxLHHlFNoi/q2wKQBmGb8TQfnRJpjjNHGA61Ev\\n0Ue7/6qPvVb9B2MsLw/FxKiTFPuMG3bgKR9pbSFuJLYoaW7zqITOhVnYphGTqwAY\\nBVVcvISSLvELDmH9VZcv/9DVqVlqbbESHWh1Z4W6XGPoEqeDH/upNTyQQ/46Msgm\\nTGE6VqLHpWuSf6SqHp+r0Y0lI3vIPM1vz5FAJDJbOE/enHa0fSup0OHSMxl0HVMn\\nnO1yrGF3vsIPOej5HKr5d71bEIckzk73/yjNC1/mDQKBgQD7RtUvc9omsSsFMJ6e\\nBASAn6Dktx/QY/XNJjFzHQj69cywLDe5t5AL2gUi3phQ2oqB5XJdwnd5bTIEPEPZ\\nDOuOai2802p6FJk6kjmZAMVGx5JtXBH+vs6jrmQQSMiKbjwN1TT6xIWakvLOonUi\\nX6ZvjYYjU/E0YJU3jSiXWEr76wKBgQD5Zn4SouJ6BCDZMbausJVMBkk3qxsYooip\\np89WakC6e7AZinpkRcqjGGV9GOvc8crJs6fyXAA9ORepGP47Mc0ZrDssOkstznsM\\npr8R0S6MKwEZaT9ixOHdOcLZ47ps+JzA2Wr4KN2OvFHksUkB/46ATD1j9WZVgB8M\\namsYp/Y73QKBgHOo+PvsoZ9psVmkNX6abtAdqdtdB0HOoRea2uwXk0ig12TIFaZg\\nfedWpUKVnxqoXVTJHklV99RmlL0qWDiSH+LfsMnXro0e6iDxqZ1po2Se/CFmXcoa\\nXdctsFVmixhdATuExewfhTfPKABA+xWlXWC/jdy5CK+JPWXijaqMM4edAoGAE5Bj\\nsWiPpYyvWvpYX0nA3G7dzX0hqgQN/mkIjbnWDArp3IcNZNJIvBSM2Yxb7EAXbU0n\\njo6DAkp5Pa2VO+WDNlFZbvW/sf8xjeOCt44WPa6d7nVgIIpbQXRngZoopKW3/jTP\\n/FmQT8McFXmGxZ5belsAsdetSGW9icbLUerTGQ0CgYEAmf/G8Ag3XxmqTXvvHuv2\\n14OP7WnrVqkEMnydrftEwn4peXd/Lz+/GYX5Zc4ZoNgbN8IvZ5z0+OmRsallsbiW\\nBw0/tc68CjzxXOvReWxDluUopqWVGj5tlGqE5xUDku9SWJSxbkiQ3rqutzBdPXpr\\noqHwPyDrmK/Zgqn+uiIm4Ck=\\n-----END PRIVATE KEY-----\\n",
"client_email": "firebase-adminsdk-o2wqi@ride-b1bd8.iam.gserviceaccount.com",
"client_id": "111210077025005706623",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-o2wqi%40ride-b1bd8.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
'''; // As defined above
// Initialize AccessTokenManager
final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// Obtain an OAuth 2.0 access token
final accessToken = await accessTokenManager.getAccessToken();
// Send the notification
final response = await http
.post(
Uri.parse(
'https://fcm.googleapis.com/v1/projects/ride-b1bd8/messages:send'),
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken',
},
body: jsonEncode({
'notification': <String, dynamic>{
'title': title,
'body': body,
'sound': 'ding.wav'
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done'
},
'to': tokens[i],
}))
.whenComplete(() {})
.catchError((e) {});
}
}
// for (var i = 0; i < tokens.length; i++) {
// http
// .post(Uri.parse('https://fcm.googleapis.com/fcm/send'),
// headers: <String, String>{
// 'Content-Type': 'application/json',
// 'Authorization': 'key=${storage.read(key: BoxName.serverAPI}'
// },
// body: jsonEncode({
// 'notification': <String, dynamic>{
// 'title': title,
// 'body': body,
// 'sound': 'true'
// },
// 'priority': 'high',
// 'data': <String, dynamic>{
// 'click_action': 'FLUTTER_NOTIFICATION_CLICK',
// 'id': '1',
// 'status': 'done'
// },
// 'to': tokens[i],
// }))
// .whenComplete(() {})
// .catchError((e) {
// });
// }
// }
//android/app/src/main/res/raw/iphone_ringtone.wav
late String serviceAccountKeyJson;
void sendNotificationToAnyWithoutData(
String title, String body, String token, String tone) async {
try {
var encryptedKey = Env.privateKeyFCM;
// Log.print('encryptedKey: ${encryptedKey}');
serviceAccountKeyJson =
EncryptionHelper.instance.decryptData(encryptedKey);
// As defined above
// Initialize AccessTokenManager
final accessTokenManager = AccessTokenManager(serviceAccountKeyJson);
// Obtain an OAuth 2.0 access token
final accessToken = await accessTokenManager.getAccessToken();
// Log.print('accessToken: ${accessToken}');
// Send the notification
final response = await http.post(
Uri.parse(
'https://fcm.googleapis.com/v1/projects/intaleq-d48a7/messages:send'),
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'Bearer $accessToken',
},
body: jsonEncode({
'message': {
'token': token,
'notification': {
'title': title,
'body': body,
},
'android': {
'notification': {
'sound': tone,
},
},
'apns': {
'payload': {
'aps': {
'sound': tone,
},
},
},
},
}),
);
if (response.statusCode == 200) {
SnackBar(content: Text('${response.statusCode}'));
print(
'Notification sent successfully. Status code: ${response.statusCode}');
print('Response body: ${response.body}');
} else {
print(
'Failed to send notification. Status code: ${response.statusCode}');
print('Response body: ${response.body}');
}
} catch (e) {
print('Error sending notification: $e');
}
}
}
@@ -0,0 +1,29 @@
// import 'package:flutter_local_notifications/flutter_local_notifications.dart';
// import 'package:get/get.dart';
// class NotificationController extends GetxController {
// final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
// FlutterLocalNotificationsPlugin();
// // Initializes the local notifications plugin
// Future<void> initNotifications() async {
// const AndroidInitializationSettings android =
// AndroidInitializationSettings('@mipmap/launcher_icon');
// const InitializationSettings initializationSettings =
// InitializationSettings(android: android);
// await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
// }
// // Displays a notification with the given title and message
// void showNotification(String title, String message, String tone) async {
// AndroidNotificationDetails android = AndroidNotificationDetails(
// 'your channel id', 'your channel name',
// importance: Importance.max,
// priority: Priority.high,
// showWhen: false,
// sound: RawResourceAndroidNotificationSound(tone));
// NotificationDetails details = NotificationDetails(android: android);
// await _flutterLocalNotificationsPlugin.show(0, title, message, details);
// }
// }
@@ -0,0 +1,59 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../print.dart';
class NotificationService {
// تأكد من أن هذا هو الرابط الصحيح لملف الإرسال
static const String _serverUrl =
'https://syria.intaleq.xyz/intaleq/fcm/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 {
final Map<String, dynamic> payload = {
'target': target,
'title': title,
'body': body,
'isTopic': isTopic,
};
if (category != null) {
payload['category'] = category; // <-- [الإضافة الثانية]
}
if (tone != null) {
payload['tone'] = tone;
}
if (driverList != null) {
// [مهم] تطبيق السائق يرسل passengerList
payload['passengerList'] = jsonEncode(driverList);
}
final response = await http.post(
Uri.parse(_serverUrl),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(payload),
);
if (response.statusCode == 200) {
print('✅ Notification sent successfully.');
} else {
print(
'❌ Failed to send notification. Status code: ${response.statusCode}');
}
} catch (e) {
print('❌ An error occurred while sending notification: $e');
}
}
}
@@ -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');
}
}
}
@@ -0,0 +1,808 @@
import 'dart:convert';
import 'dart:io';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/char_map.dart';
import '../../constant/info.dart';
import '../../constant/links.dart';
import '../../env/env.dart';
import '../../main.dart';
import '../../print.dart';
import 'device_info.dart';
import 'encrypt_decrypt.dart';
import 'security_checks.dart';
class CRUD {
var dev = '';
getJWT() async {
// إذا كان الأدمن مسجل دخوله بالفعل، لا تقم بتوليد توكن "ضيف" قديم
if (box.read(BoxName.driverID) != null) {
Log.print('Admin session active. Skipping guest JWT.');
return;
}
dev = Platform.isAndroid ? 'android' : 'ios';
var payload = {
'id': 'admin',
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
};
Log.print('payload: ${payload}');
var response1 = await http.post(
Uri.parse(AppLink.loginJwtDriver),
body: payload,
);
if (response1.statusCode == 200) {
final decodedResponse1 = jsonDecode(response1.body);
final jwt = decodedResponse1['jwt'];
Log.print('jwt: ${jwt}');
await box.write(BoxName.jwt, X.c(X.c(X.c(jwt, cn), cC), cs));
// await AppInitializer().getKey();
}
}
Future<dynamic> get({
required String link,
Map<String, dynamic>? payload,
}) async {
String token = '';
var rawJwt = box.read(BoxName.jwt);
if (rawJwt == null) {
await getJWT();
rawJwt = box.read(BoxName.jwt);
}
if (rawJwt != null) {
token = r(rawJwt.toString()).split(AppInformation.addd)[0];
try {
if (JwtDecoder.isExpired(token)) {
// If we have an admin ID, we should probably re-login or refresh,
// but for now let's just fall back to guest JWT if needed.
if (box.read(BoxName.driverID) == null) {
await getJWT();
token = r(box.read(BoxName.jwt).toString())
.split(AppInformation.addd)[0];
}
}
} catch (e) {
print('❌ JWT Decode Error: $e');
// If decryption failed, try using raw (maybe it was saved plain)
token = rawJwt.toString().split(AppInformation.addd)[0];
}
}
var url = Uri.parse(link);
Log.print('--- [CRUD GET] ---');
Log.print('URL: $link');
Log.print('Payload: $payload');
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Bearer $token',
'X-Device-FP': box.read(BoxName.fingerPrint) ?? '',
},
);
Log.print('Status Code: ${response.statusCode}');
Log.print('Response Body: ${response.body}');
Log.print('------------------');
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return response.body;
}
return jsonData['status'] ?? 'failure';
} catch (e) {
return 'failure';
}
} else if (response.statusCode == 401) {
return 'token_expired';
} else {
return 'failure';
}
}
Future<dynamic> post(
{required String link, Map<String, dynamic>? payload}) async {
var url = Uri.parse(link);
try {
String token = '';
var rawJwt = box.read(BoxName.jwt);
if (rawJwt != null) {
token = r(rawJwt.toString()).split(AppInformation.addd)[0];
try {
if (JwtDecoder.isExpired(token)) {
if (box.read(BoxName.driverID) == null) {
await getJWT();
token = r(box.read(BoxName.jwt).toString())
.split(AppInformation.addd)[0];
}
}
} catch (e) {
token = rawJwt.toString().split(AppInformation.addd)[0];
}
}
Log.print('--- [CRUD POST] ---');
Log.print('URL: $link');
Log.print('Payload: $payload');
var response = await http.post(
url,
body: payload,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization': 'Bearer $token',
'X-Device-FP': box.read(BoxName.fingerPrint) ?? '',
},
);
Log.print('Status Code: ${response.statusCode}');
Log.print('Response Body: ${response.body}');
Log.print('-------------------');
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return jsonData;
} else {
return jsonData['status'];
}
} catch (e) {
return 'failure';
}
} else if (response.statusCode == 401) {
return 'token_expired';
} else {
return 'failure';
}
} catch (e) {
// addError('HTTP request error: $e', 'crud().post - HTTP');
return 'failure';
}
}
getJwtWallet() async {
// 1. فحص التوكن المخزن أولاً (Caching) لتقليل طلبات الـ SSO
var cachedWalletJwt = box.read('wallet_jwt');
var cachedWalletExpiry = box.read('wallet_jwt_expiry');
if (cachedWalletJwt != null && cachedWalletExpiry != null) {
int expiryMs = int.tryParse(cachedWalletExpiry.toString()) ?? 0;
// إذا لم ينته بعد (مع هامش أمان 60 ثانية)
if (DateTime.now().millisecondsSinceEpoch < expiryMs - 60000) {
Log.print(
'Using cached Wallet JWT. Expiry: ${DateTime.fromMillisecondsSinceEpoch(expiryMs)}');
return cachedWalletJwt.toString();
}
}
String fingerPrint = await DeviceHelper.getDeviceFingerprint();
final mainTokenEnc = box.read(BoxName.jwt);
if (mainTokenEnc == null) return null;
String mainToken = mainTokenEnc.toString();
// فك تشفير التوكن بنفس طريقة دالة get() التي تعمل بنجاح
try {
mainToken = r(mainTokenEnc.toString()).split(AppInformation.addd)[0];
} catch (e) {
// إذا فشل فك التشفير، نستخدم القيمة كما هي (ربما مخزنة بدون تشفير)
mainToken = mainTokenEnc.toString().split(AppInformation.addd)[0];
}
Log.print('Wallet SSO mainToken length: ${mainToken.length}');
Log.print(
'Wallet SSO token starts with: ${mainToken.substring(0, mainToken.length > 10 ? 10 : mainToken.length)}');
// استخدام الـ SSO للسيرفر الرئيسي إذا كان الأدمن مسجل دخوله
var response1 = await http.post(
Uri.parse(AppLink.loginWalletAdminV3),
headers: {
'Authorization': 'Bearer $mainToken',
'X-Device-FP': fingerPrint,
},
);
Log.print('Wallet SSO login status: ${response1.statusCode}');
if (response1.statusCode == 200) {
final decoded = jsonDecode(response1.body);
final msg = decoded['message'];
String? jwt;
String? hmac;
if (msg is Map) {
jwt = msg['jwt'];
hmac = msg['hmac'];
} else {
jwt = decoded['jwt'];
hmac = decoded['hmac'];
}
if (hmac != null) await box.write(BoxName.hmac, hmac);
// تخزين التوكن الجديد في الكاش لمدة 10 دقائق (600 ثانية)
if (jwt != null) {
await box.write('wallet_jwt', jwt);
await box.write('wallet_jwt_expiry',
(DateTime.now().millisecondsSinceEpoch + (600 * 1000)).toString());
Log.print('Wallet JWT cached successfully.');
}
return jwt?.toString();
} else {
// Fallback: المحاولة بالطريقة القديمة إذا فشل الـ SSO
var payload = {
'id': box.read(BoxName.driverID) ?? '1',
'password': AK.passnpassenger,
'aud': '${Env.allowedWallet}${Platform.isAndroid ? 'android' : 'ios'}',
'fingerPrint': fingerPrint
};
var fallbackRes = await http.post(
Uri.parse(AppLink.loginWalletAdmin),
body: payload,
);
if (fallbackRes.statusCode == 200) {
final decoded = jsonDecode(fallbackRes.body);
if (decoded['jwt'] != null) return decoded['jwt'].toString();
}
}
return null;
}
Future<dynamic> getWallet({
required String link,
Map<String, dynamic>? payload,
bool isRetry = false,
}) async {
var s = await getJwtWallet();
final hmac = box.read(BoxName.hmac);
var url = Uri.parse(link);
Log.print('--- getWallet Execution ---');
Log.print('URL: $url');
Log.print('JWT: $s');
Log.print('HMAC: $hmac');
Log.print('Is Retry: $isRetry');
Log.print('Payload: $payload');
if (payload != null && hmac != null) {
payload['hmac'] = hmac.toString();
}
try {
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': box.read(BoxName.fingerPrint) ?? '',
},
);
Log.print('Status Code: ${response.statusCode}');
Log.print('Response Body: ${response.body}');
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return jsonData;
}
Log.print('Logic Error: Status is not success. Data: $jsonData');
return jsonData['status'] ?? 'failure';
} catch (e) {
Log.print('JSON Decode Error in getWallet: $e');
return 'failure';
}
} else if (response.statusCode == 401 && !isRetry) {
Log.print('Token expired (401). Clearing cache and retrying...');
await box.remove('wallet_jwt');
await box.remove('wallet_jwt_expiry');
return await getWallet(link: link, payload: payload, isRetry: true);
} else {
Log.print('HTTP Error in getWallet. Status: ${response.statusCode}');
return 'failure';
}
} catch (e) {
Log.print('HTTP Request Exception in getWallet: $e');
return 'failure';
}
}
Future<dynamic> postWallet(
{required String link, Map<String, dynamic>? payload}) async {
var s = await getJwtWallet();
Log.print('jwt: ${s}');
final hmac = box.read(BoxName.hmac);
Log.print('hmac: ${hmac}');
var url = Uri.parse(link);
Log.print('url: ${url}');
// إضافة الـ HMAC للـ payload لزيادة التوافقية
if (payload != null && hmac != null) {
payload['hmac'] = hmac.toString();
}
try {
// await LoginDriverController().getJWT();
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': box.read(BoxName.fingerPrint) ?? '',
},
);
// Log.print('response.request:${response.request}');
// Log.print('response.body: ${response.body}');
// Log.print('payload:$payload');
if (response.statusCode == 200) {
try {
var jsonData = jsonDecode(response.body);
if (jsonData['status'] == 'success') {
return jsonData;
}
return jsonData['status'] ?? 'failure';
} catch (e) {
return 'failure';
}
} else if (response.statusCode == 401) {
await getJwtWallet();
return 'token_expired';
} else {
return 'failure';
}
} catch (e) {
// addError('HTTP request error: $e', 'crud().post - HTTP');
return 'failure';
}
}
// }
Future sendWhatsAppAuth(String to, message) async {
var res = await CRUD().post(
link: AppLink.send_whatsapp_message,
payload: {'receiver': to, 'message': message});
if (res != 'failure') {
Get.snackbar('Success', 'Message sent successfully');
} else {
Get.snackbar('Error', 'Failed to send message');
}
}
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://repulsive-pig-rugby-shirt.cyclic.app/token?channelName=$channelName'),
headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'});
if (res.statusCode == 200) {
var response = jsonDecode(res.body);
return response['token'];
} else {}
}
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'
// 'Authorization': 'Bearer ${Env.llamaKey}'
};
var data = json.encode({
"model": "Llama-3-70b-Inst-FW",
// "model": "llama-13b-chat",
"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, driverID, imagePath) async {
// await ImageController().choosImage(linkPHP, imagePath);
Future.delayed(const Duration(seconds: 2));
var extractedString = await arabicTextExtractByVisionAndAI(
imagePath: imagePath, driverID: driverID);
var json = jsonDecode(extractedString);
var textValues = getAllTextValuesWithLineNumbers(json);
// List<String> textValues = getAllTextValues(json);
// await AI().geminiAiExtraction(prompt, textValues);
}
Map<String, List<Map<String, String>>> getAllTextValuesWithLineNumbers(
Map json) {
Map<String, List<Map<String, String>>> output = {};
int lineNumber = 1;
if (json.containsKey('regions')) {
List<dynamic> regions = json['regions'];
for (Map<String, dynamic> region in regions) {
if (region.containsKey('lines')) {
List<dynamic> lines = region['lines'];
List<Map<String, String>> linesWithText = [];
for (Map<String, dynamic> line in lines) {
if (line.containsKey('words')) {
List<dynamic> words = line['words'];
String lineText = "";
for (Map<String, dynamic> word in words) {
if (word.containsKey('text')) {
lineText += word['text'] + " ";
}
}
lineText = lineText.trim();
linesWithText.add(
{"line_number": lineNumber.toString(), "text": lineText});
lineNumber++;
}
}
output["region_${region.hashCode}"] = linesWithText;
}
}
}
return output;
}
// List<String> getAllTextValues(Map json) {
// List<String> textValues = [];
// if (json.containsKey('regions')) {
// List<dynamic> regions = json['regions'];
// for (Map<String, dynamic> region in regions) {
// if (region.containsKey('lines')) {
// List<dynamic> lines = region['lines'];
// for (Map<String, dynamic> line in lines) {
// if (line.containsKey('words')) {
// List<dynamic> words = line['words'];
// for (Map<String, dynamic> word in words) {
// if (word.containsKey('text')) {
// textValues.add(word['text']);
// }
// }
// }
// }
// }
// }
// }
// return textValues;
// }
Future<dynamic> arabicTextExtractByVisionAndAI({
required String imagePath,
required String driverID,
}) async {
var headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': AK.ocpApimSubscriptionKey
};
String imagePathFull =
'${AppLink.server}/card_image/$imagePath-$driverID.jpg';
var request = http.Request(
'POST',
Uri.parse(
'https://eastus.api.cognitive.microsoft.com/computervision/imageanalysis:analyze?features=caption,read&model-version=latest&language=en&api-version=2024-02-01'));
request.body = json.encode({"url": imagePathFull});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
return await response.stream.bytesToString();
} else {}
}
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> kazumiSMS({
required String link,
Map<String, dynamic>? payload,
}) async {
var url = Uri.parse(
link,
);
var headers = {'Content-Type': 'application/json'};
var request = http.Request('POST', url);
request.body = json.encode({
"username": "Sefer",
"password": AK.smsPasswordEgypt,
});
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
var responseBody = await response.stream.bytesToString();
var data = json.decode(responseBody);
return data;
} else {}
}
Future<dynamic> sendSmsEgypt(String phone, otp) async {
// String sender = await getSender();
var headers = {'Content-Type': 'application/json'};
var body = jsonEncode({
"username": "Sefer",
"password": "E)Pu=an/@Z",
"message": otp,
"language": "e",
"sender": "Sefer Egy",
"receiver": phone
});
var res = await http.post(
Uri.parse(AppLink.sendSms),
body: body,
headers: headers,
);
}
Future<dynamic> postPayMob({
required String link,
Map<String, dynamic>? payload,
}) async {
// String? basicAuthCredentials =
// await storage.read(key: BoxName.basicAuthCredentials);
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;
} else {
return (jsonData['status']);
}
} else {
return response.statusCode;
}
}
sendEmail(
String link,
Map<String, String>? payload,
) async {
var headers = {
"Content-Type": "application/x-www-form-urlencoded",
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
};
var request = http.Request('POST', Uri.parse(link));
request.bodyFields = payload!;
request.headers.addAll(headers);
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
} else {}
}
Future<dynamic> postFromDialogue({
required String link,
Map<String, dynamic>? payload,
}) async {
// String? basicAuthCredentials =
// await storage.read(key: BoxName.basicAuthCredentials);
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) {
if (jsonData['status'] == 'success') {
Get.back();
// Get.snackbar(
// jsonData['status'],
// jsonData['message'],
// );
return response.body;
}
}
return (jsonData['status']);
}
}
Future<void> sendVerificationRequest(String phoneNumber) async {
final accountSid = AK.accountSIDTwillo;
final authToken = AK.authTokenTwillo;
final verifySid = AK.twilloRecoveryCode;
final Uri verificationUri = Uri.parse(
'https://verify.twilio.com/v2/Services/$verifySid/Verifications');
// Send the verification request
final response = await http.post(
verificationUri,
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode('$accountSid:$authToken'))}',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
'To': phoneNumber,
'Channel': 'sms',
},
);
if (response.statusCode == 201) {
} else {}
// Prompt the user to enter the OTP
const otpCode = "123456"; // Replace with user input
// Check the verification code
final checkUri = Uri.parse(
'https://verify.twilio.com/v2/Services/$verifySid/VerificationCheck');
final checkResponse = await http.post(
checkUri,
headers: {
'Authorization':
'Basic ${base64Encode(utf8.encode('$accountSid:$authToken'))}',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
'To': phoneNumber,
'Code': otpCode,
},
);
if (checkResponse.statusCode == 201) {
} else {}
}
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 {
// String? basicAuthCredentials =
// await storage.read(key: BoxName.basicAuthCredentials);
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 {
// String? basicAuthCredentials =
// await storage.read(key: BoxName.basicAuthCredentials);
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);
}
List phoneDriversTest = [
201023248456,
201023248456,
];
}
@@ -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;
}
@@ -0,0 +1,291 @@
// import 'dart:io';
// import 'package:device_info_plus/device_info_plus.dart';
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
class DeviceHelper {
static Future<String> getDeviceFingerprint() async {
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
var deviceData;
try {
if (Platform.isAndroid) {
// Fetch Android-specific device information
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
deviceData = androidInfo.toMap(); // Convert to a map for easier access
// Log.print('deviceData: ${jsonEncode(deviceData)}');
} else if (Platform.isIOS) {
// Fetch iOS-specific device information
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
deviceData = iosInfo.toMap(); // Convert to a map for easier access
} else if (Platform.isMacOS) {
// Fetch macOS-specific device information
MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo;
deviceData = macInfo.toMap();
} else {
throw UnsupportedError('Unsupported platform');
}
String deviceId = 'unknown';
if (Platform.isAndroid) {
deviceId = deviceData['fingerprint'] ?? 'unknown';
} else if (Platform.isIOS) {
deviceId = deviceData['identifierForVendor'] ?? 'unknown';
} else if (Platform.isMacOS) {
deviceId = deviceData['systemGUID'] ?? 'unknown';
}
final String deviceModel = deviceData['model'] ?? 'unknown';
final String fingerprint = '${deviceId}_$deviceModel';
print(fingerprint);
return (fingerprint);
} catch (e) {
throw Exception('Failed to generate device fingerprint');
}
}
}
// 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;
// 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) {
// // Pass the CORRECT bundle ID to isTampered
// 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();
// }
// }
// /// 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');
// }
// }
// class DeviceInfoPlus {
// static List<Map<String, dynamic>> deviceDataList = [];
// static Future<List<Map<String, dynamic>>> getDeviceInfo() async {
// final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
// try {
// if (Platform.isAndroid) {
// AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'Android',
// 'brand': androidInfo.brand,
// 'model': androidInfo.model,
// 'androidId': androidInfo.device,
// 'versionRelease': androidInfo.version.release,
// 'sdkVersion': androidInfo.version.sdkInt,
// 'manufacturer': androidInfo.manufacturer,
// 'isPhysicalDevice': androidInfo.isPhysicalDevice,
// 'serialNumber': androidInfo.serialNumber,
// 'fingerprint': androidInfo.fingerprint,
// 'type': androidInfo.type,
// 'data': androidInfo.data,
// 'version': androidInfo.version,
// 'tags': androidInfo.tags,
// 'display': androidInfo.display,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isIOS) {
// IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
// Map<String, dynamic> deviceData = {
// 'brand': 'Apple',
// 'model': iosInfo.model,
// 'systemName': iosInfo.systemName,
// 'systemVersion': iosInfo.systemVersion,
// 'utsname': iosInfo.utsname,
// 'isPhysicalDevice': iosInfo.isPhysicalDevice,
// 'identifierForVendor': iosInfo.identifierForVendor,
// 'name': iosInfo.name,
// 'localizedModel': iosInfo.localizedModel,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isMacOS) {
// MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'macOS',
// 'model': macInfo.model,
// 'version': macInfo.systemGUID,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isWindows) {
// WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'Windows',
// 'manufacturer': windowsInfo.computerName,
// 'version': windowsInfo.majorVersion,
// 'deviceId': windowsInfo.deviceId,
// 'userName': windowsInfo.userName,
// 'productName': windowsInfo.productName,
// 'installDate': windowsInfo.installDate,
// 'productId': windowsInfo.productId,
// 'numberOfCores': windowsInfo.numberOfCores,
// 'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
// };
// deviceDataList.add(deviceData);
// } else if (Platform.isLinux) {
// LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo;
// Map<String, dynamic> deviceData = {
// 'platform': 'Linux',
// 'manufacturer': linuxInfo.name,
// 'version': linuxInfo.version,
// };
// deviceDataList.add(deviceData);
// }
// } catch (e) {
// }
// return deviceDataList;
// }
// // Method to print all device data
// static void printDeviceInfo() {
// for (Map<String, dynamic> deviceData in deviceDataList) {
// 'Version: ${deviceData['version'] ?? deviceData['versionRelease'] ?? 'N/A'}');
// }
// }
// }
@@ -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);
}
}
@@ -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;
// }
// }
@@ -0,0 +1,79 @@
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];
// Log.print('keyOfApp: ${keyOfApp}');
var initializationVector =
r(Env.initializationVector).toString().split(Env.addd)[0];
// Log.print('initializationVector: ${initializationVector}');
// 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();
}
@@ -0,0 +1,829 @@
// import 'dart:convert';
// import 'dart:io';
// import 'package:get/get.dart';
// import 'package:image_cropper/image_cropper.dart';
// import 'package:image_picker/image_picker.dart';
// import 'package:http/http.dart' as http;
// import 'package:image/image.dart' as img;
// import 'package:path_provider/path_provider.dart';
// import '../../constant/api_key.dart';
// import '../../constant/colors.dart';
// class AI extends GetxController {
// final picker = ImagePicker();
// Map<String, dynamic> responseMap = {};
// Map<String, dynamic> responseCarLicenseMap = {};
// Map<String, dynamic> responseBackCarLicenseMap = {};
// Map<String, dynamic> responseIdCardeMap = {};
// bool isloading = false;
// var image;
// CroppedFile? croppedFile;
// DateTime now = DateTime.now();
// Future<void> pickImage() async {
// final pickedImage = await picker.pickImage(source: ImageSource.gallery);
// if (pickedImage != null) {
// image = File(pickedImage.path);
// // Crop the image
// croppedFile = await ImageCropper().cropImage(
// sourcePath: image!.path,
// aspectRatioPresets: [
// CropAspectRatioPreset.square,
// CropAspectRatioPreset.ratio3x2,
// CropAspectRatioPreset.original,
// CropAspectRatioPreset.ratio4x3,
// CropAspectRatioPreset.ratio16x9
// ],
// uiSettings: [
// AndroidUiSettings(
// toolbarTitle: 'Cropper'.tr,
// toolbarColor: AppColor.blueColor,
// toolbarWidgetColor: AppColor.yellowColor,
// initAspectRatio: CropAspectRatioPreset.original,
// lockAspectRatio: false),
// IOSUiSettings(
// title: 'Cropper'.tr,
// ),
// ],
// );
// // image = croppedFile;
// // Resize the image
// final rawImage =
// img.decodeImage(File(croppedFile!.path).readAsBytesSync());
// final resizedImage =
// img.copyResize(rawImage!, width: 800); // Adjust the width as needed
// final appDir = await getTemporaryDirectory();
// final resizedImagePath = '${appDir.path}/resized_image.jpg';
// final resizedImageFile = File(resizedImagePath);
// resizedImageFile.writeAsBytesSync(
// img.encodeJpg(resizedImage)); // Save the resized image as JPEG
// image = resizedImageFile;
// update();
// }
// }
// Future<void> generateContent() async {
// await pickImage();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text':
// 'write json for all data as first name ,last name,dob,licenseID,expiration date,issued date asdress class type ,output json type',
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 32,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// Future<void> geminiAiExtraction(String prompt, payload) async {
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// // {
// // 'inlineData': {
// // 'mimeType': 'image/jpeg',
// // 'data': imageData,
// // },
// // },
// {
// 'text':
// "Extract the desired information from the following passage as json decoded like $prompt .and look for this instruction first name in line 3or 2 ,fullname in line 4 from it written left to right but you modify it rtl,address in line 5 it written left to right but you modify it rtl and 6,dob,nationalid in the last line as just in this:\n\n$payload"
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 32,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safety_settings': [
// {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
// {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
// {
// "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
// "threshold": "BLOCK_NONE"
// },
// {
// "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
// "threshold": "BLOCK_NONE"
// },
// ]
// });
// final response = await http.post(
// Uri.parse(
// // 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?key=AIzaSyCyoLcSkDzK5_SMe00nhut56SSXWPR074w'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result = responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// } else {
// }
// // Rest of your code...
// } else {
// }
// }
// Future<void> getDriverLicenseJordanContent() async {
// await pickImage();
// isloading = true;
// update();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text':
// 'write json for all data as first name ,last name,dob,id ,expiration date,issued date asdress class type,age in years ,output json type in arabic value and stay engish key and make date format like YYYY-MM-DD , for name please extract name in arabic in Name in json plus first_name ',
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 32,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// isloading = false;
// update();
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// responseMap = jsonDecode(jsonString);
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// Future<void> getCarLicenseJordanContent() async {
// await pickImage();
// isloading = true;
// update();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text':
// '''Extract the following information from the front face of the Jordanian ID card:
// Name
// National ID number
// Gender
// Date of birth
// Output the extracted information in the following JSON format''',
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 32,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// // 'https://${API_ENDPOINT}/v1/projects/${PROJECT_ID}/locations/${LOCATION_ID}/publishers/google/models/${MODEL_ID}:streamGenerateContent'),
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// isloading = false;
// update();
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// responseCarLicenseMap = jsonDecode(jsonString);
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// Future<void> jordanID() async {
// await pickImage();
// isloading = true;
// update();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text':
// '''Extract the following information from the front face of the Jordanian ID card:
// Name
// National ID number
// Gender
// Date of birth
// Output the extracted information in the following JSON format''',
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 32,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// isloading = false;
// update();
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// responseCarLicenseMap = jsonDecode(jsonString);
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// Future<void> carLicenseJordan() async {
// await pickImage();
// isloading = true;
// update();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text':
// '''Extract the following information from the front face of the car license card in Jordan:
// * name
// * Address
// * Vehicle type
// * car_kind
// * car_color
// * Vehicle category
// * car_year
// * car_plate
// * Registration type
// * Usage type
// * expire_date_of_license
// Output the extracted information in the following JSON formate and make date format like YYYY-MM-DD''',
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 32,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.0-pro-vision-latest:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// isloading = false;
// update();
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// responseCarLicenseMap = jsonDecode(jsonString);
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// Future getTextFromCard(String prompt) async {
// await pickImage();
// isloading = true;
// update();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text': prompt,
// },
// ],
// },
// ],
// 'generationConfig': {
// "temperature": 1,
// "topK": 32,
// "topP": 0.1,
// "maxOutputTokens": 4096,
// "stopSequences": []
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.0-pro-vision-latest:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// isloading = false;
// update();
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// responseBackCarLicenseMap = jsonDecode(jsonString);
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// Future<void> generateBackCarLicenseJordanContent() async {
// await pickImage();
// isloading = true;
// update();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text':
// 'write json output from extracting car license back face for these key ,vin,fuelType,passengerType,curbWeight,insuranceCompany,policyNumber,notes,insuranceType and output it json .dont add data else this image',
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 343,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// isloading = false;
// update();
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// responseBackCarLicenseMap = jsonDecode(jsonString);
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// Future<void> getFromCarRegistration() async {
// await pickImage();
// if (image != null) {
// final imageBytes = await image.readAsBytes();
// final imageData = base64Encode(imageBytes);
// var requestBody = jsonEncode({
// 'contents': [
// {
// 'parts': [
// {
// 'inlineData': {
// 'mimeType': 'image/jpeg',
// 'data': imageData,
// },
// },
// {
// 'text':
// 'write output json from image for[ vin, make, model, year, expiration_date, color, owner, registration_date ],output json type ',
// },
// ],
// },
// ],
// 'generationConfig': {
// 'temperature': 0.4,
// 'topK': 32,
// 'topP': 1,
// 'maxOutputTokens': 4096,
// 'stopSequences': [],
// },
// 'safetySettings': [
// {
// 'category': 'HARM_CATEGORY_HARASSMENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_HATE_SPEECH',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// {
// 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
// 'threshold': 'BLOCK_MEDIUM_AND_ABOVE',
// },
// ],
// });
// final response = await http.post(
// Uri.parse(
// 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro-vision:generateContent?key=${AK.geminiApi}'),
// headers: {'Content-Type': 'application/json'},
// body: requestBody,
// );
// if (response.statusCode == 200) {
// var responseData = jsonDecode(response.body);
// // Process the responseData as needed
// var result =
// responseData['candidates'][0]['content']['parts'][0]['text'];
// RegExp regex = RegExp(r"```json([^`]*)```");
// String? jsonString =
// regex.firstMatch(responseData.toString())?.group(1)?.trim();
// if (jsonString != null) {
// // Convert the JSON object to a String
// jsonString = jsonEncode(json.decode(jsonString));
// } else {
// }
// // Rest of your code...
// } else {
// }
// } else {
// }
// }
// @override
// void onInit() {
// // generateContent();
// super.onInit();
// }
// }
@@ -0,0 +1,71 @@
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
void showInBrowser(String url) async {
if (await canLaunchUrl(Uri.parse(url))) {
launchUrl(Uri.parse(url));
} else {}
}
Future<void> makePhoneCall(String phoneNumber) async {
final Uri launchUri = Uri(
scheme: 'tel',
path: phoneNumber,
);
await launchUrl(launchUri);
}
void launchCommunication(
String method, String contactInfo, String message) async {
String url;
if (Platform.isIOS) {
switch (method) {
case 'phone':
url = 'tel:$contactInfo';
break;
case 'sms':
url = 'sms:$contactInfo?body=$message';
break;
case 'whatsapp':
url = 'https://api.whatsapp.com/send?phone=$contactInfo&text=$message';
break;
case 'email':
url = 'mailto:$contactInfo?subject=Subject&body=$message';
break;
default:
return;
}
} else if (Platform.isAndroid) {
switch (method) {
case 'phone':
url = 'tel:$contactInfo';
break;
case 'sms':
url = 'sms:$contactInfo?body=$message';
break;
case 'whatsapp':
url = 'whatsapp://send?phone=$contactInfo&text=$message';
break;
case 'email':
url = 'mailto:$contactInfo?subject=Subject&body=$message';
break;
default:
return;
}
} else {
return;
}
if (await canLaunchUrl(Uri.parse(url))) {
launchUrl(Uri.parse(url));
} else {}
}
@@ -0,0 +1,36 @@
import 'dart:convert';
import '../../constant/links.dart';
import 'crud.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);
}
}
@@ -0,0 +1,133 @@
// import 'dart:async';
// import 'package:get/get.dart';
// import 'package:google_maps_flutter/google_maps_flutter.dart';
// import 'package:location/location.dart';
// import '../../constant/box_name.dart';
// import '../../constant/links.dart';
// import '../../main.dart';
// import 'crud.dart';
// // LocationController.dart
// class LocationController extends GetxController {
// LocationData? _currentLocation;
// late Location location;
// bool isLoading = false;
// late double heading = 0;
// late double accuracy = 0;
// late double previousTime = 0;
// late double latitude;
// late double totalDistance = 0;
// late double longitude;
// late DateTime time;
// late double speed = 0;
// late double speedAccuracy = 0;
// late double headingAccuracy = 0;
// bool isActive = false;
// late LatLng myLocation;
// String totalPoints = '0';
// LocationData? get currentLocation => _currentLocation;
// Timer? _locationTimer;
// @override
// void onInit() async {
// super.onInit();
// location = Location();
// getLocation();
// // startLocationUpdates();
// }
// Future<void> startLocationUpdates() async {
// if (box.read(BoxName.driverID) != null) {
// _locationTimer =
// Timer.periodic(const Duration(seconds: 5), (timer) async {
// try {
// // if (isActive) {
// if (double.parse(totalPoints) > -300) {
// await getLocation();
// // if (box.read(BoxName.driverID) != null) {
// await CRUD()
// .post(link: AppLink.addCarsLocationByPassenger, payload: {
// 'driver_id': box.read(BoxName.driverID).toString(),
// 'latitude': myLocation.latitude.toString(),
// 'longitude': myLocation.longitude.toString(),
// 'heading': heading.toString(),
// 'speed': (speed * 3.6).toStringAsFixed(1),
// 'distance': totalDistance == 0
// ? '0'
// : totalDistance < 1
// ? totalDistance.toStringAsFixed(3)
// : totalDistance.toStringAsFixed(1),
// 'status': box.read(BoxName.statusDriverLocation).toString()
// });
// }
// } catch (e) {
// // Handle the error gracefully
// }
// });
// }
// }
// void stopLocationUpdates() {
// _locationTimer?.cancel();
// }
// Future<void> getLocation() async {
// // isLoading = true;
// // update();
// 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
// return;
// }
// }
// // Configure location accuracy
// // LocationAccuracy desiredAccuracy = LocationAccuracy.high;
// // Get the current location
// LocationData _locationData = await location.getLocation();
// myLocation =
// (_locationData.latitude != null && _locationData.longitude != null
// ? LatLng(_locationData.latitude!, _locationData.longitude!)
// : null)!;
// speed = _locationData.speed!;
// heading = _locationData.heading!;
// // isLoading = false;
// update();
// }
// double calculateDistanceInKmPerHour(
// double? startTime, double? endTime, double speedInMetersPerSecond) {
// // Calculate the time difference in hours
// double timeDifferenceInHours = (endTime! - startTime!) / 1000 / 3600;
// // Convert speed to kilometers per hour
// double speedInKmPerHour = speedInMetersPerSecond * 3.6;
// // Calculate the distance in kilometers
// double distanceInKilometers = speedInKmPerHour * timeDifferenceInHours;
// return distanceInKilometers;
// }
// }
@@ -0,0 +1,16 @@
// import 'package:location/location.dart';
// import 'package:get/get.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;
// }
// }
// }
@@ -0,0 +1,181 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../../views/widgets/elevated_btn.dart';
import '../../views/widgets/my_textField.dart';
import 'crud.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)}).then((value) => exit(0));
}
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: () {
if (checkTxtController.text == box.read(BoxName.nameDriver)) {
deletecaptainAccount();
}
}));
}
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: () {
// box.remove(BoxName.agreeTerms);
box.remove(BoxName.driverID);
box.remove(BoxName.email);
box.remove(BoxName.lang);
box.remove(BoxName.name);
box.remove(BoxName.passengerID);
box.remove(BoxName.phone);
box.remove(BoxName.tokenFCM);
box.remove(BoxName.tokens);
box.remove(BoxName.addHome);
box.remove(BoxName.addWork);
box.remove(BoxName.agreeTerms);
box.remove(BoxName.apiKeyRun);
box.remove(BoxName.countryCode);
box.remove(BoxName.passengerWalletTotal);
Get.offAll(const MainApp());
},
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: () {
// box.remove(BoxName.agreeTerms);
box.remove(BoxName.driverID);
box.remove(BoxName.sexDriver);
box.remove(BoxName.dobDriver);
box.remove(BoxName.nameDriver);
box.remove(BoxName.emailDriver);
box.remove(BoxName.phoneDriver);
box.remove(BoxName.statusDriverLocation);
box.remove(BoxName.cvvCodeDriver);
box.remove(BoxName.lastNameDriver);
box.remove(BoxName.passwordDriver);
box.remove(BoxName.cardNumberDriver);
box.remove(BoxName.expiryDateDriver);
box.remove(BoxName.cardHolderNameDriver);
box.remove(BoxName.vin);
box.remove(BoxName.make);
box.remove(BoxName.year);
box.remove(BoxName.owner);
box.remove(BoxName.onBoarding);
box.remove(BoxName.agreeTerms);
Get.offAll(const MainApp());
},
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 {
Get.snackbar('Email Wrong'.tr, 'Email you inserted is Wrong.'.tr,
snackPosition: SnackPosition.BOTTOM,
backgroundColor: AppColor.redColor);
}
}
}
}
@@ -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();
// }
// }
@@ -0,0 +1,14 @@
import 'package:flutter_secure_storage/flutter_secure_storage.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;
}
}
+50
View File
@@ -0,0 +1,50 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class SecurityChecks {
static const platform = MethodChannel(
'com.intaleq.intaleq_admin/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 {
// Continue with normal app flow
print("Device is secure.");
}
}
}
@@ -0,0 +1,455 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter_image_compress/flutter_image_compress.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:path_provider/path_provider.dart' as path_provider;
import '../../constant/api_key.dart';
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, String id) 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}');
await uploadImage(
compressedImage,
{
'driverID': id,
'imageType': imageType,
},
link,
);
} catch (e) {
print('Error in choosImage: $e');
Get.snackbar('Image Upload Failed'.tr, e.toString(),
backgroundColor: AppColor.redColor);
} 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) {
Get.snackbar('Image Upload Failed'.tr, e.toString(),
backgroundColor: AppColor.redColor);
} finally {
isloading = false;
update();
}
}
}
uploadImage(File file, Map data, String link) async {
var request = http.MultipartRequest(
'POST',
Uri.parse(link),
);
Log.print('request: ${request}');
var length = await file.length();
var stream = http.ByteStream(file.openRead());
final headers = {
'Authorization':
'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}',
// 'X-HMAC-Auth': '${box.read(BoxName.hmac)}',
};
Log.print('headers: ${headers}');
var multipartFile = http.MultipartFile(
'image',
stream,
length,
filename: basename(file.path),
);
request.headers.addAll(headers);
// Set the file name to the driverID
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
if (res.statusCode == 200) {
Log.print('jsonDecode(res.body): ${jsonDecode(res.body)}');
if (jsonDecode(res.body)['status'] == 'Image uploaded successfully!') {
Get.snackbar('Success'.tr, 'Image uploaded successfully!'.tr,
backgroundColor: AppColor.greenColor);
}
return jsonDecode(res.body);
} else {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
choosImagePicture(String link, String imageType) async {
final pickedImage = await picker.pickImage(
source: ImageSource.gallery,
// preferredCameraDevice: CameraDevice.rear,
// maxHeight: Get.height * .3,
// maxWidth: Get.width * .9,
// imageQuality: 100,
);
image = File(pickedImage!.path);
croppedFile = await ImageCropper().cropImage(
sourcePath: image!.path,
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper'.tr,
toolbarColor: AppColor.blueColor,
toolbarWidgetColor: AppColor.yellowColor,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false),
IOSUiSettings(
title: 'Cropper'.tr,
),
],
);
myImage = File(pickedImage.path);
isloading = true;
update();
// Save the cropped image
// File savedCroppedImage = File(croppedFile!.path);
File compressedImage = await compressImage(File(croppedFile!.path));
print('link =$link');
try {
await uploadImage(
compressedImage,
{
'driverID':
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
'imageType': imageType
},
link,
);
} catch (e) {
Get.snackbar('Image Upload Failed'.tr, e.toString(),
backgroundColor: AppColor.redColor);
} finally {
isloading = false;
update();
}
}
uploadImagePicture(File file, Map data, String link) async {
var request = http.MultipartRequest(
'POST',
Uri.parse(link), //'https://ride.mobile-app.store/uploadImage1.php'
);
var length = await file.length();
var stream = http.ByteStream(file.openRead());
var multipartFile = http.MultipartFile(
'image',
stream,
length,
filename: basename(file.path),
);
request.headers.addAll({
'Authorization':
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}',
});
// Set the file name to the driverID
request.files.add(
http.MultipartFile(
'image',
stream,
length,
filename: '${box.read(BoxName.driverID)}.jpg',
),
);
data.forEach((key, value) {
request.fields[key] = value;
});
var myrequest = await request.send();
var res = await http.Response.fromStream(myrequest);
if (res.statusCode == 200) {
return jsonDecode(res.body);
} else {
throw Exception(
'Failed to upload image: ${res.statusCode} - ${res.body}');
}
}
}
Future<File> compressImage(File file) async {
final dir = await path_provider.getTemporaryDirectory();
final targetPath = "${dir.absolute.path}/temp.jpg";
var result = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
targetPath,
quality: 70,
minWidth: 1024,
minHeight: 1024,
);
return File(result!.path);
}
// 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();
// }
// }
@@ -0,0 +1,108 @@
// import 'dart:convert';
// import 'dart:io';
// import 'package:get/get.dart';
// import 'package:http/http.dart' as http;
// import 'package:image_cropper/image_cropper.dart';
// import 'package:image_picker/image_picker.dart';
// import 'package:path/path.dart';
// import '../../constant/api_key.dart';
// import '../../constant/box_name.dart';
// import '../../constant/colors.dart';
// import '../../main.dart';
// class ImageController extends GetxController {
// File? myImage;
// bool isloading = false;
// CroppedFile? croppedFile;
// final picker = ImagePicker();
// var image;
// choosImage(String link, String imageType) async {
// final pickedImage = await picker.pickImage(source: ImageSource.gallery);
// image = File(pickedImage!.path);
// croppedFile = await ImageCropper().cropImage(
// sourcePath: image!.path,
// aspectRatioPresets: [
// CropAspectRatioPreset.square,
// CropAspectRatioPreset.ratio3x2,
// CropAspectRatioPreset.original,
// CropAspectRatioPreset.ratio4x3,
// CropAspectRatioPreset.ratio16x9
// ],
// 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);
// try {
// await uploadImage(
// savedCroppedImage,
// {
// 'driverID':
// box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
// 'imageType': imageType
// },
// link,
// );
// } catch (e) {
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
// backgroundColor: AppColor.redColor);
// } finally {
// isloading = false;
// update();
// }
// }
// uploadImage(File file, Map data, String link) async {
// var request = http.MultipartRequest(
// 'POST',
// Uri.parse(link), //'https://ride.mobile-app.store/uploadImage1.php'
// );
// var length = await file.length();
// var stream = http.ByteStream(file.openRead());
// var multipartFile = http.MultipartFile(
// 'image',
// stream,
// length,
// filename: basename(file.path),
// );
// request.headers.addAll({
// 'Authorization':
// 'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials.toString()))}',
// });
// // Set the file name to the driverID
// request.files.add(
// http.MultipartFile(
// 'image',
// stream,
// length,
// filename: '${box.read(BoxName.driverID)}.jpg',
// ),
// );
// data.forEach((key, value) {
// request.fields[key] = value;
// });
// var myrequest = await request.send();
// var res = await http.Response.fromStream(myrequest);
// if (res.statusCode == 200) {
// return jsonDecode(res.body);
// } else {
// throw Exception(
// 'Failed to upload image: ${res.statusCode} - ${res.body}');
// }
// }
// }
@@ -0,0 +1,96 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import '../../constant/links.dart';
import '../firebase/firbase_messge.dart';
import 'crud.dart';
class WalletController extends GetxController {
String paymentToken = '';
Future<String> generateTokenDriver(String amount, driverID) async {
var res = await CRUD().post(link: AppLink.addPaymentTokenDriver, payload: {
'driverID': driverID.toString(),
'amount': amount.toString(),
});
var d = jsonDecode(res);
return d['message'];
}
addPaymentToDriver(String amount, driverID, token) async {
paymentToken = await generateTokenDriver(amount.toString(), driverID);
var res = await CRUD().post(link: AppLink.addDrivePayment, payload: {
'rideId': 'gift_$driverID _${DateTime.now().toIso8601String()}',
'amount': amount,
'payment_method': 'visaRide',
'passengerID': 'gift',
'token': paymentToken,
'driverID': driverID.toString(),
});
if (res != 'failure') {
FirebaseMessagesController().sendNotificationToAnyWithoutData(
"لديك هدية من سفَر".tr,
'لقد حصلت على هدية من سفر بقيمة $amount ',
token, // Access token correctly
'ding.wav',
);
Get.snackbar('success', 'addPaymentToDriver',
backgroundColor: AppColor.greenColor);
} else {
Get.snackbar('error', 'addPaymentToDriver',
backgroundColor: AppColor.redColor);
}
}
Future addDriverWallet(String paymentMethod, driverID, point, phone) async {
// paymentToken = await generateToken(count);
// var paymentID = await getPaymentId(paymentMethod, point.toString());
await CRUD().postWallet(link: AppLink.addFromAdmin, payload: {
'driverID': driverID.toString(),
'paymentID': 'gift_connect_$driverID${DateTime.timestamp()}'.toString(),
'amount': point,
'token': 'gift_connect',
'paymentMethod': paymentMethod,
'phone': phone,
});
}
Future addDrivergift300(String paymentMethod, driverID, point, phone) async {
// paymentToken = await generateToken(count);
// var paymentID = await getPaymentId(paymentMethod, point.toString());
var res = await CRUD().postWallet(link: AppLink.add300ToDriver, payload: {
'driverID': driverID.toString(),
'paymentID': paymentMethod,
'amount': point,
'token': 'gift_connect_300',
'paymentMethod': paymentMethod,
'phone': phone,
});
if (res != 'failure') {
Get.snackbar('success', 'addDrivergift300',
backgroundColor: AppColor.greenColor);
} else {
Get.snackbar('error', res, backgroundColor: AppColor.redColor);
}
}
Future addSeferWallet(String point, driverID) async {
var amount = (int.parse(point) * -1).toStringAsFixed(0);
var seferToken = await generateTokenDriver(amount, driverID);
var res = await CRUD().post(link: AppLink.addSeferWallet, payload: {
'amount': amount.toString(),
'paymentMethod': 'visaRide',
'passengerId': 'gift$driverID',
'token': seferToken,
'driverId': driverID.toString(),
});
if (res != 'failure') {
Get.snackbar('success', 'addSeferWallet',
backgroundColor: AppColor.greenColor);
} else {
Get.snackbar('error', 'addSeferWallet',
backgroundColor: AppColor.redColor);
}
}
}
@@ -0,0 +1,237 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'firebase/notification_service.dart';
class NotificationController extends GetxController {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController titleController = TextEditingController();
final TextEditingController bodyController = TextEditingController();
// ألوان الثيم (متناسقة مع باقي الصفحات المحسنة)
final Color _dialogColor = const Color(0xFF1A1F3A);
final Color _inputColor = const Color(0xFF0A0E27);
final Color _primaryAccent = const Color(0xFF6366F1);
@override
void onClose() {
titleController.dispose();
bodyController.dispose();
super.onClose();
}
/// تنظيف الحقول
void _clearFields() {
titleController.clear();
bodyController.clear();
}
/// إرسال إشعار للسائقين
Future<void> sendNotificationDrivers() async {
_clearFields();
await _showCustomDialog(
dialogTitle: 'إشعار للسائقين',
targetAudience: 'drivers',
icon: Icons.drive_eta_rounded,
iconColor: Colors.orangeAccent,
);
}
/// إرسال إشعار للركاب
Future<void> sendNotificationPassengers() async {
_clearFields();
await _showCustomDialog(
dialogTitle: 'إشعار للركاب',
targetAudience: 'passengers',
icon: Icons.people_alt_rounded,
iconColor: Colors.blueAccent,
);
}
/// دالة عامة لإظهار نافذة الإرسال بتصميم عصري
Future<void> _showCustomDialog({
required String dialogTitle,
required String targetAudience,
required IconData icon,
required Color iconColor,
}) {
return Get.dialog(
Dialog(
backgroundColor: _dialogColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
elevation: 10,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 1. أيقونة العنوان
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.15),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: iconColor.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Icon(icon, color: iconColor, size: 32),
),
const SizedBox(height: 16),
// 2. العنوان
Text(
dialogTitle,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
fontFamily: 'Segoe UI',
),
),
const SizedBox(height: 24),
// 3. حقول الإدخال
_buildModernTextField(
controller: titleController,
hint: 'عنوان الإشعار',
icon: Icons.title_rounded,
),
const SizedBox(height: 16),
_buildModernTextField(
controller: bodyController,
hint: 'نص الرسالة',
icon: Icons.message_rounded,
maxLines: 3,
),
const SizedBox(height: 32),
// 4. الأزرار
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
foregroundColor: Colors.white54,
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: const Text('إلغاء'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () async {
if (titleController.text.trim().isEmpty ||
bodyController.text.trim().isEmpty) {
Get.snackbar(
"تنبيه",
"الرجاء تعبئة جميع الحقول",
backgroundColor: Colors.amber.withOpacity(0.8),
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
);
return;
}
Get.back(); // إغلاق النافذة
await _processSending(targetAudience);
},
style: ElevatedButton.styleFrom(
backgroundColor: _primaryAccent,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Text('إرسال'),
),
),
],
),
],
),
),
),
),
),
barrierColor: Colors.black.withOpacity(0.7), // تعتيم الخلفية
);
}
/// تنفيذ عملية الإرسال الفعلية
Future<void> _processSending(String target) async {
// إظهار تنبيه بدء العملية
Get.snackbar(
"جاري الإرسال",
"يتم إرسال الإشعار لـ $target...",
backgroundColor: _primaryAccent.withOpacity(0.2),
colorText: Colors.white,
duration: const Duration(seconds: 2),
);
try {
// استدعاء خدمة الإرسال
await NotificationService.sendNotification(
target: target,
title: titleController.text,
body: bodyController.text,
isTopic: true,
category: 'fromAdmin',
);
Get.snackbar(
"نجاح",
"تم إرسال الإشعار بنجاح",
backgroundColor: Colors.green.withOpacity(0.5),
colorText: Colors.white,
);
} catch (e) {
Get.snackbar(
"خطأ",
"فشل إرسال الإشعار: $e",
backgroundColor: Colors.red.withOpacity(0.5),
colorText: Colors.white,
);
}
}
/// تصميم حقل الإدخال المخصص (Dark Input Field)
Widget _buildModernTextField({
required TextEditingController controller,
required String hint,
required IconData icon,
int maxLines = 1,
}) {
return Container(
decoration: BoxDecoration(
color: _inputColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.1)),
),
child: TextField(
controller: controller,
style: const TextStyle(color: Colors.white),
maxLines: maxLines,
decoration: InputDecoration(
hintText: hint,
hintStyle:
TextStyle(color: Colors.white.withOpacity(0.3), fontSize: 14),
prefixIcon: Icon(icon, color: Colors.white38, size: 20),
border: InputBorder.none,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
),
);
}
}
@@ -0,0 +1,139 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.dart';
class RideLookupController extends GetxController {
final TextEditingController phoneCtrl = TextEditingController();
bool isLoading = false;
Map<String, dynamic>? passenger; // {id, first_name, last_name, phone}
Map<String, dynamic>? ride; // Ride details
// Status filter for the search tab
String currentStatusFilter = '';
// Whitelist of allowed statuses for the Update Dropdown
// UPDATED: Matches the exact types you requested
final List<String> statusOptions = const [
'Pending',
'Accepted',
'EnRoute',
'Arrived',
'Started',
'Completed',
'Canceled',
];
String? selectedStatus;
// Hydrate dropdown value from the current ride data
void hydrateSelectedFromRide() {
final cur = (ride?['status'] ?? '') as String;
selectedStatus = statusOptions.contains(cur) ? cur : null;
update();
}
Future<bool> updateRideStatus({String? note}) async {
if (ride == null) return false;
if (selectedStatus == null || selectedStatus!.isEmpty) return false;
isLoading = true;
update();
try {
final res = await CRUD().post(
link: AppLink.admin_update_ride_status,
payload: {
'id': "${ride!['id']}",
'status': selectedStatus!,
if (note != null && note.trim().isNotEmpty) 'reason': note.trim(),
},
);
final d = jsonDecode(res);
final ok = (d['status'] == 'success');
if (ok) {
// Update local ride details from response
final updated = (d['message'] ?? d)['ride'];
if (updated != null) {
ride = Map<String, dynamic>.from(updated);
}
update();
return true;
} else {
return false;
}
} catch (_) {
return false;
} finally {
isLoading = false;
update();
}
}
// Updated to accept status filter
Future<bool> searchLatest({String? status}) async {
final phone = phoneCtrl.text.trim();
// If status is passed, update the current filter
if (status != null) {
currentStatusFilter = status;
}
// If phone is empty, we stop unless your API supports fetching "latest of all users"
if (phone.isEmpty) {
return false;
}
isLoading = true;
update();
try {
final res = await CRUD().post(
link: AppLink.admin_get_rides_by_phone,
payload: {
'phone': phone,
// If filter is 'All', send empty string to PHP, otherwise send the exact status
'status': currentStatusFilter == 'All' ? '' : currentStatusFilter,
},
);
final d = res;
if (d['status'] == 'success') {
passenger = (d['message'] ?? d)['passenger'];
ride = (d['message'] ?? d)['ride'];
// Hydrate the dropdown for the update section based on the fetched ride
hydrateSelectedFromRide();
update();
return true;
} else {
passenger = null;
ride = null;
update();
return false;
}
} catch (_) {
passenger = null;
ride = null;
update();
return false;
} finally {
isLoading = false;
update();
}
}
String rideHeader() {
if (ride == null) return '';
final id = ride!['id'] ?? '';
final st = (ride!['status'] ?? '—').toString();
return "Ride #$id — $st";
}
}
@@ -0,0 +1,150 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import '../../print.dart';
// --- Models ---
class ServerData {
final CpuInfo cpu;
final MemoryInfo memory;
final DiskInfo disk;
final Map<String, String> services;
final List<ProcessInfo> topProcesses;
final NetworkInfo network;
final UptimeInfo uptime;
final String timestamp;
ServerData({
required this.cpu,
required this.memory,
required this.disk,
required this.services,
required this.topProcesses,
required this.network,
required this.uptime,
required this.timestamp,
});
factory ServerData.fromJson(Map<String, dynamic> json) {
return ServerData(
cpu: CpuInfo.fromJson(json['cpu']),
memory: MemoryInfo.fromJson(json['memory']),
disk: DiskInfo.fromJson(json['disk']),
services: Map<String, String>.from(json['services']),
topProcesses: (json['top_processes'] as List)
.map((i) => ProcessInfo.fromJson(i))
.toList(),
network: NetworkInfo.fromJson(json['network']),
uptime: UptimeInfo.fromJson(json['uptime']),
timestamp: json['timestamp'],
);
}
}
class CpuInfo {
final double percent;
final int cores;
final double load1m;
CpuInfo({required this.percent, required this.cores, required this.load1m});
factory CpuInfo.fromJson(Map<String, dynamic> json) => CpuInfo(
percent: json['percent'].toDouble(),
cores: json['cores'],
load1m: json['load_1m'].toDouble());
}
class MemoryInfo {
final double percent;
final double usedGb;
final double totalGb;
MemoryInfo(
{required this.percent, required this.usedGb, required this.totalGb});
factory MemoryInfo.fromJson(Map<String, dynamic> json) => MemoryInfo(
percent: json['percent'].toDouble(),
usedGb: json['used_gb'].toDouble(),
totalGb: json['total_gb'].toDouble());
}
class DiskInfo {
final double percent;
final double usedGb;
final double totalGb;
DiskInfo(
{required this.percent, required this.usedGb, required this.totalGb});
factory DiskInfo.fromJson(Map<String, dynamic> json) => DiskInfo(
percent: json['percent'].toDouble(),
usedGb: json['used_gb'].toDouble(),
totalGb: json['total_gb'].toDouble());
}
class ProcessInfo {
final String name;
final String usage;
ProcessInfo({required this.name, required this.usage});
factory ProcessInfo.fromJson(Map<String, dynamic> json) =>
ProcessInfo(name: json['name'], usage: json['usage']);
}
class NetworkInfo {
final double receivedMb;
final double sentMb;
NetworkInfo({required this.receivedMb, required this.sentMb});
factory NetworkInfo.fromJson(Map<String, dynamic> json) => NetworkInfo(
receivedMb: json['received_mb'].toDouble(),
sentMb: json['sent_mb'].toDouble());
}
class UptimeInfo {
final String formatted;
UptimeInfo({required this.formatted});
factory UptimeInfo.fromJson(Map<String, dynamic> json) =>
UptimeInfo(formatted: json['formatted']);
}
// --- Controller ---
class ServerMonitorController extends GetxController {
var isLoading = false.obs;
var serverData = Rxn<ServerData>();
var errorMessage = ''.obs;
Timer? _timer; // تخزين التايمر
@override
void onInit() {
super.onInit();
fetchServerData();
// تحديث تلقائي كل 60 ثانية
_timer = Timer.periodic(Duration(seconds: 60), (_) {
fetchServerData();
});
}
@override
void onClose() {
// إلغاء التحديث عند إغلاق الصفحة
_timer?.cancel();
super.onClose();
}
Future<void> fetchServerData() async {
try {
isLoading(true);
errorMessage('');
final response = await GetConnect().get(AppLink.serverMonitor);
if (response.status.hasError) {
errorMessage.value = 'خطأ في الاتصال: ${response.statusText}';
} else {
serverData.value = ServerData.fromJson(response.body);
}
} catch (e) {
errorMessage.value = 'حدث خطأ: $e';
} finally {
isLoading(false);
}
}
}
+22
View File
@@ -0,0 +1,22 @@
import 'package:siro_admin/controller/functions/encrypt_decrypt.dart';
import 'package:siro_admin/constant/info.dart';
void main() {
String testJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYWRtaW4ifQ.signature";
print("Original: $testJwt");
String encrypted = c(testJwt);
print("Encrypted: $encrypted");
String decrypted = r(encrypted);
print("Decrypted: $decrypted");
String split = decrypted.split(AppInformation.addd)[0];
print("Split: $split");
if (testJwt == split) {
print("✅ Match!");
} else {
print("❌ Mismatch!");
}
}
+316
View File
@@ -0,0 +1,316 @@
import 'package:envied/envied.dart';
part 'env.g.dart';
@Envied()
abstract class Env {
@EnviedField(varName: 'addd', obfuscate: true)
static final String addd = _Env.addd;
@EnviedField(varName: 'initializationVector', obfuscate: true)
static final String initializationVector = _Env.initializationVector;
@EnviedField(varName: 'ALLOWED_ADMIN_PHONES', obfuscate: true)
static final String ALLOWED_ADMIN_PHONES = _Env.ALLOWED_ADMIN_PHONES;
@EnviedField(varName: 'privateKeyFCM', obfuscate: true)
static final String privateKeyFCM = _Env.privateKeyFCM;
@EnviedField(varName: 'passnpassenger', obfuscate: true)
static final String passnpassenger = _Env.passnpassenger;
@EnviedField(varName: 'newId', obfuscate: true)
static final String newId = _Env.newId;
@EnviedField(varName: 'allowed', obfuscate: true)
static final String allowed = _Env.allowed;
@EnviedField(varName: 'basicAuthCredentials', obfuscate: true)
static final String basicAuthCredentials = _Env.basicAuthCredentials;
@EnviedField(varName: 'basicCompareFaces', obfuscate: true)
static final String basicCompareFaces = _Env.basicCompareFaces;
@EnviedField(varName: 'basicCompareFacesURL', obfuscate: true)
static final String basicCompareFacesURL = _Env.basicCompareFacesURL;
@EnviedField(varName: 'accountSIDTwillo', obfuscate: true)
static final String accountSIDTwillo = _Env.accountSIDTwillo;
@EnviedField(varName: 'serverAPI', obfuscate: true)
static final String serverAPI = _Env.serverAPI;
@EnviedField(varName: 'mapAPIKEY', obfuscate: true)
static final String mapAPIKEY = _Env.mapAPIKEY;
@EnviedField(varName: 'twilloRecoveryCode', obfuscate: true)
static final String twilloRecoveryCode = _Env.twilloRecoveryCode;
@EnviedField(varName: 'authTokenTwillo', obfuscate: true)
static final String authTokenTwillo = _Env.authTokenTwillo;
@EnviedField(varName: 'chatGPTkey', obfuscate: true)
static final String chatGPTkey = _Env.chatGPTkey;
@EnviedField(varName: 'transactionCloude', obfuscate: true)
static final String transactionCloude = _Env.transactionCloude;
@EnviedField(varName: 'visionApi', obfuscate: true)
static final String visionApi = _Env.visionApi;
@EnviedField(varName: 'secretKey', obfuscate: true)
static final String secretKey = _Env.secretKey;
@EnviedField(varName: 'chatGPTkeySefer', obfuscate: true)
static final String chatGPTkeySefer = _Env.chatGPTkeySefer;
@EnviedField(varName: 'llamaKey', obfuscate: true)
static final String llamaKey = _Env.llamaKey;
@EnviedField(varName: 'serverPHP', obfuscate: true)
static final String serverPHP = _Env.serverPHP;
@EnviedField(varName: 'seferAlexandriaServer', obfuscate: true)
static final String seferAlexandriaServer = _Env.seferAlexandriaServer;
@EnviedField(varName: 'whatsapp', obfuscate: true)
static final String whatsapp = _Env.whatsapp;
@EnviedField(varName: 'whatappID', obfuscate: true)
static final String whatappID = _Env.whatappID;
@EnviedField(varName: 'seferPaymentServer', obfuscate: true)
static final String seferPaymentServer = _Env.seferPaymentServer;
@EnviedField(varName: 'seferCairoServer', obfuscate: true)
static final String seferCairoServer = _Env.seferCairoServer;
@EnviedField(varName: 'seferGizaServer', obfuscate: true)
static final String seferGizaServer = _Env.seferGizaServer;
@EnviedField(varName: 'chatGPTkeySeferNew', obfuscate: true)
static final String chatGPTkeySeferNew = _Env.chatGPTkeySeferNew;
@EnviedField(varName: 'cohere', obfuscate: true)
static final String cohere = _Env.cohere;
@EnviedField(varName: 'claudeAiAPI', obfuscate: true)
static final String claudeAiAPI = _Env.claudeAiAPI;
@EnviedField(varName: 'geminiApi', obfuscate: true)
static final String geminiApi = _Env.geminiApi;
@EnviedField(varName: 'geminiApiMasa', obfuscate: true)
static final String geminiApiMasa = _Env.geminiApiMasa;
@EnviedField(varName: 'agoraAppId', obfuscate: true)
static final String agoraAppId = _Env.agoraAppId;
@EnviedField(varName: 'agoraAppCertificate', obfuscate: true)
static final String agoraAppCertificate = _Env.agoraAppCertificate;
@EnviedField(varName: 'integrationIdPayMob', obfuscate: true)
static final String integrationIdPayMob = _Env.integrationIdPayMob;
@EnviedField(varName: 'passwordPayMob', obfuscate: true)
static final String passwordPayMob = _Env.passwordPayMob;
@EnviedField(varName: 'usernamePayMob', obfuscate: true)
static final String usernamePayMob = _Env.usernamePayMob;
@EnviedField(varName: 'payMobApikey', obfuscate: true)
static final String payMobApikey = _Env.payMobApikey;
@EnviedField(varName: 'integrationIdPayMobWallet', obfuscate: true)
static final String integrationIdPayMobWallet =
_Env.integrationIdPayMobWallet;
@EnviedField(varName: 'smsPasswordEgypt', obfuscate: true)
static final String smsPasswordEgypt = _Env.smsPasswordEgypt;
@EnviedField(varName: 'ocpApimSubscriptionKey', obfuscate: true)
static final String ocpApimSubscriptionKey = _Env.ocpApimSubscriptionKey;
@EnviedField(varName: 'chatGPTkeySeferNew4', obfuscate: true)
static final String chatGPTkeySeferNew4 = _Env.chatGPTkeySeferNew4;
@EnviedField(varName: 'anthropicAIkeySeferNew', obfuscate: true)
static final String anthropicAIkeySeferNew = _Env.anthropicAIkeySeferNew;
@EnviedField(varName: 'llama3Key', obfuscate: true)
static final String llama3Key = _Env.llama3Key;
@EnviedField(varName: 'payMobOutClientSecrret', obfuscate: true)
static final String payMobOutClientSecrret = _Env.payMobOutClientSecrret;
@EnviedField(varName: 'allowedWallet', obfuscate: true)
static final String allowedWallet = _Env.allowedWallet;
@EnviedField(varName: 'payMobOutClient_id', obfuscate: true)
static final String payMobOutClient_id = _Env.payMobOutClient_id;
@EnviedField(varName: 'payMobOutPassword', obfuscate: true)
static final String payMobOutPassword = _Env.payMobOutPassword;
@EnviedField(varName: 'payMobOutUserName', obfuscate: true)
static final String payMobOutUserName = _Env.payMobOutUserName;
@EnviedField(varName: 'A', obfuscate: true)
static final String A = _Env.A;
@EnviedField(varName: 'B', obfuscate: true)
static final String B = _Env.B;
@EnviedField(varName: 'C', obfuscate: true)
static final String C = _Env.C;
@EnviedField(varName: 'D', obfuscate: true)
static final String D = _Env.D;
@EnviedField(varName: 'E', obfuscate: true)
static final String E = _Env.E;
@EnviedField(varName: 'F', obfuscate: true)
static final String F = _Env.F;
@EnviedField(varName: 'G', obfuscate: true)
static final String G = _Env.G;
@EnviedField(varName: 'H', obfuscate: true)
static final String H = _Env.H;
@EnviedField(varName: 'I', obfuscate: true)
static final String I = _Env.I;
@EnviedField(varName: 'J', obfuscate: true)
static final String J = _Env.J;
@EnviedField(varName: 'K', obfuscate: true)
static final String K = _Env.K;
@EnviedField(varName: 'L', obfuscate: true)
static final String L = _Env.L;
@EnviedField(varName: 'M', obfuscate: true)
static final String M = _Env.M;
@EnviedField(varName: 'N', obfuscate: true)
static final String N = _Env.N;
@EnviedField(varName: 'O', obfuscate: true)
static final String O = _Env.O;
@EnviedField(varName: 'P', obfuscate: true)
static final String P = _Env.P;
@EnviedField(varName: 'Q', obfuscate: true)
static final String Q = _Env.Q;
@EnviedField(varName: 'R', obfuscate: true)
static final String R = _Env.R;
@EnviedField(varName: 'S', obfuscate: true)
static final String S = _Env.S;
@EnviedField(varName: 'T', obfuscate: true)
static final String T = _Env.T;
@EnviedField(varName: 'U', obfuscate: true)
static final String U = _Env.U;
@EnviedField(varName: 'V', obfuscate: true)
static final String V = _Env.V;
@EnviedField(varName: 'W', obfuscate: true)
static final String W = _Env.W;
@EnviedField(varName: 'X', obfuscate: true)
static final String X = _Env.X;
@EnviedField(varName: 'Y', obfuscate: true)
static final String Y = _Env.Y;
@EnviedField(varName: 'Z', obfuscate: true)
static final String Z = _Env.Z;
@EnviedField(varName: 'a', obfuscate: true)
static final String a = _Env.a;
@EnviedField(varName: 'b', obfuscate: true)
static final String b = _Env.b;
@EnviedField(varName: 'c', obfuscate: true)
static final String c = _Env.c;
@EnviedField(varName: 'd', obfuscate: true)
static final String d = _Env.d;
@EnviedField(varName: 'e', obfuscate: true)
static final String e = _Env.e;
@EnviedField(varName: 'f', obfuscate: true)
static final String f = _Env.f;
@EnviedField(varName: 'g', obfuscate: true)
static final String g = _Env.g;
@EnviedField(varName: 'h', obfuscate: true)
static final String h = _Env.h;
@EnviedField(varName: 'i', obfuscate: true)
static final String i = _Env.i;
@EnviedField(varName: 'j', obfuscate: true)
static final String j = _Env.j;
@EnviedField(varName: 'k', obfuscate: true)
static final String k = _Env.k;
@EnviedField(varName: 'l', obfuscate: true)
static final String l = _Env.l;
@EnviedField(varName: 'm', obfuscate: true)
static final String m = _Env.m;
@EnviedField(varName: 'n', obfuscate: true)
static final String n = _Env.n;
@EnviedField(varName: 'o', obfuscate: true)
static final String o = _Env.o;
@EnviedField(varName: 'p', obfuscate: true)
static final String p = _Env.p;
@EnviedField(varName: 'q', obfuscate: true)
static final String q = _Env.q;
@EnviedField(varName: 'r', obfuscate: true)
static final String r = _Env.r;
@EnviedField(varName: 's', obfuscate: true)
static final String s = _Env.s;
@EnviedField(varName: 't', obfuscate: true)
static final String t = _Env.t;
@EnviedField(varName: 'u', obfuscate: true)
static final String u = _Env.u;
@EnviedField(varName: 'v', obfuscate: true)
static final String v = _Env.v;
@EnviedField(varName: 'w', obfuscate: true)
static final String w = _Env.w;
@EnviedField(varName: 'x', obfuscate: true)
static final String x = _Env.x;
@EnviedField(varName: 'y', obfuscate: true)
static final String y = _Env.y;
@EnviedField(varName: 'z', obfuscate: true)
static final String z = _Env.z;
@EnviedField(varName: 'keyOfApp', obfuscate: true)
static final String keyOfApp = _Env.keyOfApp;
}
+13886
View File
File diff suppressed because it is too large Load Diff
+67
View File
@@ -0,0 +1,67 @@
// File generated by FlutterFire CLI.
// ignore_for_file: type=lint
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for web - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for macos - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.windows:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for windows - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
case TargetPlatform.linux:
throw UnsupportedError(
'DefaultFirebaseOptions have not been configured for linux - '
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyAEoply_UcEP6KaCu_ziCy_ZDIjAKvi7b8',
appId: '1:825988584191:android:caa30eaac3524de51632ca',
messagingSenderId: '825988584191',
projectId: 'siro-a6957',
storageBucket: 'siro-a6957.firebasestorage.app',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyDk6x6KZUY0IQtxoCMXX0F7N_yik8O19eA',
appId: '1:825988584191:ios:8426fcf68fcc470e1632ca',
messagingSenderId: '825988584191',
projectId: 'siro-a6957',
storageBucket: 'siro-a6957.firebasestorage.app',
iosBundleId: 'com.siro.admin',
);
}
+106
View File
@@ -0,0 +1,106 @@
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'constant/box_name.dart';
import 'controller/firebase/firbase_messge.dart';
import 'controller/functions/encrypt_decrypt.dart';
import 'firebase_options.dart';
import 'models/db_sql.dart';
import 'package:google_fonts/google_fonts.dart';
import 'constant/colors.dart';
import 'routes.dart';
import 'binding/initial_binding.dart';
final box = GetStorage();
const storage = FlutterSecureStorage();
@pragma('vm:entry-point')
Future<void> backgroundMessageHandler(RemoteMessage message) async {
await Firebase.initializeApp();
if (message.data.isNotEmpty && message.notification != null) {
FirebaseMessagesController().fireBaseTitles(message);
}
} //
DbSql sql = DbSql.instance;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
await initializeDateFormatting('ar', null);
await EncryptionHelper.initialize();
if (Platform.isAndroid || Platform.isIOS) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// await FirebaseMessagesController().requestFirebaseMessagingPermission();
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
List<Future> initializationTasks = [
FirebaseMessagesController().getNotificationSettings(),
FirebaseMessagesController().getToken(),
];
await Future.wait(initializationTasks);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
} // Enable Crashlytics collection
// FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Intaleq Admin',
locale: const Locale('ar'),
fallbackLocale: const Locale('en'),
themeMode: ThemeMode.dark,
darkTheme: ThemeData(
brightness: Brightness.dark,
scaffoldBackgroundColor: AppColor.bg,
primaryColor: AppColor.accent,
colorScheme: const ColorScheme.dark(
primary: AppColor.accent,
secondary: AppColor.accent,
surface: AppColor.surface,
background: AppColor.bg,
error: AppColor.danger,
),
dividerColor: AppColor.divider,
textTheme:
GoogleFonts.cairoTextTheme(ThemeData.dark().textTheme).copyWith(
bodyLarge: GoogleFonts.inter(color: AppColor.textPrimary),
bodyMedium: GoogleFonts.inter(color: AppColor.textPrimary),
),
appBarTheme: const AppBarTheme(
backgroundColor: AppColor.bg,
elevation: 0,
centerTitle: true,
titleTextStyle: TextStyle(
color: AppColor.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
initialBinding: InitialBinding(),
initialRoute: box.read(BoxName.phoneVerified) == true ? "/" : "/login",
getPages: routes,
);
}
}
+116
View File
@@ -0,0 +1,116 @@
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../constant/table_names.dart';
class DbSql {
static final DbSql instance = DbSql._();
static Database? _database;
DbSql._();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
Future<Database> _initDatabase() async {
String path = join(await getDatabasesPath(), 'my_database.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS ${TableName.carLocations}(
id INTEGER PRIMARY KEY AUTOINCREMENT,
driver_id TEXT,
latitude REAL,
longitude REAL,
created_at TEXT,
updated_at TEXT
)
''');
await db.execute('''
CREATE TABLE IF NOT EXISTS ${TableName.placesFavorite}(
id INTEGER PRIMARY KEY AUTOINCREMENT,
latitude REAL,
longitude REAL,
name TEXT UNIQUE,
rate TEXT
)
''');
await db.execute('''
CREATE TABLE IF NOT EXISTS ${TableName.recentLocations}(
id INTEGER PRIMARY KEY AUTOINCREMENT,
latitude REAL,
longitude REAL,
name TEXT ,
rate TEXT
)
''');
await db.execute('''
CREATE TABLE IF NOT EXISTS ${TableName.driverOrdersRefuse}(
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id TEXT UNIQUE,
created_at TEXT,
driver_id TEXT
)
''');
await db.execute('''
CREATE TABLE IF NOT EXISTS ${TableName.rideLocation}(
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id TEXT ,
created_at TEXT,
lat TEXT,
lng TEXT
)
''');
await db.execute('''
CREATE TABLE IF NOT EXISTS ${TableName.faceDetectTimes}(
id INTEGER PRIMARY KEY AUTOINCREMENT,
faceDetectTimes INTEGER
)
''');
await db.execute('''
CREATE TABLE IF NOT EXISTS ${TableName.captainNotification}(
id INTEGER PRIMARY KEY AUTOINCREMENT,
faceDetectTimes INTEGER
)
''');
},
);
}
Future<List<Map<String, dynamic>>> getAllData(String table) async {
Database db = await instance.database;
return await db.query(table);
}
Future<List<Map<String, dynamic>>> getCustomQuery(String query) async {
Database db = await instance.database;
return await db.rawQuery(query);
}
Future<int> insertData(Map<String, dynamic> map, String table) async {
Database db = await instance.database;
return await db.insert(table, map);
}
Future<int> updateData(Map<String, dynamic> map, String table, int id) async {
Database db = await instance.database;
return await db.update(table, map, where: 'id = ?', whereArgs: [id]);
}
Future<int> deleteData(String table, int id) async {
Database db = await instance.database;
return await db.delete(table, where: 'id = ?', whereArgs: [id]);
}
Future<int> deleteAllData(String table) async {
Database db = await instance.database;
return await db.delete(table);
}
}
+40
View File
@@ -0,0 +1,40 @@
-- Frequent Complaint Passengers
SELECT
passengers.id AS passenger_id,
passengers.first_name,
passengers.last_name,
passengers.phone,
COUNT(`feedBack`.id) AS complaint_count
FROM
passengers
JOIN `feedBack` ON passengers.id = `feedBack`.`passengerId`
GROUP BY
passengers.id
ORDER BY
complaint_count
DESC
LIMIT 10;
--==========
-- to get all driver payment to pay to them
SELECT
p.driverID,
COALESCE(SUM(p.amount), 0) AS total_amount,
COALESCE(SUM(p.amount), 0) + COALESCE(pd.total_points, 0) AS diff
FROM
payments p
JOIN (
SELECT
driverID,
SUM(amount) AS total_points
FROM
paymentsDriverPoints
WHERE
payment_method = 'fromBudgetToPoints'
GROUP BY
driverID
) pd ON p.driverID = pd.driverID
WHERE
p.isGiven = 'waiting'
AND p.payment_method IN ('visa-in', 'visa', 'visaRide', 'TransferFrom', 'payout', 'TransferTo')
GROUP BY
p.driverID;
@@ -0,0 +1,21 @@
class MonthlyDataModel {
final int year;
final int month;
final int day;
final int ridesCount;
MonthlyDataModel({
required this.year,
required this.month,
required this.day,
required this.ridesCount,
});
factory MonthlyDataModel.fromJson(Map<String, dynamic> json) =>
MonthlyDataModel(
year: json['year'] as int,
month: json['month'] as int,
day: json['day'] as int,
ridesCount: json['rides_count'] as int,
);
}
@@ -0,0 +1,79 @@
class Passenger {
String id;
String phone;
String email;
String gender;
String status;
String birthdate;
String site;
String firstName;
String lastName;
String sosPhone;
String education;
String employmentType;
String maritalStatus;
String createdAt;
String updatedAt;
int countPassenger;
int countFeedback;
double ratingPassenger;
int countDriverRate;
int countPassengerCancel;
double passengerAverageRating;
int countPassengerRate;
int countPassengerRide;
Passenger({
required this.id,
required this.phone,
required this.email,
required this.gender,
required this.status,
required this.birthdate,
required this.site,
required this.firstName,
required this.lastName,
required this.sosPhone,
required this.education,
required this.employmentType,
required this.maritalStatus,
required this.createdAt,
required this.updatedAt,
required this.countPassenger,
required this.countFeedback,
required this.ratingPassenger,
required this.countDriverRate,
required this.countPassengerCancel,
required this.passengerAverageRating,
required this.countPassengerRate,
required this.countPassengerRide,
});
factory Passenger.fromJson(Map<String, dynamic> json) {
return Passenger(
id: json['id'],
phone: json['phone'],
email: json['email'],
gender: json['gender'],
status: json['status'],
birthdate: json['birthdate'],
site: json['site'],
firstName: json['first_name'],
lastName: json['last_name'],
sosPhone: json['sosPhone'],
education: json['education'],
employmentType: json['employmentType'],
maritalStatus: json['maritalStatus'],
createdAt: json['created_at'],
updatedAt: json['updated_at'],
countPassenger: json['countPassenger'],
countFeedback: json['countFeedback'],
ratingPassenger: json['ratingPassenger'].toDouble(),
countDriverRate: json['countDriverRate'],
countPassengerCancel: json['countPassengerCancel'],
passengerAverageRating: json['passengerAverageRating'].toDouble(),
countPassengerRate: json['countPassengerRate'],
countPassengerRide: json['countPassengerRide'],
);
}
}
@@ -0,0 +1,12 @@
class MonthlyDataModel {
int day;
int totalDuration;
MonthlyDataModel({required this.day, required this.totalDuration});
factory MonthlyDataModel.fromJson(Map<String, dynamic> json) =>
MonthlyDataModel(
day: int.parse(json['day'].toString().split('-')[2]),
totalDuration:
int.parse(json['total_duration'].toString().split(':')[0]));
}
@@ -0,0 +1,34 @@
class CarLocationModel {
String id;
String driverId;
double latitude;
double heading;
double speed;
double longitude;
DateTime createdAt;
DateTime updatedAt;
CarLocationModel({
required this.id,
required this.driverId,
required this.latitude,
required this.longitude,
required this.heading,
required this.speed,
required this.createdAt,
required this.updatedAt,
});
factory CarLocationModel.fromJson(Map<String, dynamic> json) {
return CarLocationModel(
id: json['id'],
driverId: json['driver_id'],
latitude: double.parse(json['latitude'].toString()),
longitude: double.parse(json['longitude'].toString()),
heading: double.parse(json['heading'].toString()),
speed: double.parse(json['speed'].toString()),
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
);
}
}
@@ -0,0 +1,30 @@
import 'package:get/get.dart';
List<OnBoardingModel> onBoardingList = [
OnBoardingModel(
title: 'Welcome to Sefer!'.tr,
image: 'assets/images/on1.png',
body:
'Sefer is the ride-hailing app that is safe, reliable, and accessible.'
.tr,
),
OnBoardingModel(
title: 'Get to your destination quickly and easily.'.tr,
image: 'assets/images/on2.png',
body: 'With Sefer, you can get a ride to your destination in minutes.'.tr,
),
OnBoardingModel(
title: 'Enjoy a safe and comfortable ride.'.tr,
image: 'assets/images/on3.png',
body:
'Sefer is committed to safety, and all of our captains are carefully screened and background checked.'
.tr,
),
];
class OnBoardingModel {
final String? title;
final String? image;
final String? body;
OnBoardingModel({this.body, this.title, this.image});
}
@@ -0,0 +1,77 @@
class MonthlyPassengerInstall {
int day;
int totalPassengers;
MonthlyPassengerInstall({required this.day, required this.totalPassengers});
factory MonthlyPassengerInstall.fromJson(Map<String, dynamic> json) =>
MonthlyPassengerInstall(
day: int.parse(json['day'].toString().split('-')[2]),
totalPassengers:
int.parse(json['totalPassengers'].toString().split(':')[0]));
}
class MonthlyRidesInstall {
int day;
int totalRides;
MonthlyRidesInstall({required this.day, required this.totalRides});
factory MonthlyRidesInstall.fromJson(Map<String, dynamic> json) =>
MonthlyRidesInstall(
day: int.parse(json['day'].toString().split('-')[2]),
totalRides: int.parse(json['totalRides'].toString().split(':')[0]));
}
class MonthlyEmployeeData {
int day;
int totalEmployees;
String name;
MonthlyEmployeeData(
{required this.day, required this.totalEmployees, required this.name});
factory MonthlyEmployeeData.fromJson(Map<String, dynamic> json) =>
MonthlyEmployeeData(
day: int.parse(json['date'].toString().split('-')[2]), // Extract day
totalEmployees: json['count'],
name: json['NAME'],
);
}
class MonthlyDriverInstall {
int day;
int totalDrivers;
int dailyTotalDrivers;
int dailyTotalCallingDrivers;
int dailyMatchingNotes;
int totalMonthlyDrivers;
int totalMonthlyCallingDrivers;
int totalMonthlyMatchingNotes;
MonthlyDriverInstall({
required this.day,
required this.totalDrivers,
required this.dailyTotalDrivers,
required this.dailyTotalCallingDrivers,
required this.dailyMatchingNotes,
required this.totalMonthlyDrivers,
required this.totalMonthlyCallingDrivers,
required this.totalMonthlyMatchingNotes,
});
factory MonthlyDriverInstall.fromJson(Map<String, dynamic> json) =>
MonthlyDriverInstall(
day: int.parse(json['day'].toString().split('-')[2]),
totalDrivers: int.parse(json['totalDrivers'].toString()),
dailyTotalDrivers: int.parse(json['dailyTotalDrivers'].toString()),
dailyTotalCallingDrivers:
int.parse(json['dailyTotalCallingDrivers'].toString()),
dailyMatchingNotes: int.parse(json['dailyMatchingNotes'].toString()),
totalMonthlyDrivers: int.parse(json['totalMonthlyDrivers'].toString()),
totalMonthlyCallingDrivers:
int.parse(json['totalMonthlyCallingDrivers'].toString()),
totalMonthlyMatchingNotes:
int.parse(json['totalMonthlyMatchingNotes'].toString()),
);
}
+18
View File
@@ -0,0 +1,18 @@
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
class Log {
Log._();
static void print(dynamic value, {StackTrace? stackTrace}) {
// استخدام debugPrint بدلاً من print العادي لتجنب تعليق الجهاز في الرسائل الطويلة
debugPrint("LOG: ${value.toString()}");
// يمكن أيضاً إرسالها للـ developer log لضمان ظهورها في الـ Debug Console الخاص بـ VS Code
developer.log(value.toString(), name: 'LOG', stackTrace: stackTrace);
}
static Object? inspect(Object? object) {
return developer.inspect(object);
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:get/get.dart';
import 'views/admin/admin_home_page.dart';
import 'views/auth/login_page.dart';
import 'views/auth/register_page.dart';
import 'views/admin/promo/promo_management_page.dart';
import 'views/admin/pricing/kazan_editor_page.dart';
import 'views/admin/complaints/complaint_list_page.dart';
import 'views/admin/drivers/driver_documents_review_page.dart';
List<GetPage<dynamic>> routes = [
GetPage(name: "/", page: () => const AdminHomePage()),
GetPage(name: "/login", page: () => const AdminLoginPage()),
GetPage(name: "/register", page: () => const RegisterPage()),
GetPage(name: "/promo", page: () => PromoManagementPage()),
GetPage(name: "/kazan", page: () => KazanEditorPage()),
GetPage(name: "/complaints", page: () => ComplaintListPage()),
GetPage(name: "/driver-docs", page: () => DriverDocsReviewPage()),
];
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,335 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../main.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/mycircular.dart';
import '../../../controller/admin/captain_admin_controller.dart';
import 'captain_details.dart';
class CaptainsPage extends StatelessWidget {
CaptainsPage({super.key});
final CaptainAdminController captainController =
Get.put(CaptainAdminController());
final TextEditingController searchController = TextEditingController();
String myPhone = box.read(BoxName.adminPhone).toString();
bool isSuperAdmin = false;
@override
Widget build(BuildContext context) {
isSuperAdmin = myPhone == '963942542053' || myPhone == '963992952235';
return MyScafolld(
title: 'Search for Captain'.tr,
isleading: true,
body: [
Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Theme.of(context).primaryColor.withOpacity(0.03),
Colors.white,
],
),
),
child: Column(
children: [
_buildHeaderSection(context),
Expanded(
child: GetBuilder<CaptainAdminController>(
builder: (controller) {
if (controller.isLoading) {
return _buildLoadingState();
}
final message = controller.captainData['message'];
if (message == null) {
return _buildEmptyState();
}
// 🔥 الحل هنا: توحيد الشكل إلى List
final List<dynamic> captains =
message is List ? message : [message];
if (captains.isEmpty) {
return _buildEmptyState();
}
return _buildResultsList(context, captains);
},
),
),
],
),
),
],
);
}
// ================= HEADER =================
Widget _buildHeaderSection(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.manage_search_rounded,
color: Theme.of(context).primaryColor,
size: 24,
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Find Captain'.tr,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
Text(
'Search by phone number'.tr,
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
),
],
),
],
),
const SizedBox(height: 20),
_buildModernSearchBar(context),
],
),
);
}
Widget _buildModernSearchBar(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey[200]!),
),
child: Row(
children: [
Expanded(
child: TextField(
controller: searchController,
keyboardType: TextInputType.phone,
style: const TextStyle(fontSize: 15),
decoration: InputDecoration(
hintText: '0990000000'.tr,
hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14),
prefixIcon: Icon(Icons.phone_android_rounded,
color: Colors.grey[400], size: 22),
border: InputBorder.none,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
onSubmitted: (_) => _performSearch(),
),
),
Padding(
padding: const EdgeInsets.all(6.0),
child: Material(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: _performSearch,
borderRadius: BorderRadius.circular(12),
child: const Padding(
padding: EdgeInsets.all(12),
child: Icon(Icons.search, color: Colors.white, size: 24),
),
),
),
),
],
),
);
}
void _performSearch() {
final phone = searchController.text.trim();
if (phone.isNotEmpty) {
captainController.find_driver_by_phone(phone);
}
}
// ================= RESULTS =================
Widget _buildResultsList(BuildContext context, List<dynamic> captains) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 12),
child: Row(
children: [
Text(
'Search Results'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${captains.length}',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
),
),
],
),
),
Expanded(
child: ListView.separated(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(20, 0, 20, 80),
itemCount: captains.length,
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (context, index) {
final captain = captains[index] as Map<String, dynamic>;
return _buildModernCaptainCard(context, captain);
},
),
),
],
);
}
// ================= CARD =================
Widget _buildModernCaptainCard(
BuildContext context, Map<String, dynamic> captain) {
final String fullName =
'${captain['first_name'] ?? ''} ${captain['last_name'] ?? ''}';
final String phone = captain['phone']?.toString() ?? '';
final String? email = captain['email']?.toString();
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {
Get.to(() => const CaptainDetailsPage(),
arguments: {'data': captain});
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).primaryColor.withOpacity(0.8),
Theme.of(context).primaryColor,
],
),
borderRadius: BorderRadius.circular(16),
),
child: const Icon(
Icons.person_rounded,
color: Colors.white,
size: 28,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fullName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
Text(phone),
if (isSuperAdmin && email != null) ...[
const SizedBox(height: 4),
Text(email),
],
],
),
),
],
),
),
),
),
);
}
// ================= STATES =================
Widget _buildLoadingState() {
return const Center(child: MyCircularProgressIndicator());
}
Widget _buildEmptyState() {
return const Center(
child: Text("No captains found"),
);
}
}
@@ -0,0 +1,425 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
import '../../../controller/admin/captain_admin_controller.dart';
import '../../../controller/firebase/firbase_messge.dart';
import '../../../main.dart'; // Import main to access myPhone
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/my_textField.dart';
import '../quality/driver_scorecard_page.dart';
import 'form_captain.dart';
class CaptainDetailsPage extends StatelessWidget {
const CaptainDetailsPage({super.key});
@override
Widget build(BuildContext context) {
final Map<String, dynamic> data = Get.arguments['data'];
final controller = Get.find<CaptainAdminController>();
String myPhone = box.read(BoxName.adminPhone).toString();
// Define Super Admin Logic
final bool isSuperAdmin =
myPhone == '963942542053' || myPhone == '963992952235';
return MyScafolld(
title: 'Captain Profile'.tr,
isleading: true,
body: [
SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 40),
child: Column(
children: [
// --- Header Section (Avatar & Name) ---
_buildHeaderSection(context, data),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
// --- Personal Information Card ---
_buildInfoCard(
title: 'Personal Information',
icon: Icons.person,
children: [
_buildDetailTile(
Icons.email_outlined,
'Email',
isSuperAdmin
? data['email']
: _maskEmail(
data['email']) // Mask email for non-super
),
_buildDetailTile(
Icons.phone_iphone,
'Phone',
_formatPhoneNumber(
data['phone'].toString(), isSuperAdmin)),
_buildDetailTile(Icons.transgender, 'Gender',
data['gender'] ?? 'Not specified'),
_buildDetailTile(Icons.cake_outlined, 'Birthdate',
data['birthdate'] ?? 'N/A'),
],
),
const SizedBox(height: 16),
// --- Ride Statistics Card ---
_buildInfoCard(
title: 'Performance & Stats',
icon: Icons.bar_chart_rounded,
children: [
_buildDetailTile(Icons.star_rate_rounded, 'Rating',
'${data['ratingPassenger'] ?? 0.0} / 5.0',
valueColor: Colors.amber[700]),
_buildDetailTile(Icons.directions_car_filled_outlined,
'Total Rides', data['countPassengerRide']),
_buildDetailTile(Icons.cancel_outlined,
'Canceled Rides', data['countPassengerCancel'],
valueColor: Colors.redAccent),
],
),
const SizedBox(height: 30),
// --- Action Buttons ---
_buildActionButtons(
context, controller, data, isSuperAdmin),
],
),
),
],
),
),
],
);
}
// --- Header with Gradient Background ---
Widget _buildHeaderSection(BuildContext context, Map<String, dynamic> data) {
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 25),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
)
],
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(30)),
),
child: Column(
children: [
CircleAvatar(
radius: 45,
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
child: Text(
data['first_name'] != null
? data['first_name'][0].toUpperCase()
: 'C',
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor),
),
),
const SizedBox(height: 12),
Text(
'${data['first_name']} ${data['last_name']}',
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.black87),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'Active Captain'.tr,
style: const TextStyle(
fontSize: 12,
color: Colors.green,
fontWeight: FontWeight.w600),
),
),
],
),
);
}
Widget _buildInfoCard(
{required String title,
required IconData icon,
required List<Widget> children}) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.05),
spreadRadius: 2,
blurRadius: 10)
],
border: Border.all(color: Colors.grey.withOpacity(0.1)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: AppColor.primaryColor, size: 22),
const SizedBox(width: 10),
Text(title.tr,
style: const TextStyle(
fontSize: 17, fontWeight: FontWeight.bold)),
],
),
Divider(height: 25, color: Colors.grey.withOpacity(0.2)),
...children,
],
),
);
}
Widget _buildDetailTile(IconData icon, String label, dynamic value,
{Color? valueColor}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8)),
child: Icon(icon, color: Colors.grey[600], size: 18),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label.tr,
style: TextStyle(fontSize: 12, color: Colors.grey[500])),
Text(
value?.toString() ?? 'N/A',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: valueColor ?? Colors.black87),
),
],
),
),
],
),
);
}
Widget _buildActionButtons(
BuildContext context,
CaptainAdminController controller,
Map<String, dynamic> data,
bool isSuperAdmin) {
return Column(
children: [
// Driver Scorecard Button
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
icon: const Icon(Icons.analytics_outlined, color: Colors.white),
label: Text("بطاقة الأداء (Scorecard)",
style: const TextStyle(color: Colors.white, fontSize: 16)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
onPressed: () {
Get.to(() => DriverScorecardPage(driverId: data['id'].toString()));
},
),
),
const SizedBox(height: 16),
// Notification is available for everyone
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton.icon(
icon: const Icon(Icons.notifications_active_outlined,
color: Colors.white),
label: Text("Send Notification".tr,
style: const TextStyle(color: Colors.white, fontSize: 16)),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
onPressed: () => _showSendNotificationDialog(controller, data),
),
),
// Edit and Delete ONLY for Super Admin
if (isSuperAdmin) ...[
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.edit_note_rounded, size: 20),
label: Text("Edit".tr),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: AppColor.yellowColor,
elevation: 0,
side: BorderSide(color: AppColor.yellowColor),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () {
Get.to(() => const FormCaptain(), arguments: {
'isEditMode': true,
'captainData': data,
});
},
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
icon: const Icon(Icons.delete_outline_rounded, size: 20),
label: Text("Delete".tr),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[50],
foregroundColor: Colors.red,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
onPressed: () => _showDeleteConfirmation(data),
),
),
],
),
] else ...[
// Message for normal admins
const SizedBox(height: 15),
Text(
"Only Super Admins can edit or delete captains.",
style: TextStyle(
color: Colors.grey[400],
fontSize: 12,
fontStyle: FontStyle.italic),
)
]
],
);
}
// --- Helper Methods ---
String _formatPhoneNumber(String phone, bool isSuperAdmin) {
if (isSuperAdmin) return phone;
if (phone.length <= 4) return phone;
return '${'*' * (phone.length - 4)}${phone.substring(phone.length - 4)}';
}
String _maskEmail(String? email) {
if (email == null || email.isEmpty) return 'N/A';
int atIndex = email.indexOf('@');
if (atIndex <= 1) return email; // Too short to mask
return '${email.substring(0, 2)}****${email.substring(atIndex)}';
}
void _showSendNotificationDialog(
CaptainAdminController controller, Map<String, dynamic> data) {
Get.defaultDialog(
title: 'Send Notification'.tr,
titleStyle: const TextStyle(fontWeight: FontWeight.bold),
content: Form(
key: controller.formCaptainPrizeKey,
child: Column(
children: [
MyTextForm(
controller: controller.titleNotify,
label: 'Title'.tr,
hint: 'Enter notification title'.tr,
type: TextInputType.text,
),
const SizedBox(height: 10),
MyTextForm(
controller: controller.bodyNotify,
label: 'Body'.tr,
hint: 'Enter message body'.tr,
type: TextInputType.text,
),
],
),
),
confirm: SizedBox(
width: 100,
child: MyElevatedButton(
title: 'Send',
onPressed: () {
// Check if key is valid (might be recreated)
if (controller.formCaptainPrizeKey.currentState?.validate() ??
true) {
FirebaseMessagesController().sendNotificationToAnyWithoutData(
controller.titleNotify.text,
controller.bodyNotify.text,
data['passengerToken'] ?? '', // Safety check
'order.wav');
Get.back();
Get.snackbar("Success", "Notification Sent",
backgroundColor: Colors.green.withOpacity(0.2));
}
},
),
),
cancel: TextButton(
onPressed: () => Get.back(),
child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))),
);
}
void _showDeleteConfirmation(Map<String, dynamic> user) {
Get.defaultDialog(
title: 'Confirm Deletion'.tr,
titleStyle:
const TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold),
middleText:
'Are you sure you want to delete ${user['first_name']}? This action cannot be undone.'
.tr,
confirm: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
onPressed: () {
// Call delete function here
// controller.deleteCaptain(user['id']);
Get.back();
Get.snackbar("Deleted", "Captain has been removed",
backgroundColor: Colors.red.withOpacity(0.2));
},
child: Text('Delete'.tr, style: const TextStyle(color: Colors.white)),
),
cancel: TextButton(
onPressed: () => Get.back(),
child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))),
);
}
}
@@ -0,0 +1,81 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../controller/drivers/driver_not_active_controller.dart';
class DriverDetailsPage extends StatelessWidget {
final String driverId;
final DriverController controller = Get.find();
DriverDetailsPage({super.key, required this.driverId});
/// Helper function to safely get String values from dynamic map
String safeVal(Map d, String key) {
final v = d[key];
if (v == null || v == false) return '';
return v.toString();
}
@override
Widget build(BuildContext context) {
controller.getDriverDetails(driverId);
return Scaffold(
appBar: AppBar(title: const Text("Driver Details")),
body: GetBuilder<DriverController>(
id: 'driverDetails',
builder: (c) {
if (c.driverDetails.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
final d = c.driverDetails['driver'] as Map;
final docs = c.driverDetails['documents'] as List;
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Name: ${safeVal(d, 'first_name')} ${safeVal(d, 'last_name')}"),
Text("Phone: ${safeVal(d, 'phone')}"),
Text("Email: ${safeVal(d, 'email')}"),
Text("National Number: ${safeVal(d, 'national_number')}"),
Text("Gender: ${safeVal(d, 'gender')}"),
Text("Birthdate: ${safeVal(d, 'birthdate')}"),
Text("Status: ${safeVal(d, 'status')}"),
Text("License Type: ${safeVal(d, 'license_type')}"),
Text("License Categories: ${safeVal(d, 'license_categories')}"),
Text("Issue Date: ${safeVal(d, 'issue_date')}"),
Text("Expiry Date: ${safeVal(d, 'expiry_date')}"),
Text("Address: ${safeVal(d, 'address')}"),
Text("Site: ${safeVal(d, 'site')}"),
Text("Employment Type: ${safeVal(d, 'employmentType')}"),
Text("Marital Status: ${safeVal(d, 'maritalStatus')}"),
const SizedBox(height: 16),
const Text("Documents:",
style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
...docs.map((doc) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(safeVal(doc, "doc_type")),
const SizedBox(height: 4),
Image.network(
safeVal(doc, "link"),
height: 200,
fit: BoxFit.contain,
errorBuilder: (ctx, err, st) =>
const Icon(Icons.broken_image),
),
const SizedBox(height: 16),
],
)),
],
),
);
},
),
);
}
}
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/views/admin/captain/register_captain.dart';
import 'package:siro_admin/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
import '../../../controller/admin/register_captain_controller.dart';
class DriversCantRegister extends StatelessWidget {
const DriversCantRegister({super.key});
@override
Widget build(BuildContext context) {
Get.put(RegisterCaptainController());
return MyScafolld(
title: 'drivers cant register'.tr,
body: [
GetBuilder<RegisterCaptainController>(builder: (mainController) {
return ListView.builder(
itemCount: mainController.driverNotCompleteRegistration.length,
itemBuilder: (context, index) {
final driver =
mainController.driverNotCompleteRegistration[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () {
Get.to(() => RegisterCaptain(), arguments: {
"phone_number": driver['phone_number'].toString(),
'driverId': driver['driverId'].toString(),
'email': driver['email'].toString(),
});
},
child: Container(
color: driver['note'] == null
? AppColor.greenColor
: AppColor.accentColor,
child: Column(
children: [
Text(driver['phone_number'].toString()),
Text(driver['driverId'].toString()),
Text(driver['email'].toString()),
],
),
),
),
);
},
);
}),
],
isleading: true);
}
}
@@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import '../../../constant/links.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/captain_admin_controller.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/my_textField.dart';
class FormCaptain extends StatefulWidget {
const FormCaptain({super.key});
@override
State<FormCaptain> createState() => _FormCaptainState();
}
class _FormCaptainState extends State<FormCaptain> {
final CaptainAdminController controller = Get.find();
final _formKey = GlobalKey<FormState>();
late TextEditingController firstNameController;
late TextEditingController lastNameController;
late TextEditingController phoneController;
bool isEditMode = false;
Map<String, dynamic>? captainData;
@override
void initState() {
super.initState();
if (Get.arguments != null && Get.arguments['isEditMode'] == true) {
isEditMode = true;
captainData = Get.arguments['captainData'];
firstNameController =
TextEditingController(text: captainData?['first_name'] ?? '');
lastNameController =
TextEditingController(text: captainData?['last_name'] ?? '');
phoneController =
TextEditingController(text: captainData?['phone'] ?? '');
} else {
firstNameController = TextEditingController();
lastNameController = TextEditingController();
phoneController = TextEditingController();
}
}
@override
void dispose() {
firstNameController.dispose();
lastNameController.dispose();
phoneController.dispose();
super.dispose();
}
Future<void> _saveForm() async {
if (_formKey.currentState!.validate()) {
// Create a map of the updated data
Map<String, dynamic> updatedData = {
'id': captainData?['id'], // Important for the WHERE clause in SQL
// 'first_name': firstNameController.text,
// 'last_name': lastNameController.text,
'phone': phoneController.text,
};
var res = await CRUD()
.post(link: AppLink.updateDriverFromAdmin, payload: updatedData);
if (res != 'failure') {
print('Updating data: $updatedData');
Get.back(); // Go back after saving
Get.snackbar('Success', 'Captain data updated successfully!');
}
// controller.updateCaptain(updatedData);
}
}
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Edit Captain'.tr,
isleading: true,
body: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: [
MyTextForm(
controller: firstNameController,
label: 'First Name'.tr,
hint: 'Enter first name'.tr,
type: TextInputType.name,
),
const SizedBox(height: 16),
MyTextForm(
controller: lastNameController,
label: 'Last Name'.tr,
hint: 'Enter last name'.tr,
type: TextInputType.name,
),
const SizedBox(height: 16),
MyTextForm(
controller: phoneController,
label: 'Phone Number'.tr,
hint: 'Enter phone number'.tr,
type: TextInputType.phone,
),
const SizedBox(height: 32),
MyElevatedButton(
title: 'Update'.tr,
onPressed: _saveForm,
),
],
),
),
),
],
);
}
}
@@ -0,0 +1,982 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/controller/admin/register_captain_controller.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/links.dart';
import '../../../../constant/style.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/mycircular.dart';
class RegisterCaptain extends StatelessWidget {
RegisterCaptain({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(RegisterCaptainController());
// String text = '';
controller.driveInit();
return MyScafolld(
title: 'Documents check'.tr,
action: GetBuilder<RegisterCaptainController>(builder: (controller) {
return IconButton(
onPressed: () {
controller.isLoading = false;
controller.update();
},
icon: const Icon(Icons.refresh),
);
}),
body: [
GetBuilder<RegisterCaptainController>(builder: (controller) {
return controller.isLoading
? const MyCircularProgressIndicator()
: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
(controller.responseIdCardDriverEgyptBack.isNotEmpty &&
controller.responseIdCardDriverEgyptFront
.isNotEmpty &&
controller.responseIdEgyptFront.isNotEmpty &&
controller.responseIdEgyptBack.isNotEmpty &&
controller
.responseIdEgyptDriverLicense.isNotEmpty
// &&
// controller
// .responseCriminalRecordEgypt.isNotEmpty
)
? MyElevatedButton(
title: 'Next'.tr,
onPressed: () {
controller.addDriverAndCarEgypt();
})
: const SizedBox(),
SizedBox(
height:
(controller.responseIdCardDriverEgyptBack
.isNotEmpty &&
controller.responseIdCardDriverEgyptFront
.isNotEmpty &&
controller
.responseIdEgyptFront.isNotEmpty &&
controller
.responseIdEgyptBack.isNotEmpty &&
controller.responseIdEgyptDriverLicense
.isNotEmpty
// &&
// controller.responseCriminalRecordEgypt
// .isNotEmpty
)
? Get.height * .7
: Get.height * .85,
child: ListView(
children: [
egyptDriverLicense(),
egyptCarLicenceFront(),
egyptCarLicenceBack(),
egyptDriverIDFront(),
egyptDriverIDBack(),
// egyptCriminalRecord(),
],
),
),
],
),
);
}),
],
isleading: true);
}
GetBuilder<RegisterCaptainController> egyptDriverLicense() {
return GetBuilder<RegisterCaptainController>(
builder: (ai) {
if (ai.responseIdEgyptDriverLicense.isNotEmpty) {
final expiryDate = ai.responseIdEgyptDriverLicense['expiry_date'];
// Check if the expiry date is before today
final today = DateTime.now();
// Try parsing the expiry date. If it fails, set it to null.
final expiryDateTime = DateTime.tryParse(expiryDate);
final isExpired =
expiryDateTime != null && expiryDateTime.isBefore(today);
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Driver\'s License'.tr, style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
await ai.allMethodForAI("""
Write a JSON object from the following information extracted from the provided Arabic text:
{
"license_type": "",
"national_number": "",
"name_arabic": "",
"name_english": "",
"firstName": "",
"lastName": "",
"address": "",
"issue_date": "", // Format: YYYY-MM-DD using Latin numerals (0-9)
"expiry_date": "", // Format: YYYY-MM-DD using Latin numerals (0-9)
"employmentType": "",
"license_categories": []
}
Important notes:
1. Ensure all dates are in the format YYYY-MM-DD using Latin (Western) numerals (0-9), not Arabic numerals.
2. The 'license_categories' should be an array, even if there's only one category.
3. Fill in all fields based on the information provided in the Arabic text.
4. If any information is missing, leave the field as an empty string or empty array as appropriate.
""", 'driver_license', ai.driverId); //egypt
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
Text(
'${'License Type'.tr}: ${ai.responseIdEgyptDriverLicense['license_type']}',
style: AppStyle.title,
),
const SizedBox(height: 8.0),
Text(
'${'National Number'.tr}: ${ai.responseIdEgyptDriverLicense['national_number']}',
style: AppStyle.title.copyWith(
color: ai.responseIdEgyptDriverLicense[
'national_number'] ==
ai.responseIdEgyptBack['nationalID']
? AppColor.greenColor
: AppColor.redColor),
),
const SizedBox(height: 8.0),
Text(
'${'Name (Arabic)'.tr}: ${ai.responseIdEgyptDriverLicense['name_arabic']}',
),
const SizedBox(height: 8.0),
Text(
'${'Name (English)'.tr}: ${ai.responseIdEgyptDriverLicense['name_english']}',
),
const SizedBox(height: 8.0),
Text(
'${'Address'.tr}: ${ai.responseIdEgyptDriverLicense['address']}',
),
const SizedBox(height: 8.0),
Text(
'${'Issue Date'.tr}: ${ai.responseIdEgyptDriverLicense['issue_date']}',
),
const SizedBox(height: 8.0),
Text(
'${'Expiry Date'.tr}: ${ai.responseIdEgyptDriverLicense['expiry_date']}',
style: AppStyle.title.copyWith(
color:
!isExpired ? AppColor.greenColor : AppColor.redColor,
),
),
const SizedBox(height: 8.0),
Text(
'${'License Categories'.tr}: ${ai.responseIdEgyptDriverLicense['license_categories']}',
),
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
await ai.allMethodForAI("""
Write a JSON object from the following information extracted from the provided Arabic text:
{
"license_type": "",
"national_number": "",
"name_arabic": "",
"name_english": "",
"firstName": "",
"lastName": "",
"address": "",
"issue_date": "", // Format: YYYY-MM-DD using Latin numerals (0-9)
"expiry_date": "", // Format: YYYY-MM-DD using Latin numerals (0-9)
"employmentType": "",
"license_categories": []
}
Important notes:
1. Ensure all dates are in the format YYYY-MM-DD using Latin (Western) numerals (0-9), not Arabic numerals.
2. The 'license_categories' should be an array, even if there's only one category.
3. Fill in all fields based on the information provided in the Arabic text.
4. If any information is missing, leave the field as an empty string or empty array as appropriate.
""", 'driver_license', ai.driverId); //egypt
},
child: Column(
children: [
Image.network(
'${AppLink.server}/card_image/driver_license-${ai.driverId}.jpg',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your Driver License'.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}
GetBuilder<RegisterCaptainController> egyptDriverIDBack() {
return GetBuilder<RegisterCaptainController>(
builder: (ai) {
if (ai.responseIdEgyptBack.isNotEmpty) {
final taxExpiryDate = ai.responseIdEgyptBack['expirationDate'];
// Check if the tax expiry date is before today
final today = DateTime.now();
// Try parsing the tax expiry date. If it fails, set it to null.
final taxExpiryDateTime = DateTime.tryParse(taxExpiryDate);
final isExpired =
taxExpiryDateTime != null && taxExpiryDateTime.isBefore(today);
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('ID Documents Back'.tr, style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
await ai.allMethodForAI("""
Write a JSON from the following information extracted from the provided Arabic text:
- nationalID(in Latin numerals)
- issueDate (in format YYYY-MM-DD using Latin numerals)
- occupation
- gender
- religion
- maritalStatus
- fullNameMarital (if maritalStatus is "أعزب", set this to "none")
- expirationDate (in format YYYY-MM-DD using Latin numerals)
Please ensure all date fields use Latin (Western) numerals (0-9) instead of Arabic numerals. For example, use "2023-04-03" instead of "٢٠٢٣-٠٤-٠٣".
""", 'id_back', ai.driverId); //egypt
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
// Assuming these keys exist in ai.responseIdEgyptFront
Text(
'${'National ID'.tr}: ${ai.responseIdEgyptBack['nationalID']}',
style: AppStyle.title.copyWith(
color: ai.responseIdEgyptDriverLicense[
'national_number'] ==
ai.responseIdEgyptBack['nationalID']
? AppColor.greenColor
: AppColor.redColor),
),
const SizedBox(height: 8.0),
Text(
'${'Occupation'.tr}: ${ai.responseIdEgyptBack['occupation']}', // Assuming 'occupation' exists
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Issue Date'.tr}: ${ai.responseIdEgyptBack['issueDate']}', // Assuming 'issueDate' exists
),
Text(
'${'Gender'.tr}: ${ai.responseIdEgyptBack['gender']}', // Assuming 'gender' exists
),
],
),
const SizedBox(height: 8.0),
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// '${'Religion'.tr}: ${ai.responseIdEgyptBack['religion']}', // Assuming 'religion' exists
// ),
// Text(
// '${'Marital Status'.tr}: ${ai.responseIdEgyptBack['maritalStatus']}', // Assuming 'maritalStatus' exists
// ),
// ],
// ),
// const SizedBox(height: 8.0),
// Text(
// '${'Full Name (Marital)'.tr}: ${ai.responseIdEgyptBack['fullNameMaritial']}', // Assuming 'fullNameMaritial' exists
// ),
// const SizedBox(height: 8.0),
Text(
'${'Expiration Date'.tr}: ${ai.responseIdEgyptBack['expirationDate']}', // Assuming 'expirationDate' exists
style: AppStyle.title.copyWith(
color: !isExpired
? AppColor.greenColor
: AppColor.redColor),
),
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
await ai.allMethodForAI('''
Write a JSON object from the following information extracted from the provided Arabic text:
{
"nationalID": "",//(in Latin numerals)
"issueDate": "", // Format: YYYY-MM-DD using Latin numerals (0-9)
"occupation": "",
"gender": "",
"religion": "",
"maritalStatus": "",
"fullNameMaritial": "", // Set to "none" if maritalStatus is "أعزب"
"expirationDate": "" // Format: YYYY-MM-DD using Latin numerals (0-9)
}
Important notes:
1. Ensure all dates (issueDate and expirationDate) are in the format YYYY-MM-DD using Latin (Western) numerals (0-9), not Arabic numerals.
2. If maritalStatus is "أعزب" (single), set fullNameMaritial to "none".
3. Fill in all fields based on the information provided in the Arabic text.
4. If any information is missing, leave the field as an empty string.
''', 'id_back', ai.driverId); //egypt
},
child: Column(
children: [
Image.network(
'${AppLink.server}/card_image/id_back-${ai.driverId}.jpg',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your ID Document Back'.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}
GetBuilder<RegisterCaptainController> egyptDriverIDFront() {
return GetBuilder<RegisterCaptainController>(
builder: (ai) {
if (ai.responseIdEgyptFront.isNotEmpty) {
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('ID Documents Front'.tr, style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
await ai.allMethodForAI('''
Write a JSON object from the following information extracted from the provided Arabic text:
{
"first_name": "", // The word next to "بطاقة تحقيق الشخصية" (National Identification Card)
"full_name": "", // The full name on the next line after the first name
"address": "", // The complete address spanning the next two lines
"national_number": "", // The National ID number before the last line (convert Arabic numerals to Latin)
"card_id": "", // The card ID in English on the last line
"dob": "" // Year of birth only, in Latin numerals (YYYY format)
}
Important notes:
1. For 'first_name', extract the word immediately following "بطاقة تحقيق الشخصية".
2. 'full_name' should be the complete name found on the line after the first name.
3. 'address' should combine information from two consecutive lines.
4. Convert the 'national_number' from Arabic numerals to Latin numerals (0-9).
5. 'card_id' should be extracted as-is from the last line (it's already in English).
6. For 'dob', include only the year of birth in YYYY format using Latin numerals.
7. If any information is missing, leave the field as an empty string.
''', 'id_front', ai.driverId); //egypt
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
// Removed Make, Model, etc. as they are not available
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'First Name'.tr}: ${ai.responseIdEgyptFront['first_name']}',
),
Text(
'${'CardID'.tr}: ${ai.responseIdEgyptFront['card_id']}',
),
],
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Full Name'.tr}: ${ai.responseIdEgyptFront['full_name']}',
),
Text(
'${'DOB'.tr}: ${ai.responseIdEgyptFront['dob']}',
),
],
),
const SizedBox(height: 8.0),
Text(
'${'Address'.tr}: ${ai.responseIdEgyptFront['address']}',
),
const SizedBox(height: 8.0),
// Text(
// '${'National Number'.tr}: ${ai.responseIdEgyptFront['national_number']}',
// ),
// const SizedBox(height: 8.0),
// Removed Inspection Date as it's not available
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
await ai.allMethodForAI(""""
Write a JSON object from the following information extracted from the provided Arabic text:
{
"first_name": "", // The word next to "بطاقة تحقيق الشخصية" (National Identification Card)
"full_name": "", // The full name on the next line after the first name
"address": "", // The complete address spanning the next two lines
"national_number": "", // The National ID number before the last line (convert Arabic numerals to Latin)
"card_id": "", // The card ID in English on the last line
"dob": "" // Year of birth only, in Latin numerals (YYYY format)
}
Important notes:
1. For 'first_name', extract the word immediately following "بطاقة تحقيق الشخصية".
2. 'full_name' should be the complete name found on the line after the first name.
3. 'address' should combine information from two consecutive lines.
4. Convert the 'national_number' from Arabic numerals to Latin numerals (0-9).
5. 'card_id' should be extracted as-is from the last line (it's already in English).
6. For 'dob', include only the year of birth in YYYY format using Latin numerals.
7. If any information is missing, leave the field as an empty string.
""", 'id_front', ai.driverId); //egypt
},
child: Column(
children: [
Image.network(
'${AppLink.server}/card_image/id_front-${ai.driverId}.png',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your ID Document front'.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}
GetBuilder<RegisterCaptainController> egyptCarLicenceFront() {
return GetBuilder<RegisterCaptainController>(
builder: (ai) {
if (ai.responseIdCardDriverEgyptFront.isNotEmpty) {
// No need to access ai.responseIdCardDriverEgyptBack anymore
final licenseExpiryDate = DateTime.parse(
ai.responseIdCardDriverEgyptFront['LicenseExpirationDate']);
// Check if license has expired
final today = DateTime.now();
final isLicenseExpired = licenseExpiryDate.isBefore(today);
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('Vehicle Details Front'.tr,
style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
ai.allMethodForAI("""
Extract the following details from the provided car license data and format them into a JSON object:
License Expiration Date
Car Plate
Owner
Address
Car License Data:
JSON Format:
{
"LicenseExpirationDate": "YYYY-MM-DD",
"car_plate": "[Car plate number]",//the car plate is line next to line contain 'ادارة مرور' for bot numbers and letters in arabic with partition like| but you remove |
"owner": "[Owner's full name]",
"address": "[Address if available, otherwise 'Not provided']"
}
Important notes:
1. For the LicenseExpirationDate, ensure the date is in YYYY-MM-DD format using Latin numerals (0-9).
2. Replace all occurrences of '|' (pipe character) with a space in all fields.
3. If any information is missing, leave the corresponding field as an empty string.
4. Ensure all text is properly formatted and spaces are used correctly.
Please fill in the JSON object with the extracted information, following these guidelines.
""", 'car_front', ai.driverId);
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
// Removed Make, Model, etc. as they are not available
Text(
'${'Plate Number'.tr}: ${ai.responseIdCardDriverEgyptFront['car_plate']}',
),
const SizedBox(height: 8.0),
Text(
'${'Owner Name'.tr}: ${ai.responseIdCardDriverEgyptFront['owner']}',
),
const SizedBox(height: 8.0),
Text(
'${'Address'.tr}: ${ai.responseIdCardDriverEgyptFront['address']}',
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'License Expiry Date'.tr}: ${licenseExpiryDate.toString().substring(0, 10)}',
style: TextStyle(
color: isLicenseExpired ? Colors.red : Colors.green,
),
),
// Removed Fuel as it's not available
],
),
// Removed Inspection Date as it's not available
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
ai.allMethodForAI("""
Extract the following details from the provided car license data and format them into a JSON object:
License Expiration Date
Car Plate
Owner
Address
Car License Data:
JSON Format:
{
"LicenseExpirationDate": "YYYY-MM-DD",
"car_plate": "[Car plate number]",//the car plate is line next to line contain 'ادارة مرور' for bot numbers and letters in arabic with partition like| but you remove |
"owner": "[Owner's full name]",
"address": "[Address if available, otherwise 'Not provided']"
}
Important notes:
1. For the LicenseExpirationDate, ensure the date is in YYYY-MM-DD format using Latin numerals (0-9).
2. Replace all occurrences of '|' (pipe character) with a space in all fields.
3. If any information is missing, leave the corresponding field as an empty string.
4. Ensure all text is properly formatted and spaces are used correctly.
Please fill in the JSON object with the extracted information, following these guidelines.
""", 'car_front', ai.driverId);
},
child: Column(
children: [
Image.network(
'${AppLink.server}/card_image/car_front-${ai.driverId}.jpg',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your car license front '.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}
GetBuilder<RegisterCaptainController> egyptCarLicenceBack() {
return GetBuilder<RegisterCaptainController>(
builder: (ai) {
if (ai.responseIdCardDriverEgyptBack.isNotEmpty) {
// Get the tax expiry date from the response
final taxExpiryDate = ai.responseIdCardDriverEgyptBack['tax_expiry'];
// final displacement = ai.responseIdCardDriverEgyptBack['displacement'];
// if (int.parse(displacement) < 1000) {}
// Get the inspection date from the response
final inspectionDate =
ai.responseIdCardDriverEgyptBack['inspection_date'];
final year = int.parse(inspectionDate.split('-')[0]);
// Set inspectionDateTime to December 31st of the given year
final inspectionDateTime = DateTime(year, 12, 31);
String carBackLicenseExpired =
inspectionDateTime.toString().split(' ')[0];
// Get the current date
final today = DateTime.now();
// Try parsing the tax expiry date. If it fails, set it to null.
final taxExpiryDateTime = DateTime.tryParse(taxExpiryDate ?? '');
final isExpired =
taxExpiryDateTime != null && taxExpiryDateTime.isBefore(today);
// Check if the inspection date is before today
bool isInspectionExpired = inspectionDateTime.isBefore(today);
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Vehicle Details Back'.tr,
style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
ai.allMethodForAI("""
Analyze the extracted car license information and create a JSON object with the following keys:
{
"make": "",
"year": "",
"chassis": "",
"model": "",
"engine": "",
"displacement": "",
"cylinders": "",
"fuel": "",
"color": "",
"color_hex": "",
"inspection_date": "",
"assuranceNumber": "",
"tax_expiry": ""
}
Important notes:
1. For dates (inspection_date and tax_expiry), use the format YYYY-MM-DD with Latin numerals (0-9).
2. Convert the color name to its corresponding hex color code for the 'color_hex' field.
3. Ensure all numeric values (year, displacement, cylinders) are in Latin numerals.
4. If any information is missing, leave the corresponding field as an empty string.
5. Do not include any explanatory text in the JSON fields, only the extracted values.
displacement in the line contain (سم٣ )
Please fill in the JSON object with the extracted information, following these guidelines.
""", 'car_back', ai.driverId);
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Make'.tr}: ${ai.responseIdCardDriverEgyptBack['make']}'),
Text(
'${'Model'.tr}: ${ai.responseIdCardDriverEgyptBack['model']}'),
],
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Year'.tr}: ${ai.responseIdCardDriverEgyptBack['year']}'),
Text(
'${'Chassis'.tr}: ${ai.responseIdCardDriverEgyptBack['chassis']}'),
],
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Color'.tr}: ${ai.responseIdCardDriverEgyptBack['color']}'),
Text(
'${'Displacement'.tr}: ${ai.responseIdCardDriverEgyptBack['displacement']} cc'),
],
),
const SizedBox(height: 8.0),
Text(
'${'Fuel'.tr}: ${ai.responseIdCardDriverEgyptBack['fuel']}'),
const SizedBox(height: 8.0),
if (taxExpiryDateTime != null)
Text(
'${'Tax Expiry Date'.tr}: $taxExpiryDate',
style: TextStyle(
color: isExpired ? Colors.red : Colors.green,
),
),
const SizedBox(height: 8.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${'Inspection Date'.tr}: $carBackLicenseExpired',
style: TextStyle(
color:
isInspectionExpired ? Colors.red : Colors.green,
),
),
],
),
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
ai.allMethodForAI("""
Analyze the extracted car license information and create a JSON object with the following keys:
{
"make": "",
"year": "",
"chassis": "",
"model": "",
"engine": "",
"displacement": "",
"cylinders": "",
"fuel": "",
"color": "",
"color_hex": "",
"inspection_date": "",
"assuranceNumber": "",
"tax_expiry": ""
}
Important notes:
1. For dates (inspection_date and tax_expiry), use the format YYYY-MM-DD with Latin numerals (0-9).
2. Convert the color name to its corresponding hex color code for the 'color_hex' field.
3. Ensure all numeric values (year, displacement, cylinders) are in Latin numerals.
4. If any information is missing, leave the corresponding field as an empty string.
5. Do not include any explanatory text in the JSON fields, only the extracted values.
Please fill in the JSON object with the extracted information, following these guidelines.
""", 'car_back', ai.driverId);
},
child: Column(
children: [
Image.network(
'${AppLink.server}/card_image/car_back-${ai.driverId}.jpg',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your car license back'.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}
GetBuilder<RegisterCaptainController> egyptCriminalRecord() {
return GetBuilder<RegisterCaptainController>(
builder: (ai) {
if (ai.responseCriminalRecordEgypt.isNotEmpty) {
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Criminal Record'.tr, style: AppStyle.headTitle2),
IconButton(
onPressed: () async {
await ai.allMethodForAI("""
Write a JSON object from the following information extracted from the provided Arabic text:
{
"InspectionResult": "",
"NationalID": "",
"FullName": "",
"IssueDate": "" // Format: YYYY-MM-DD
}
Important notes:
1. For the IssueDate, ensure the date is in YYYY-MM-DD format using Latin numerals (0-9).
2. Add appropriate spaces in all text fields to ensure readability.
3. If any information is missing, leave the corresponding field as an empty string.
4. Ensure all text is properly formatted and spaces are used correctly.
5. Convert any Arabic numerals to Latin numerals (0-9) where applicable.
Please fill in the JSON object with the extracted information, following these guidelines.
""", 'criminalRecord', ai.driverId);
},
icon: const Icon(Icons.refresh),
),
],
),
const SizedBox(height: 8.0),
const Divider(color: AppColor.accentColor),
const SizedBox(height: 8.0),
Text(
'${'InspectionResult'.tr}: ${ai.responseCriminalRecordEgypt['InspectionResult']}'),
const SizedBox(height: 8.0),
Text(
'${'FullName'.tr}: ${ai.responseCriminalRecordEgypt['FullName']}',
style: AppStyle.title.copyWith(
color: ai.responseCriminalRecordEgypt['FullName'] ==
ai.responseIdEgyptDriverLicense['name_arabic']
? AppColor.greenColor
: AppColor.redColor),
),
const SizedBox(height: 8.0),
Text(
'${'NationalID'.tr}: ${ai.responseCriminalRecordEgypt['NationalID']}'),
const SizedBox(height: 8.0),
Text(
'${'IssueDate'.tr}: ${ai.responseCriminalRecordEgypt['IssueDate']}'),
],
),
),
);
}
return Card(
child: InkWell(
onTap: () async {
await ai.allMethodForAI("""
Write a JSON object from the following information extracted from the provided Arabic text:
{
"InspectionResult": "",
"NationalID": "",
"FullName": "",
"IssueDate": "" // Format: YYYY-MM-DD
}
Important notes:
1. For the IssueDate, ensure the date is in YYYY-MM-DD format using Latin numerals (0-9).
2. Add appropriate spaces in all text fields to ensure readability.
3. If any information is missing, leave the corresponding field as an empty string.
4. Ensure all text is properly formatted and spaces are used correctly.
5. Convert any Arabic numerals to Latin numerals (0-9) where applicable.
Please fill in the JSON object with the extracted information, following these guidelines.
""", 'criminalRecord', ai.driverId);
},
child: Column(
children: [
Image.network(
'${AppLink.server}/card_image/6.png',
height: Get.height * .25,
width: double.maxFinite,
fit: BoxFit.fitHeight,
),
Text(
'Capture an Image of Your Criminal Record'.tr,
style: AppStyle.title,
),
],
),
),
);
},
);
}
}
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import 'package:siro_admin/constant/box_name.dart';
import '../../../constant/info.dart';
import '../../../constant/char_map.dart';
import '../../../controller/drivers/driver_not_active_controller.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
import '../../../main.dart';
import '../../../print.dart';
import 'driver_details_not_active_page.dart';
class DriversPendingPage extends StatelessWidget {
final DriverController controller = Get.put(DriverController());
DriversPendingPage({super.key});
@override
Widget build(BuildContext context) {
controller.getDriversPending();
Log.print(
': ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}');
return Scaffold(
appBar: AppBar(title: const Text("Drivers Pending")),
body: GetBuilder<DriverController>(
id: 'drivers',
builder: (c) {
if (c.drivers.isEmpty) {
return Center(
child: const Text('no drivers found yet',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
);
}
return ListView.builder(
itemCount: c.drivers.length,
itemBuilder: (ctx, i) {
final d = c.drivers[i];
return ListTile(
title: Text(d["first_name"] + d['last_name'] ?? ""),
subtitle: Text(d["phone"] ?? ""),
onTap: () {
Get.to(() => DriverDetailsPage(driverId: d["id"].toString()));
},
);
},
);
},
),
);
}
}
@@ -0,0 +1,287 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/complaint_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_textField.dart';
class ComplaintListPage extends StatelessWidget {
ComplaintListPage({super.key});
final ComplaintController controller = Get.put(ComplaintController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'إدارة الشكاوى'.tr,
isleading: true,
body: [
Obx(() {
if (controller.delayedComplaints.isNotEmpty) {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.danger.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.danger.withOpacity(0.3)),
),
child: Column(
children: [
Row(
children: [
const Icon(Icons.warning_amber_rounded, color: AppColor.danger),
const SizedBox(width: 12),
Expanded(
child: Text(
'هناك ${controller.delayedComplaints.length} شكاوى لم يتم حلها منذ أكثر من أسبوع!',
style: AppStyle.body.copyWith(color: AppColor.danger, fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 12),
Obx(() => MyElevatedButton(
title: controller.showOnlyDelayed.value ? 'عرض جميع الشكاوى' : 'عرض الشكاوى المتأخرة فقط',
onPressed: () => controller.showOnlyDelayed.toggle(),
kolor: AppColor.danger,
)),
],
),
);
}
return const SizedBox.shrink();
}),
Obx(() {
final list = controller.showOnlyDelayed.value ? controller.delayedComplaints : controller.complaintList;
if (controller.isLoading.value && list.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
return RefreshIndicator(
onRefresh: () => controller.getComplaints(),
child: ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
itemCount: list.length,
itemBuilder: (context, index) {
final complaint = list[index];
return _buildComplaintCard(context, complaint);
},
),
);
}),
],
);
}
Widget _buildComplaintCard(BuildContext context, dynamic c) {
Color statusColor = _getStatusColor(c['statusComplaint']);
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: AppStyle.cardDecoration,
child: ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: _buildStatusIndicator(c['statusComplaint'], statusColor),
title: Text(
c['description']?.toString() ?? 'بدون وصف',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: AppStyle.title,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
'النوع: ${c['complaint_type'] ?? 'عام'} | الرحلة: ${c['ride_id']}',
style: AppStyle.caption,
),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.person_rounded, size: 12, color: AppColor.textSecondary),
const SizedBox(width: 4),
Text(c['passengerName'] ?? 'غير معروف', style: AppStyle.caption),
const SizedBox(width: 12),
Icon(Icons.drive_eta_rounded, size: 12, color: AppColor.textSecondary),
const SizedBox(width: 4),
Text(c['driverName'] ?? 'غير معروف', style: AppStyle.caption),
],
),
],
),
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider(color: AppColor.divider),
_buildInfoRow('الوصف', c['description'] ?? 'لا يوجد وصف'),
const SizedBox(height: 12),
_buildInfoRow('الحل الحالي', c['resolution'] ?? 'لم يتم الحل بعد'),
const SizedBox(height: 12),
_buildRideDetails(c),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: MyElevatedButton(
title: 'تحديث الحالة / حل الشكوى',
onPressed: () => _showResolveDialog(context, c),
kolor: AppColor.accent,
),
),
],
),
],
),
),
],
),
);
}
Widget _buildStatusIndicator(String? status, Color color) {
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color.withOpacity(0.12),
shape: BoxShape.circle,
border: Border.all(color: color.withOpacity(0.25)),
),
child: Icon(
status == 'Resolved' ? Icons.check_circle_rounded : Icons.pending_rounded,
color: color,
size: 20,
),
);
}
Widget _buildInfoRow(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: AppStyle.caption.copyWith(color: AppColor.accent)),
const SizedBox(height: 4),
Text(value, style: AppStyle.body),
],
);
}
Widget _buildRideDetails(dynamic c) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.surfaceElevated,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.divider),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildSmallStat('السعر', '${c['priceOfRide']} ل.س'),
_buildSmallStat('التقييم', '${c['avgRatingDriverFromPassengers'] ?? 0}★'),
_buildSmallStat('النوع', c['ascarType'] ?? 'N/A'),
],
),
],
),
);
}
Widget _buildSmallStat(String label, String value) {
return Column(
children: [
Text(label, style: AppStyle.caption.copyWith(fontSize: 10)),
const SizedBox(height: 2),
Text(value, style: AppStyle.number.copyWith(fontSize: 12)),
],
);
}
Color _getStatusColor(String? status) {
switch (status) {
case 'Open': return AppColor.danger;
case 'In Progress': return AppColor.warning;
case 'Resolved': return AppColor.success;
default: return AppColor.textSecondary;
}
}
void _showResolveDialog(BuildContext context, dynamic c) {
final TextEditingController resController = TextEditingController(text: c['resolution']);
String selectedStatus = c['statusComplaint'] ?? 'Open';
Get.bottomSheet(
StatefulBuilder(
builder: (context, setModalState) => Container(
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
color: AppColor.surfaceElevated,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('تحديث حالة الشكوى', style: AppStyle.headTitle),
const SizedBox(height: 20),
_buildStatusDropdown(selectedStatus, (val) {
setModalState(() => selectedStatus = val!);
}),
const SizedBox(height: 20),
MyTextForm(
controller: resController,
label: 'قرار الحل / الملاحظات',
hint: 'اكتب تفاصيل الحل هنا...',
type: TextInputType.multiline,
prefixIcon: Icons.gavel_rounded,
),
const SizedBox(height: 24),
MyElevatedButton(
title: 'حفظ التحديث',
onPressed: () async {
bool success = await controller.updateComplaintStatus(
c['id'].toString(),
selectedStatus,
resController.text
);
if (success) Get.back();
},
),
const SizedBox(height: 20),
],
),
),
),
isScrollControlled: true,
);
}
Widget _buildStatusDropdown(String current, Function(String?) onChanged) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.divider),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: current,
isExpanded: true,
dropdownColor: AppColor.surfaceElevated,
items: ['Open', 'In Progress', 'Resolved']
.map((s) => DropdownMenuItem(value: s, child: Text(s.tr, style: AppStyle.body)))
.toList(),
onChanged: onChanged,
),
),
);
}
}
@@ -0,0 +1,339 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
class DashboardV2Widget extends StatelessWidget {
const DashboardV2Widget({super.key});
@override
Widget build(BuildContext context) {
// Initialize controller
final controller = Get.put(DashboardV2Controller());
return GetBuilder<DashboardV2Controller>(
builder: (ctrl) {
if (ctrl.isLoading) {
return const SliverToBoxAdapter(
child: SizedBox(
height: 150,
child: Center(
child: CircularProgressIndicator(color: AppColor.accent),
),
),
);
}
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 1. Real-time stats
_buildSectionTitle('مركز العمليات الحي (Real-time)'),
_buildRealtimeStats(ctrl.realtimeData),
const SizedBox(height: 20),
// 2. Smart Alerts
if (ctrl.smartAlerts.isNotEmpty) ...[
_buildSectionTitle('التنبيهات الذكية (${ctrl.smartAlerts.length})'),
_buildSmartAlerts(ctrl.smartAlerts),
const SizedBox(height: 10),
]
],
),
);
},
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 10),
child: Row(
children: [
Container(
width: 3,
height: 14,
decoration: BoxDecoration(
color: AppColor.danger, // Distinct color
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
title,
style: const TextStyle(
color: AppColor.textSecondary,
fontSize: 12,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
],
),
);
}
Widget _buildRealtimeStats(Map<String, dynamic> data) {
final stats = [
{
'title': 'رحلات نشطة',
'value': data['active_rides']?.toString() ?? '0',
'icon': Icons.directions_car_rounded,
'color': AppColor.info,
},
{
'title': 'سائقون أونلاين',
'value': data['online_drivers']?.toString() ?? '0',
'icon': Icons.wifi_tethering,
'color': AppColor.success,
},
{
'title': 'إيرادات اليوم',
'value': '${data['revenue_today'] ?? 0}',
'icon': Icons.monetization_on_rounded,
'color': AppColor.warning,
},
{
'title': 'إيرادات الأمس',
'value': '${data['revenue_yesterday'] ?? 0}',
'icon': Icons.history_rounded,
'color': Colors.grey,
},
{
'title': 'شكاوى مفتوحة',
'value': data['new_complaints']?.toString() ?? '0',
'icon': Icons.warning_rounded,
'color': AppColor.danger,
},
{
'title': 'رخص تنتهي قريبًا',
'value': data['expiring_licenses']?.toString() ?? '0',
'icon': Icons.sd_card_alert_rounded,
'color': Colors.orangeAccent,
},
];
return SizedBox(
height: 110,
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
scrollDirection: Axis.horizontal,
itemCount: stats.length,
itemBuilder: (ctx, i) {
final stat = stats[i];
return Padding(
padding: const EdgeInsets.only(right: 10),
child: _buildStatCard(stat),
);
},
),
);
}
Widget _buildStatCard(Map<String, dynamic> stat) {
final color = stat['color'] as Color;
return Container(
width: 140,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: color.withOpacity(0.3)),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Icon(stat['icon'] as IconData, color: color, size: 16),
),
const Spacer(),
// Pulsing indicator for active things
if (stat['title'] == 'رحلات نشطة' || stat['title'] == 'سائقون أونلاين')
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [
BoxShadow(
color: color.withOpacity(0.5),
blurRadius: 4,
spreadRadius: 1,
)
]
),
),
],
),
const Spacer(),
Text(
stat['value'].toString(),
style: const TextStyle(
color: AppColor.textPrimary,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 2),
Text(
stat['title'].toString(),
style: const TextStyle(
color: AppColor.textSecondary,
fontSize: 10,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
Widget _buildSmartAlerts(List<dynamic> alerts) {
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: alerts.length > 5 ? 5 : alerts.length, // Show top 5
itemBuilder: (context, index) {
final alert = alerts[index];
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 300),
child: SlideAnimation(
verticalOffset: 20.0,
child: FadeInAnimation(
child: _buildAlertItem(alert),
),
),
);
},
);
}
Widget _buildAlertItem(Map<String, dynamic> alert) {
Color getSeverityColor(String severity) {
switch (severity) {
case 'high':
return AppColor.danger;
case 'medium':
return AppColor.warning;
case 'warning':
return Colors.orangeAccent;
default:
return AppColor.info;
}
}
IconData getAlertIcon(String type) {
switch (type) {
case 'complaint':
return Icons.report_problem_rounded;
case 'ride':
return Icons.directions_car_rounded;
case 'license':
return Icons.badge_rounded;
default:
return Icons.notifications_active_rounded;
}
}
final color = getSeverityColor(alert['severity']);
final icon = getAlertIcon(alert['type']);
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.4)),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.05),
blurRadius: 5,
offset: const Offset(0, 2),
)
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
alert['title'] ?? '',
style: const TextStyle(
color: AppColor.textPrimary,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
alert['description'] ?? '',
style: const TextStyle(
color: AppColor.textSecondary,
fontSize: 11,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
const SizedBox(width: 8),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
alert['date'] != null
? alert['date'].toString().split(' ')[0]
: '',
style: const TextStyle(
color: AppColor.textSecondary,
fontSize: 10,
),
),
const SizedBox(height: 10),
Icon(Icons.arrow_forward_ios_rounded,
color: AppColor.textSecondary, size: 12),
],
),
],
),
);
}
}
@@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart'; // For Get.width if needed, and .tr
import 'package:siro_admin/constant/colors.dart'; // Assuming AppColor is here
import 'package:siro_admin/constant/style.dart'; // Assuming AppStyle is here
class DashboardStatCard extends StatelessWidget {
final String title;
final String value;
final IconData? icon;
final Color? iconColor;
final Color? backgroundColor;
final Color? valueColor;
const DashboardStatCard({
Key? key,
required this.title,
required this.value,
this.icon,
this.iconColor,
this.backgroundColor,
this.valueColor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
// Attempt to use AppStyle.boxDecoration1 properties if it's a BoxDecoration
BoxDecoration? baseDecoration = AppStyle.boxDecoration1;
Color? finalBackgroundColor =
backgroundColor ?? baseDecoration?.color ?? Theme.of(context).cardColor;
BorderRadius? finalBorderRadius =
baseDecoration?.borderRadius?.resolve(Directionality.of(context)) ??
BorderRadius.circular(12.0);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0),
decoration: BoxDecoration(
color: finalBackgroundColor,
borderRadius: finalBorderRadius,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 6,
offset: const Offset(0, 2),
),
],
// If AppStyle.boxDecoration1 includes a border, you might want to add it here too
// border: baseDecoration?.border,
),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center, // Center content vertically
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Text(
title.tr,
style: AppStyle.title.copyWith(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodySmall?.color,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
if (icon != null)
Icon(
icon,
size: 24,
color: iconColor ?? AppColor.primaryColor.withOpacity(0.7),
),
],
),
const SizedBox(height: 6),
Text(
value,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: valueColor ?? AppColor.primaryColor,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
}
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/controller/functions/wallet.dart';
import 'package:siro_admin/views/widgets/my_scafold.dart';
import '../../../controller/drivers/driverthebest.dart';
class DriverTheBestAlexandria extends StatelessWidget {
const DriverTheBestAlexandria({super.key});
@override
Widget build(BuildContext context) {
Get.put(DriverTheBestAlexandriaController(), permanent: true);
return MyScafolld(
title: 'Alexandria'.tr,
body: [
GetBuilder<DriverTheBestAlexandriaController>(builder: (driverthebest) {
return driverthebest.driver.isNotEmpty
? ListView.builder(
itemCount: driverthebest.driver.length,
itemBuilder: (context, index) {
final driver = driverthebest.driver[index];
return ListTile(
leading: CircleAvatar(
child: Text(
((driver['driver_count'] * 5) / 3600)
.toStringAsFixed(0),
),
),
title: Text(driver['name_arabic'] ?? 'Unknown Name'),
subtitle: Text('Phone: ${driver['phone'] ?? 'N/A'}'),
trailing: IconButton(
onPressed: () async {
Get.defaultDialog(
title:
'are you sure to pay to this driver gift'.tr,
middleText: '',
onConfirm: () async {
final wallet = Get.put(WalletController());
await wallet.addPaymentToDriver('100',
driver['id'].toString(), driver['token']);
await wallet.addSeferWallet(
'100', driver['id'].toString());
await CRUD().post(
link: AppLink.deleteRecord,
payload: {
'driver_id': driver['id'].toString()
});
driverthebest.driver.removeAt(index);
driverthebest.update();
},
onCancel: () => Get.back());
},
icon: const Icon(Icons.wallet_giftcard_rounded),
),
);
},
)
: const Center(
child: Text('No drivers available.'),
);
})
],
isleading: true,
);
}
}
@@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/driver_docs_controller.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/elevated_btn.dart';
import '../../../constant/links.dart';
class DriverDocsReviewPage extends StatelessWidget {
DriverDocsReviewPage({super.key});
final DriverDocsController controller = Get.put(DriverDocsController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'مراجعة طلبات التسجيل'.tr,
isleading: true,
body: [
Obx(() => controller.isLoading.value && controller.pendingDrivers.isEmpty
? const Center(child: CircularProgressIndicator())
: controller.pendingDrivers.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.how_to_reg_rounded, size: 64, color: AppColor.textMuted),
const SizedBox(height: 16),
Text('لا يوجد طلبات تسجيل حالياً', style: AppStyle.subtitle),
],
),
)
: RefreshIndicator(
onRefresh: () => controller.getPendingDrivers(),
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (!controller.isLoading.value &&
!controller.isMoreLoading.value &&
scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 200) {
controller.loadMore();
}
return false;
},
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: controller.pendingDrivers.length + (controller.hasMore.value ? 1 : 0),
itemBuilder: (context, index) {
if (index == controller.pendingDrivers.length) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
);
}
final driver = controller.pendingDrivers[index];
return _buildDriverCard(context, driver);
},
),
),
)),
],
);
}
Widget _buildDriverCard(BuildContext context, dynamic driver) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: AppStyle.cardDecoration,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: CircleAvatar(
backgroundColor: AppColor.accentSoft,
child: Text(driver['first_name']?[0] ?? 'D', style: const TextStyle(color: AppColor.accent)),
),
title: Text('${driver['first_name']} ${driver['last_name']}', style: AppStyle.title),
subtitle: Text(driver['phone'] ?? '', style: AppStyle.caption),
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16, color: AppColor.textSecondary),
onTap: () => _showDriverDetails(context, driver['id'].toString()),
),
);
}
void _showDriverDetails(BuildContext context, String id) async {
final details = await controller.getDriverFullDetails(id);
if (details == null) return;
final driver = details['driver'];
final List docs = details['documents'];
Get.to(() => MyScafolld(
title: 'تفاصيل السائق',
isleading: true,
body: [
SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDriverHeader(driver),
const SizedBox(height: 24),
Text('الوثائق المرفوعة', style: AppStyle.title),
const SizedBox(height: 12),
...docs.map((doc) => _buildDocCard(doc)).toList(),
const SizedBox(height: 32),
MyElevatedButton(
title: 'اعتماد وتفعيل الحساب',
icon: Icons.check_circle_rounded,
kolor: AppColor.success,
onPressed: () async {
bool success = await controller.approveDriver(id);
if (success) {
Get.back();
Get.snackbar('نجاح', 'تم تفعيل حساب السائق بنجاح');
}
},
),
const SizedBox(height: 100),
],
),
),
],
));
}
Widget _buildDriverHeader(dynamic driver) {
return Container(
padding: const EdgeInsets.all(20),
decoration: AppStyle.elevatedCard,
child: Column(
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${driver['first_name']} ${driver['last_name']}', style: AppStyle.headTitle),
Text(driver['phone'] ?? '', style: AppStyle.subtitle),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColor.warning.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text('Pending', style: AppStyle.caption.copyWith(color: AppColor.warning)),
),
],
),
const Divider(height: 32, color: AppColor.divider),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildSmallInfo('الرقم الوطني', driver['national_number'] ?? 'N/A'),
_buildSmallInfo('الجنس', driver['gender'] ?? 'N/A'),
_buildSmallInfo('تاريخ الميلاد', driver['birthdate'] ?? 'N/A'),
],
),
],
),
);
}
Widget _buildSmallInfo(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: AppStyle.caption.copyWith(fontSize: 10)),
const SizedBox(height: 2),
Text(value, style: AppStyle.body.copyWith(fontSize: 12, fontWeight: FontWeight.bold)),
],
);
}
Widget _buildDocCard(dynamic doc) {
String imageUrl = doc['link'] ?? '';
// Ensure URL is absolute
if (!imageUrl.startsWith('http')) {
imageUrl = '${AppLink.server}/upload/drivers/$imageUrl';
}
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: AppStyle.cardDecoration,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
const Icon(Icons.file_present_rounded, color: AppColor.accent, size: 20),
const SizedBox(width: 8),
Text(doc['doc_type'] ?? 'وثيقة', style: AppStyle.title.copyWith(fontSize: 14)),
],
),
),
ClipRRect(
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(16)),
child: Image.network(
imageUrl,
width: double.infinity,
height: 200,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container(
height: 200,
color: AppColor.surfaceElevated,
child: const Center(child: Icon(Icons.broken_image_rounded, size: 48, color: AppColor.textMuted)),
),
),
),
],
),
);
}
}
@@ -0,0 +1,230 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/controller/functions/wallet.dart';
import '../../../constant/links.dart'; // تأكد من المسار
// --- Controller: المسؤول عن المنطق (البحث، الفحص، الإضافة) ---
class DriverGiftCheckerController extends GetxController {
// للتحكم في حقل النص
final TextEditingController phoneController = TextEditingController();
// لعرض النتائج وحالة التحميل
var statusLog = "".obs;
var isLoading = false.obs;
// قائمة السائقين (سنقوم بتحميلها للبحث عن الـ ID)
List<dynamic> driversCache = [];
@override
void onInit() {
super.onInit();
// fetchDriverCache(); // تحميل البيانات عند فتح الصفحة
}
// 1. تحميل قائمة السائقين لاستخراج الـ ID منها
Future<void> fetchDriverCache() async {
try {
final response = await CRUD().post(
link: '${AppLink.server}/Admin/driver/getDriverGiftPayment.php',
payload: {'phone': phoneController.text.trim()},
);
// print('response: ${response}');
if (response != 'failure') {
driversCache = (response['message']);
}
} catch (e) {
debugPrint("Error loading cache: $e");
}
}
// --- الدالة الرئيسية التي تنفذ العملية المطلوبة ---
Future<void> processDriverGift() async {
String phoneInput = phoneController.text.trim();
if (phoneInput.isEmpty) {
Get.snackbar("تنبيه", "يرجى إدخال رقم الهاتف",
backgroundColor: Colors.orange);
return;
}
await fetchDriverCache();
isLoading.value = true;
statusLog.value = "جاري البحث عن السائق...";
try {
// الخطوة 1: استخراج الـ ID بناءً على رقم الهاتف
var driver = driversCache.firstWhere(
(d) {
String dbPhone =
d['phone'].toString().replaceAll(RegExp(r'[^0-9]'), '');
String inputPhone = phoneInput.replaceAll(RegExp(r'[^0-9]'), '');
// قارن آخر 9 أرقام لتجاوز مشكلة 09 مقابل 963
if (dbPhone.length >= 9 && inputPhone.length >= 9) {
return dbPhone.substring(dbPhone.length - 9) ==
inputPhone.substring(inputPhone.length - 9);
}
return dbPhone == inputPhone;
},
orElse: () => null,
);
if (driver == null) {
statusLog.value = "❌ لم يتم العثور على سائق بهذا الرقم في الكاش.";
isLoading.value = false;
return;
}
String driverId = driver['id'].toString();
String driverName = driver['name_arabic'] ?? 'بدون اسم';
statusLog.value =
"✅ تم العثور على السائق: $driverName (ID: $driverId)\nجاري فحص رصيد الهدايا...";
// الخطوة 2: فحص السيرفر هل الهدية موجودة؟
// bool hasGift = await _checkIfGiftExistsOnServer(driverId);
// if (hasGift) {
// statusLog.value +=
// "\n⚠️ هذا السائق لديه هدية الافتتاح (30,000) مسبقاً. لم يتم اتخاذ إجراء.";
// } else {
// الخطوة 3: إضافة الهدية
statusLog.value += "\n🎁 الهدية غير موجودة. جاري الإضافة...";
await _addGiftToDriver(driverId, phoneInput, "300");
// }
} catch (e) {
statusLog.value = "حدث خطأ غير متوقع: $e";
} finally {
isLoading.value = false;
}
}
// دالة إضافة الهدية باستخدام WalletController الموجود عندك
Future<void> _addGiftToDriver(
String driverId, String phone, String amount) async {
final wallet = Get.put(WalletController());
// استخدام الدالة الموجودة في نظامك
await wallet.addDrivergift300('new driver', driverId, amount, phone);
// statusLog.value += "\n✅ تمت إضافة مبلغ $amount ل.س بنجاح!";
// إضافة تنبيه مرئي
// Get.snackbar("تم بنجاح", "تمت إضافة هدية الافتتاح للسائق",
// backgroundColor: Colors.green, colorText: Colors.white);
}
}
// --- View: واجهة المستخدم ---
class DriverGiftCheckPage extends StatelessWidget {
const DriverGiftCheckPage({super.key});
@override
Widget build(BuildContext context) {
// حقن الكنترولر
final controller = Get.put(DriverGiftCheckerController());
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: AppBar(
title: const Text("فحص ومنح هدية الافتتاح",
style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: const Color(0xFF0F172A), // نفس لون الهيدر السابق
foregroundColor: Colors.white,
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
// كارد الإدخال
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 10)
],
),
child: Column(
children: [
const Icon(Icons.card_giftcard,
size: 50, color: Colors.amber),
const SizedBox(height: 10),
const Text(
"أدخل رقم الهاتف للتحقق",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
// حقل الإدخال
TextField(
controller: controller.phoneController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
hintText: 'مثال: 0912345678',
prefixIcon: const Icon(Icons.phone),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)),
filled: true,
fillColor: Colors.grey[50],
),
),
const SizedBox(height: 20),
// زر التنفيذ
Obx(() => SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: controller.isLoading.value
? null
: () => controller.processDriverGift(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0F172A),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
child: controller.isLoading.value
? const CircularProgressIndicator(
color: Colors.white)
: const Text("تحقق ومنح الهدية (30,000)",
style: TextStyle(fontSize: 16)),
),
)),
],
),
),
const SizedBox(height: 30),
// منطقة عرض النتائج (Log)
Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(12),
),
child: SingleChildScrollView(
child: Obx(() => Text(
controller.statusLog.value.isEmpty
? "بانتظار العملية..."
: controller.statusLog.value,
style: const TextStyle(
color: Colors.greenAccent,
fontFamily: 'monospace',
height: 1.5),
)),
),
),
),
],
),
),
);
}
}
@@ -0,0 +1,654 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; // Ensure get_storage is in pubspec.yaml
import 'package:siro_admin/controller/functions/wallet.dart';
// --- New Controller to handle the specific JSON URL ---
class DriverCacheController extends GetxController {
List<dynamic> drivers = [];
bool isLoading = false;
String lastUpdated = '';
String searchQuery = ''; // Search query state
// Storage for paid drivers
final box = GetStorage();
List<String> paidDrivers = [];
@override
void onInit() {
super.onInit();
// Load previously paid drivers from storage
var stored = box.read('paid_drivers');
if (stored != null) {
paidDrivers = List<String>.from(stored.map((e) => e.toString()));
}
fetchData();
}
Future<void> fetchData() async {
isLoading = true;
update(); // Notify UI to show loader
try {
// Using GetConnect to fetch the JSON directly
final response = await GetConnect().get(
'https://api.intaleq.xyz/intaleq/ride/location/active_drivers_cache.json',
);
if (response.body != null && response.body is Map) {
if (response.body['data'] != null) {
drivers = List<dynamic>.from(response.body['data']);
}
if (response.body['last_updated'] != null) {
lastUpdated = response.body['last_updated'].toString();
}
}
} catch (e) {
debugPrint("Error fetching driver cache: $e");
} finally {
isLoading = false;
update(); // Update UI with data
}
}
// Update search query
void updateSearchQuery(String query) {
searchQuery = query;
update();
}
// Mark driver as paid and save to storage
void markAsPaid(String driverId) {
// Validation: Don't mark if ID is invalid
if (driverId == 'null' || driverId.isEmpty) return;
if (!paidDrivers.contains(driverId)) {
paidDrivers.add(driverId);
box.write('paid_drivers', paidDrivers);
update();
}
}
// Clear all paid status (Delete Box)
void clearPaidStorage() {
paidDrivers.clear();
box.remove('paid_drivers');
update();
Get.snackbar(
"Storage Cleared",
"Paid status history has been reset",
backgroundColor: Colors.redAccent,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM,
);
}
// Check if driver is already paid
bool isDriverPaid(String driverId) {
return paidDrivers.contains(driverId);
}
}
class DriverTheBestRedesigned extends StatelessWidget {
const DriverTheBestRedesigned({super.key});
@override
Widget build(BuildContext context) {
// Put the new controller
final controller = Get.put(DriverCacheController());
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC), // slate-50 background
body: SafeArea(
child: GetBuilder<DriverCacheController>(builder: (ctrl) {
if (ctrl.isLoading) {
return const Center(child: CircularProgressIndicator());
}
// Filter List based on Search Query
List<dynamic> filteredDrivers = ctrl.drivers.where((driver) {
if (ctrl.searchQuery.isEmpty) return true;
final phone = driver['phone']?.toString() ?? '';
// Simple contains check for phone
return phone.contains(ctrl.searchQuery);
}).toList();
// Sort by Active Time (Hours) Descending
// We use the filtered list for sorting and display
filteredDrivers.sort((a, b) {
double hoursA = _calculateHoursFromStr(a['active_time']);
double hoursB = _calculateHoursFromStr(b['active_time']);
return hoursB.compareTo(hoursA);
});
// --- 1. Calculate Stats (Based on ALL drivers, not just filtered, to keep dashboard stable) ---
int totalDrivers = ctrl.drivers.length;
int eliteCount = 0;
int inactiveCount = 0;
double maxTime = 0.0;
for (var driver in ctrl.drivers) {
double hours = _calculateHoursFromStr(driver['active_time']);
if (hours > maxTime) maxTime = hours;
if (hours >= 50) {
eliteCount++;
} else if (hours < 5) {
inactiveCount++;
}
}
return Column(
children: [
// --- 2. Header (Slate-900 style) ---
Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Color(0xFF0F172A), // slate-900
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 4)],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.local_taxi,
color: Colors.yellow, size: 24),
const SizedBox(width: 8),
Text(
'Best Drivers Dashboard'.tr,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
const Icon(Icons.access_time,
color: Colors.grey, size: 12),
const SizedBox(width: 4),
Text(
ctrl.lastUpdated.isNotEmpty
? 'Updated: ${ctrl.lastUpdated}'
: 'Data Live',
style: TextStyle(
color: Colors.grey[400], fontSize: 12),
),
],
),
],
),
// Action Buttons (Delete Box & Refresh)
Row(
children: [
// Delete Box Icon
IconButton(
onPressed: () {
Get.defaultDialog(
title: "Reset Paid Status",
middleText:
"Are you sure you want to clear the list of paid drivers? This cannot be undone.",
textConfirm: "Yes, Clear",
textCancel: "Cancel",
confirmTextColor: Colors.white,
buttonColor: Colors.red,
onConfirm: () {
ctrl.clearPaidStorage();
Get.back();
},
);
},
icon: const Icon(Icons.delete_forever,
color: Colors.redAccent),
tooltip: "Clear Paid Storage",
style: IconButton.styleFrom(
backgroundColor: Colors.white10),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {
ctrl.fetchData();
},
icon: const Icon(Icons.refresh,
color: Colors.blueAccent),
style: IconButton.styleFrom(
backgroundColor: Colors.white10),
),
],
)
],
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// --- 3. Statistics Cards Grid ---
SizedBox(
height: 100, // Fixed height for cards
child: Row(
children: [
Expanded(
child: _buildStatCard('Total',
totalDrivers.toString(), Colors.blue)),
const SizedBox(width: 8),
Expanded(
child: _buildStatCard('Elite',
eliteCount.toString(), Colors.amber)),
],
),
),
const SizedBox(height: 8),
SizedBox(
height: 100,
child: Row(
children: [
Expanded(
child: _buildStatCard('Inactive',
inactiveCount.toString(), Colors.red)),
const SizedBox(width: 8),
Expanded(
child: _buildStatCard(
'Max Time',
'${maxTime.toStringAsFixed(1)}h',
Colors.green)),
],
),
),
const SizedBox(height: 24),
// --- 4. Search Bar ---
TextField(
onChanged: (val) => ctrl.updateSearchQuery(val),
decoration: InputDecoration(
hintText: 'Search by phone number...',
prefixIcon:
const Icon(Icons.search, color: Colors.grey),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200),
),
),
),
const SizedBox(height: 16),
// --- 5. Driver List ---
if (filteredDrivers.isEmpty)
Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
ctrl.searchQuery.isNotEmpty
? "No drivers found with this number"
: "No drivers available",
style: TextStyle(color: Colors.grey[400])),
))
else
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: filteredDrivers.length,
separatorBuilder: (c, i) =>
const SizedBox(height: 12),
itemBuilder: (context, index) {
final driver = filteredDrivers[index];
return _buildDriverCard(
context, driver, index, ctrl);
},
),
],
),
),
),
],
);
}),
),
);
}
// --- Helper Methods ---
// Updated to parse the Arabic string format "5 ساعة 30 دقيقة"
double _calculateHoursFromStr(dynamic activeTimeStr) {
if (activeTimeStr == null || activeTimeStr is! String) return 0.0;
try {
int hours = 0;
int mins = 0;
// Extract hours
final hoursMatch = RegExp(r'(\d+)\s*ساعة').firstMatch(activeTimeStr);
if (hoursMatch != null) {
hours = int.parse(hoursMatch.group(1) ?? '0');
}
// Extract minutes
final minsMatch = RegExp(r'(\d+)\s*دقيقة').firstMatch(activeTimeStr);
if (minsMatch != null) {
mins = int.parse(minsMatch.group(1) ?? '0');
}
return hours + (mins / 60.0);
} catch (e) {
return 0.0;
}
}
Widget _buildStatCard(String title, String value, Color color) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border(right: BorderSide(color: color, width: 4)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2))
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(title,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(value,
style: TextStyle(
fontSize: 22,
color: color.withOpacity(0.8),
fontWeight: FontWeight.bold)),
],
),
);
}
Widget _buildDriverCard(BuildContext context, Map driver, int index,
DriverCacheController controller) {
double hours = _calculateHoursFromStr(driver['active_time']);
String driverId = driver['id']?.toString() ?? 'null';
bool isPaid = controller.isDriverPaid(driverId);
// Determine Status Category (mimicking HTML logic)
String statusText;
Color statusColor;
if (hours >= 50) {
statusText = "Elite";
statusColor = Colors.amber;
} else if (hours >= 20) {
statusText = "Stable";
statusColor = Colors.green;
} else if (hours >= 5) {
statusText = "Experimental";
statusColor = Colors.blue;
} else {
statusText = "Inactive";
statusColor = Colors.red;
}
// Override colors if paid
Color cardBackground = isPaid ? Colors.teal.shade50 : Colors.white;
Color borderColor = isPaid ? Colors.teal : Colors.transparent;
// Calculate progress (max assumed 60 hours for 100% bar)
double progress = (hours / 60).clamp(0.0, 1.0);
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: cardBackground,
border: isPaid ? Border.all(color: borderColor, width: 2) : null,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4))
],
),
child: Column(
children: [
if (isPaid)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.teal,
borderRadius: BorderRadius.circular(4)),
child: const Text("PAID",
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold)),
)
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Avatar
CircleAvatar(
backgroundColor: statusColor.withOpacity(0.1),
radius: 24,
child: Text(
hours.toStringAsFixed(0),
style: TextStyle(
color: statusColor, fontWeight: FontWeight.bold),
),
),
const SizedBox(width: 12),
// Name and Phone
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
driver['name_arabic'] ?? 'Unknown Name',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: isPaid
? Colors.teal.shade900
: const Color(0xFF334155)),
),
const SizedBox(height: 4),
Text(
driver['phone'] ?? 'N/A',
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
color: Colors.grey),
),
const SizedBox(height: 2),
Text(
driver['active_time'] ?? '',
style: TextStyle(fontSize: 10, color: Colors.grey[400]),
),
],
),
),
// Status Badge
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: statusColor.withOpacity(0.2)),
),
child: Text(
statusText,
style: TextStyle(
fontSize: 10,
color: statusColor,
fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 12),
// Progress Bar
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Performance",
style: TextStyle(fontSize: 10, color: Colors.grey[600])),
Text("${hours.toStringAsFixed(2)} hrs",
style: const TextStyle(
fontSize: 10, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 4),
LinearProgressIndicator(
value: progress,
backgroundColor: Colors.grey[100],
color: statusColor,
minHeight: 6,
borderRadius: BorderRadius.circular(3),
),
],
),
const SizedBox(height: 16),
const Divider(height: 1),
const SizedBox(height: 8),
// Actions Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Pay Gift Button (The specific request)
isPaid
? const Text("Payment Completed",
style: TextStyle(
color: Colors.teal, fontWeight: FontWeight.bold))
: ElevatedButton.icon(
onPressed: () {
_showPayDialog(driver, controller);
},
icon: const Icon(Icons.card_giftcard, size: 16),
label: Text("Pay Gift".tr),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.indigo, // Dark blue/purple
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
),
],
)
],
),
);
}
void _showPayDialog(Map driver, DriverCacheController controller) {
// Check for valid ID immediately
String driverId = driver['driver_id']?.toString() ?? '';
String phone = driver['phone']?.toString() ?? '';
if (driverId.isEmpty || driverId == 'null') {
Get.snackbar("Error", "Cannot pay driver with missing ID",
backgroundColor: Colors.red, colorText: Colors.white);
return;
}
// Controller for the Amount Field
final TextEditingController amountController =
TextEditingController(text: '50000');
Get.defaultDialog(
title: 'Confirm Payment',
titleStyle: const TextStyle(
color: Color(0xFF0F172A), fontWeight: FontWeight.bold),
content: Column(
children: [
const Icon(Icons.wallet_giftcard, size: 50, color: Colors.indigo),
const SizedBox(height: 10),
Text(
'Sending gift to ${driver['name_arabic']}',
textAlign: TextAlign.center,
),
const SizedBox(height: 15),
// Amount Field
TextFormField(
controller: amountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Amount (SYP)',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.attach_money),
),
),
],
),
textConfirm: 'Pay Now',
confirmTextColor: Colors.white,
buttonColor: Colors.indigo,
onConfirm: () async {
final wallet = Get.put(WalletController());
// Get amount from field
String amount = amountController.text.trim();
if (amount.isEmpty) amount = '0';
// String driverToken = driver['token'] ?? '';
// 1. Add Payment
await wallet.addDriverWallet('gift_connect', driverId, amount, phone);
// 2. Add to Sefer Wallet
//await wallet.addSeferWallet(amount, driverId);
// 3. Delete Record via CRUD
// await CRUD()
// .post(link: AppLink.deleteRecord, payload: {'driver_id': driverId});
// 4. UI Update & Storage
// Mark as paid instead of removing completely, so we can see the color change
controller.markAsPaid(driverId);
Get.back(); // Close Dialog
Get.snackbar("Success", "Payment of $amount EGP sent to driver",
backgroundColor: Colors.green,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM);
},
textCancel: 'Cancel',
onCancel: () => Get.back(),
);
}
}
@@ -0,0 +1,837 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:http/http.dart' as http;
import 'package:siro_admin/constant/links.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../constant/box_name.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart';
class IntaleqTrackerScreen extends StatefulWidget {
const IntaleqTrackerScreen({super.key});
@override
State<IntaleqTrackerScreen> createState() => _IntaleqTrackerScreenState();
}
class _IntaleqTrackerScreenState extends State<IntaleqTrackerScreen>
with TickerProviderStateMixin {
// === Map Controller ===
final MapController _mapController = MapController();
List<Marker> _markers = [];
// === State Variables ===
bool isLiveMode = true;
bool isLoading = false;
String lastUpdated = "جاري التحميل...";
// === Counters ===
int liveCount = 0;
int dayCount = 0;
Timer? _timer;
// === Animation Controllers ===
late AnimationController _fadeController;
late AnimationController _scaleController;
// === Admin Info ===
String myPhone = box.read(BoxName.adminPhone).toString();
bool get isSuperAdmin =>
myPhone == '963942542053' || myPhone == '963992952235';
// === URLs ===
final String _baseDir = "${AppLink.server}/ride/location/";
@override
void initState() {
super.initState();
_initAnimations();
fetchData();
_timer = Timer.periodic(const Duration(minutes: 5), (timer) {
if (mounted) fetchData();
});
}
void _initAnimations() {
_fadeController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_scaleController = AnimationController(
duration: const Duration(milliseconds: 600),
vsync: this,
);
_fadeController.forward();
_scaleController.forward();
}
@override
void dispose() {
_timer?.cancel();
_mapController.dispose();
_fadeController.dispose();
_scaleController.dispose();
super.dispose();
}
Future<void> _makePhoneCall(String phoneNumber) async {
final Uri launchUri = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri);
} else {
_showSnackBar("لا يمكن إجراء الاتصال لهذا الرقم");
}
}
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: const Color(0xFF2C3E50),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
);
}
Future<void> fetchData() async {
if (!mounted) return;
setState(() => isLoading = true);
try {
String updateUrl =
"${_baseDir}getUpdatedLocationForAdmin.php?mode=${isLiveMode ? 'live' : 'day'}";
print("📡 Calling Update URL: $updateUrl");
var responseUpdate = await CRUD().post(link: updateUrl, payload: {});
print("📡 Update Response: $responseUpdate");
String v = DateTime.now().millisecondsSinceEpoch.toString();
String liveUrl = "${_baseDir}locations_live.json?v=$v";
print("📡 Calling Live JSON URL: $liveUrl");
final responseLive = await http.get(Uri.parse(liveUrl));
print(
"📡 Live JSON Response (${responseLive.statusCode}): ${responseLive.body.length > 100 ? responseLive.body.substring(0, 100) : responseLive.body}");
if (responseLive.statusCode == 200) {
final data = json.decode(responseLive.body);
List drivers = (data is Map && data.containsKey('drivers'))
? data['drivers']
: data;
setState(() {
liveCount = drivers.length;
if (isLiveMode) _buildMarkers(drivers);
});
}
String dayUrl = "${_baseDir}locations_day.json?v=$v";
print("📡 Calling Day JSON URL: $dayUrl");
final responseDay = await http.get(Uri.parse(dayUrl));
print(
"📡 Day JSON Response (${responseDay.statusCode}): ${responseDay.body.length > 100 ? responseDay.body.substring(0, 100) : responseDay.body}");
if (responseDay.statusCode == 200) {
final data = json.decode(responseDay.body);
List drivers = (data is Map && data.containsKey('drivers'))
? data['drivers']
: data;
setState(() {
dayCount = drivers.length;
if (!isLiveMode) _buildMarkers(drivers);
});
}
setState(() {
lastUpdated = DateTime.now().toString().substring(11, 19);
});
} catch (e) {
print("Exception: $e");
setState(() => lastUpdated = "خطأ في الاتصال");
} finally {
if (mounted) setState(() => isLoading = false);
}
}
void _buildMarkers(List<dynamic> drivers) {
List<Marker> newMarkers = [];
for (var d in drivers) {
double lat = double.tryParse((d['lat'] ?? "0").toString()) ?? 0.0;
double lon = double.tryParse((d['lon'] ?? "0").toString()) ?? 0.0;
double heading = double.tryParse((d['heading'] ?? "0").toString()) ?? 0.0;
String id = (d['id'] ?? "Unknown").toString();
String speed = (d['speed'] ?? "0").toString();
String name = (d['name'] ?? "كابتن").toString();
String phone = (d['phone'] ?? "").toString();
String completed = (d['completed'] ?? "0").toString();
String cancelled = (d['cancelled'] ?? "0").toString();
if (lat != 0 && lon != 0) {
newMarkers.add(
Marker(
point: LatLng(lat, lon),
width: 60,
height: 60,
child: GestureDetector(
onTap: () {
_showDriverInfoDialog(
driverId: id,
name: name,
phone: phone,
speed: speed,
heading: heading,
completed: completed,
cancelled: cancelled,
);
},
child: _buildMarkerWidget(heading),
),
),
);
}
}
setState(() {
_markers = newMarkers;
});
}
Widget _buildMarkerWidget(double heading) {
return Stack(
alignment: Alignment.center,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: isLiveMode
? [const Color(0xFF27AE60), const Color(0xFF229954)]
: [const Color(0xFF3498DB), const Color(0xFF2980B9)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: (isLiveMode
? const Color(0xFF27AE60)
: const Color(0xFF3498DB))
.withOpacity(0.5),
blurRadius: 12,
spreadRadius: 2,
)
],
),
),
Transform.rotate(
angle: heading * (math.pi / 180),
child: Icon(
Icons.navigation,
color: Colors.white,
size: 26,
),
),
],
);
}
void _showDriverInfoDialog({
required String driverId,
required String name,
required String phone,
required String speed,
required double heading,
required String completed,
required String cancelled,
}) {
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.white, Colors.grey.shade50],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 30,
spreadRadius: 5,
)
],
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isLiveMode
? [const Color(0xFF27AE60), const Color(0xFF229954)]
: [const Color(0xFF3498DB), const Color(0xFF2980B9)],
),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.person_outline, color: Colors.white, size: 20),
const SizedBox(width: 8),
const Text(
"معلومات الكابتن",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
const SizedBox(height: 20),
_buildInfoCard(Icons.person, "الاسم", name),
const SizedBox(height: 12),
_buildInfoCard(Icons.badge, "المعرف", driverId),
const SizedBox(height: 12),
_buildInfoCard(Icons.speed, "السرعة", "$speed كم/س"),
const SizedBox(height: 20),
_buildStatsContainer(completed, cancelled),
if (isSuperAdmin) ...[
const SizedBox(height: 16),
_buildPhoneButton(phone),
],
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2C3E50),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Text(
"إغلاق",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
)
],
),
),
),
),
);
}
Widget _buildInfoCard(IconData icon, String label, String value) {
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Colors.grey.shade200,
width: 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
)
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: const Color(0xFF2C3E50), size: 20),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Color(0xFF2C3E50),
),
),
],
),
],
),
);
}
Widget _buildStatsContainer(String completed, String cancelled) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.grey.shade50, Colors.grey.shade100],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStatItem("✓ مكتملة", completed, const Color(0xFF27AE60)),
Container(
width: 1,
height: 40,
color: Colors.grey.shade300,
),
_buildStatItem("✕ ملغاة", cancelled, const Color(0xFFE74C3C)),
],
),
);
}
Widget _buildStatItem(String label, String value, Color color) {
return Column(
children: [
Text(
value,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
],
);
}
Widget _buildPhoneButton(String phone) {
return InkWell(
onTap: () {
if (phone.isNotEmpty) _makePhoneCall(phone);
},
borderRadius: BorderRadius.circular(12),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [const Color(0xFFFFA500), const Color(0xFFFF8C00)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: const Color(0xFFFFA500).withOpacity(0.4),
blurRadius: 12,
spreadRadius: 2,
)
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.call, color: Colors.white, size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
phone,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15,
),
textAlign: TextAlign.center,
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
centerTitle: true,
title: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF2C3E50).withOpacity(0.9),
borderRadius: BorderRadius.circular(12),
// backdropFilter: const BackdropFilter(blur: 10),
),
child: const Text(
"نظام تتبع الكابتن",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
foregroundColor: Colors.white,
),
body: Stack(
children: [
FlutterMap(
mapController: _mapController,
options: const MapOptions(
initialCenter: LatLng(33.513, 36.276),
initialZoom: 10.0,
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.tripz.app',
),
MarkerLayer(markers: _markers),
],
),
_buildDashboard(),
],
),
);
}
Widget _buildDashboard() {
return Positioned(
top: 100,
right: 16,
child: FadeTransition(
opacity: _fadeController,
child: ScaleTransition(
scale: _scaleController,
child: Container(
width: 300,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 30,
spreadRadius: 5,
)
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Column(
children: [
// Header
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF2C3E50),
const Color(0xFF34495E)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(Icons.dashboard,
color: Colors.white, size: 22),
const Text(
"لوحة التحكم",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Mode Buttons
Row(
children: [
Expanded(
child: _buildModeButton(
"أرشيف اليوم",
!isLiveMode,
() {
setState(() => isLiveMode = false);
fetchData();
},
const Color(0xFF3498DB),
),
),
const SizedBox(width: 10),
Expanded(
child: _buildModeButton(
"مباشر",
isLiveMode,
() {
setState(() => isLiveMode = true);
fetchData();
},
const Color(0xFF27AE60),
),
),
],
),
const SizedBox(height: 16),
// Stats
_buildStatRow(
icon: Icons.live_tv,
label: "نشط الآن (مباشر)",
value: liveCount.toString(),
color: const Color(0xFF27AE60),
),
const SizedBox(height: 12),
_buildStatRow(
icon: Icons.history,
label: "إجمالي اليوم",
value: dayCount.toString(),
color: const Color(0xFF3498DB),
),
const SizedBox(height: 14),
// Last Update
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
isLoading
? "جاري التحديث..."
: "تحديث: $lastUpdated",
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
Icon(
isLoading
? Icons.hourglass_bottom
: Icons.check_circle,
size: 14,
color: isLoading ? Colors.orange : Colors.green,
),
],
),
),
const SizedBox(height: 12),
// Refresh Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isLoading ? null : fetchData,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2C3E50),
foregroundColor: Colors.white,
disabledBackgroundColor: Colors.grey.shade300,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (isLoading)
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
else
const Icon(Icons.refresh, size: 18),
const SizedBox(width: 8),
const Text(
"تحديث البيانات",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
],
),
),
),
],
),
),
],
),
),
),
),
),
);
}
Widget _buildModeButton(
String title,
bool active,
VoidCallback onTap,
Color color,
) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.symmetric(vertical: 10),
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: active
? LinearGradient(
colors: [color, color.withOpacity(0.8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: active ? null : Colors.grey.shade100,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: active ? color : Colors.grey.shade300,
width: 1.5,
),
boxShadow: active
? [
BoxShadow(
color: color.withOpacity(0.3),
blurRadius: 8,
spreadRadius: 1,
)
]
: null,
),
child: Text(
title,
style: TextStyle(
color: active ? Colors.white : Colors.grey.shade700,
fontWeight: active ? FontWeight.bold : FontWeight.w600,
fontSize: 13,
),
),
),
);
}
Widget _buildStatRow({
required IconData icon,
required String label,
required String value,
required Color color,
}) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: color, size: 18),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 2),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
),
],
);
}
}
@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/controller/functions/wallet.dart';
import 'package:siro_admin/views/widgets/my_scafold.dart';
import '../../../controller/drivers/driverthebest.dart';
class DriverTheBestGiza extends StatelessWidget {
const DriverTheBestGiza({super.key});
@override
Widget build(BuildContext context) {
Get.put(DriverTheBestGizaController(), permanent: true);
return MyScafolld(
title: 'Giza'.tr,
body: [
GetBuilder<DriverTheBestGizaController>(builder: (driverthebest) {
return driverthebest.driver.isNotEmpty
? ListView.builder(
itemCount: driverthebest.driver.length,
itemBuilder: (context, index) {
final driver = driverthebest.driver[index];
return ListTile(
leading: CircleAvatar(
child: Text(
((driver['driver_count'] * 5) / 3600)
.toStringAsFixed(0),
),
),
title: Text(driver['name_arabic'] ?? 'Unknown Name'),
subtitle: Text('Phone: ${driver['phone'] ?? 'N/A'}'),
trailing: IconButton(
onPressed: () async {
Get.defaultDialog(
title:
'are you sure to pay to this driver gift'.tr,
middleText: '',
onConfirm: () async {
final wallet = Get.put(WalletController());
await wallet.addPaymentToDriver('100',
driver['id'].toString(), driver['token']);
await wallet.addSeferWallet(
'100', driver['id'].toString());
await CRUD().post(
link: AppLink.deleteRecord,
payload: {
'driver_id': driver['id'].toString()
});
driverthebest.driver.removeAt(index);
driverthebest.update();
},
onCancel: () => Get.back());
},
icon: const Icon(Icons.wallet_giftcard_rounded),
),
);
},
)
: const Center(
child: Text('No drivers available.'),
);
})
],
isleading: true,
);
}
}
@@ -0,0 +1,833 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:siro_admin/constant/links.dart';
// Keep your specific imports
import 'package:siro_admin/controller/functions/crud.dart';
/// --------------------------------------------------------------------------
/// 1. DATA MODELS
/// --------------------------------------------------------------------------
class DriverLocation {
final double latitude;
final double longitude;
final double speed;
final double heading;
final String updatedAt;
DriverLocation({
required this.latitude,
required this.longitude,
required this.speed,
required this.heading,
required this.updatedAt,
});
factory DriverLocation.fromJson(Map<String, dynamic> json) {
return DriverLocation(
latitude: double.tryParse(json['latitude'].toString()) ?? 0.0,
longitude: double.tryParse(json['longitude'].toString()) ?? 0.0,
speed: double.tryParse(json['speed'].toString()) ?? 0.0,
heading: double.tryParse(json['heading'].toString()) ?? 0.0,
updatedAt: json['updated_at'] ?? '',
);
}
}
/// --------------------------------------------------------------------------
/// 2. GETX CONTROLLER
/// --------------------------------------------------------------------------
class RideMonitorController extends GetxController {
// CONFIGURATION
final String apiUrl = "${AppLink.server}/Admin/rides/monitorRide.php";
// INPUT CONTROLLERS
final TextEditingController phoneInputController = TextEditingController();
// OBSERVABLES
var isTracking = false.obs;
var isLoading = false.obs;
var hasError = false.obs;
var errorMessage = ''.obs;
// Driver & Ride Data
var driverLocation = Rxn<DriverLocation>();
var driverName = "Unknown Driver".obs;
var rideStatus = "Waiting...".obs;
// Route Data
var startPoint = Rxn<LatLng>();
var endPoint = Rxn<LatLng>();
var routePolyline = <LatLng>[].obs; // List of points for the line
// Map Variables
final MapController mapController = MapController();
Timer? _timer;
bool _isFirstLoad = true; // To trigger auto-fit bounds only on first success
@override
void onClose() {
_timer?.cancel();
phoneInputController.dispose();
super.onClose();
}
// --- ACTIONS ---
void startSearch() {
if (phoneInputController.text.trim().isEmpty) {
Get.snackbar(
"تنبيه",
"يرجى إدخال رقم الهاتف أولاً",
backgroundColor: Colors.redAccent.withOpacity(0.9),
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
margin: const EdgeInsets.all(15),
borderRadius: 15,
);
return;
}
// Reset state
hasError.value = false;
errorMessage.value = '';
driverLocation.value = null;
startPoint.value = null;
endPoint.value = null;
routePolyline.clear();
driverName.value = "جاري التحميل...";
rideStatus.value = "جاري التحميل...";
_isFirstLoad = true;
// Switch UI
isTracking.value = true;
isLoading.value = true;
// Start fetching
fetchRideData();
// Start Polling
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 10), (timer) {
fetchRideData();
});
}
void stopTracking() {
_timer?.cancel();
isTracking.value = false;
isLoading.value = false;
// phoneInputController.clear(); // اختياري: يمكنك إبقائه لتسهيل البحث مرة أخرى
}
Future<void> fetchRideData() async {
final phone = phoneInputController.text.trim();
if (phone.isEmpty) return;
try {
final response = await CRUD().post(
link: apiUrl,
payload: {"phone": "963$phone"},
);
if (response != 'failure') {
final jsonResponse = response;
if ((jsonResponse['message'] != null &&
jsonResponse['message'] != 'failure') ||
jsonResponse['status'] == 'success') {
final data =
jsonResponse['message'] ?? jsonResponse['data'] ?? jsonResponse;
// 1. Parse Driver Info
if (data['driver_details'] != null) {
driverName.value =
data['driver_details']['fullname'] ?? "سائق غير معروف";
}
// 2. Parse Ride Info & Route
if (data['ride_details'] != null) {
rideStatus.value = data['ride_details']['status'] ?? "غير معروف";
// Parse Start/End Locations (Format: "lat,lng")
String? startStr = data['ride_details']['start_location'];
String? endStr = data['ride_details']['end_location'];
LatLng? s = _parseLatLngString(startStr);
LatLng? e = _parseLatLngString(endStr);
if (s != null && e != null) {
startPoint.value = s;
endPoint.value = e;
routePolyline.value = [s, e]; // Straight line for now
}
}
// 3. Parse Live Location
final locData = data['driver_location'];
if (locData is Map<String, dynamic>) {
final newLocation = DriverLocation.fromJson(locData);
driverLocation.value = newLocation;
// 4. Update Camera Bounds
_updateMapBounds();
} else {
// Even if no live driver, we might want to show the route
if (startPoint.value != null && endPoint.value != null) {
_updateMapBounds();
}
}
hasError.value = false;
} else {
hasError.value = true;
errorMessage.value = jsonResponse['message'] ??
"لم يتم العثور على رقم الهاتف أو لا توجد رحلة نشطة.";
}
} else {
hasError.value = true;
errorMessage.value = "فشل الاتصال بالخادم";
}
} catch (e) {
if (isLoading.value) {
hasError.value = true;
errorMessage.value = e.toString();
}
} finally {
isLoading.value = false;
}
}
// Helper to parse "lat,lng" string
LatLng? _parseLatLngString(String? str) {
if (str == null || !str.contains(',')) return null;
try {
final parts = str.split(',');
final lat = double.parse(parts[0].trim());
final lng = double.parse(parts[1].trim());
return LatLng(lat, lng);
} catch (e) {
return null;
}
}
// Logic to fit start, end, and driver on screen
void _updateMapBounds() {
if (!_isFirstLoad) return;
List<LatLng> pointsToFit = [];
if (startPoint.value != null) pointsToFit.add(startPoint.value!);
if (endPoint.value != null) pointsToFit.add(endPoint.value!);
if (driverLocation.value != null) {
pointsToFit.add(LatLng(
driverLocation.value!.latitude, driverLocation.value!.longitude));
}
if (pointsToFit.isNotEmpty) {
try {
final bounds = LatLngBounds.fromPoints(pointsToFit);
mapController.fitCamera(
CameraFit.bounds(
bounds: bounds,
padding: const EdgeInsets.all(80.0),
),
);
_isFirstLoad = false;
} catch (e) {
// Map Controller not ready yet
}
}
}
}
/// --------------------------------------------------------------------------
/// 3. UI SCREEN (Modern Light Theme)
/// --------------------------------------------------------------------------
class RideMonitorScreen extends StatelessWidget {
const RideMonitorScreen({super.key});
// 🎨 الألوان العصرية (Modern Palette)
final Color backgroundColor = const Color(0xFFF4F7FE);
final Color primaryColor = const Color(0xFF4318FF);
final Color textPrimary = const Color(0xFF2B3674);
final Color textSecondary = const Color(0xFFA3AED0);
@override
Widget build(BuildContext context) {
final RideMonitorController controller = Get.put(RideMonitorController());
return Scaffold(
backgroundColor: backgroundColor,
// الإبقاء على AppBar فقط في شاشة البحث
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Obx(() {
if (controller.isTracking.value)
return const SizedBox
.shrink(); // إخفاء الـ AppBar في وضع التتبع للخريطة الكاملة
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
iconTheme: IconThemeData(color: textPrimary),
title: Text(
"مراقبة الرحلات",
style: TextStyle(
color: textPrimary,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
}),
),
body: Obx(() {
if (!controller.isTracking.value) {
return _buildSearchForm(context, controller);
}
return _buildMapTrackingView(context, controller);
}),
);
}
// ---------------------------------------------------------------------------
// واجهة البحث (Search View)
// ---------------------------------------------------------------------------
Widget _buildSearchForm(
BuildContext context, RideMonitorController controller) {
return Center(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Container(
padding: const EdgeInsets.all(32.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: primaryColor.withOpacity(0.08),
blurRadius: 24,
offset: const Offset(0, 10),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: primaryColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child:
Icon(Icons.radar_rounded, size: 60, color: primaryColor),
),
const SizedBox(height: 24),
Text(
"تتبع رحلة نشطة",
style: TextStyle(
color: textPrimary,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
"أدخل رقم هاتف السائق أو الراكب للبدء",
style: TextStyle(
color: textSecondary,
fontSize: 14,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white, width: 2),
),
child: TextField(
controller: controller.phoneInputController,
keyboardType: TextInputType.phone,
textDirection: TextDirection.ltr,
style: TextStyle(
color: textPrimary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
decoration: InputDecoration(
hintText: "مثال: 0992952235...",
hintStyle: TextStyle(color: textSecondary),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
vertical: 18, horizontal: 20),
prefixIcon:
Icon(Icons.phone_rounded, color: primaryColor),
),
),
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: controller.startSearch,
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Text(
"بدء المراقبة",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
),
);
}
// ---------------------------------------------------------------------------
// واجهة الخريطة (Map View)
// ---------------------------------------------------------------------------
Widget _buildMapTrackingView(
BuildContext context, RideMonitorController controller) {
return Stack(
children: [
FlutterMap(
mapController: controller.mapController,
options: MapOptions(
initialCenter: const LatLng(30.0444, 31.2357),
initialZoom: 12.0,
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.sefer.admin',
),
// 1. ROUTE LINE (Polyline)
if (controller.routePolyline.isNotEmpty)
PolylineLayer(
polylines: [
Polyline(
points: controller.routePolyline.value,
strokeWidth: 6.0,
color: primaryColor.withOpacity(0.9),
borderStrokeWidth: 2.0,
borderColor: primaryColor.withOpacity(0.3),
strokeCap: StrokeCap.round,
strokeJoin: StrokeJoin.round,
),
],
),
// 2. START & END MARKERS
MarkerLayer(
markers: [
// Start Point (Green Dot)
if (controller.startPoint.value != null)
Marker(
point: controller.startPoint.value!,
width: 30,
height: 30,
child: _buildPointMarker(const Color(0xFF10B981)),
),
// End Point (Red Dot)
if (controller.endPoint.value != null)
Marker(
point: controller.endPoint.value!,
width: 30,
height: 30,
child: _buildPointMarker(const Color(0xFFEF4444)),
),
// Driver Car Marker
if (controller.driverLocation.value != null)
Marker(
point: LatLng(
controller.driverLocation.value!.latitude,
controller.driverLocation.value!.longitude,
),
width: 80,
height: 80,
child: Transform.rotate(
angle: (controller.driverLocation.value!.heading *
(3.14159 / 180)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
spreadRadius: 2,
)
],
),
child: Icon(
Icons.directions_car_rounded,
color: primaryColor,
size: 28,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: textPrimary,
borderRadius: BorderRadius.circular(6),
),
child: Text(
"${controller.driverLocation.value!.speed.toInt()} كم",
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
textDirection: TextDirection.rtl,
),
)
],
),
),
),
],
),
],
),
// زر التراجع (إيقاف التتبع) أعلى الشاشة
Positioned(
top: MediaQuery.of(context).padding.top + 10,
right: 20, // أو left حسب لغة التطبيق
child: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
),
child: IconButton(
icon: Icon(Icons.close_rounded, color: textPrimary, size: 24),
onPressed: controller.stopTracking,
tooltip: "إيقاف المراقبة",
),
),
),
// LOADING OVERLAY (Smooth Frosted Glass like)
if (controller.isLoading.value &&
controller.driverLocation.value == null &&
controller.startPoint.value == null)
Container(
color: Colors.white.withOpacity(0.8),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
color: primaryColor, strokeWidth: 3),
const SizedBox(height: 16),
Text(
"جاري تحديد الموقع...",
style: TextStyle(
color: textPrimary,
fontWeight: FontWeight.bold,
fontSize: 16,
),
)
],
),
),
),
// ERROR OVERLAY
if (controller.hasError.value)
Center(
child: Container(
margin: const EdgeInsets.all(24),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.error_outline_rounded,
color: Colors.red, size: 40),
),
const SizedBox(height: 16),
Text(
"حدث خطأ",
style: TextStyle(
color: textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
controller.errorMessage.value,
textAlign: TextAlign.center,
style: TextStyle(color: textSecondary, height: 1.5),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: controller.stopTracking,
style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor,
foregroundColor: textPrimary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text("رجوع للبحث",
style: TextStyle(fontWeight: FontWeight.bold)),
),
)
],
),
),
),
// INFO CARD (Bottom Floating Card)
if (!controller.hasError.value && !controller.isLoading.value)
Positioned(
bottom: 30,
left: 20,
right: 20,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 24,
offset: const Offset(0, 10),
)
],
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(15),
),
child: Icon(Icons.person_rounded,
color: primaryColor, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.driverName.value,
style: TextStyle(
color: textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: controller.rideStatus.value
.toLowerCase() ==
'begin'
? const Color(0xFF10B981)
: const Color(0xFFF59E0B),
),
),
const SizedBox(width: 6),
Text(
controller.rideStatus.value,
style: TextStyle(
color: textSecondary,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
),
],
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Divider(height: 1, thickness: 1),
),
if (controller.driverLocation.value != null)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildModernInfoBadge(
Icons.speed_rounded,
"${controller.driverLocation.value!.speed.toStringAsFixed(1)} كم/س",
const Color(0xFF3B82F6),
),
Container(
width: 1,
height: 30,
color: Colors.grey.withOpacity(0.2)),
_buildModernInfoBadge(
Icons.access_time_rounded,
controller.driverLocation.value!.updatedAt
.split(' ')
.last,
const Color(0xFF8B5CF6),
),
],
)
else
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
color: primaryColor, strokeWidth: 2),
),
const SizedBox(width: 10),
Text(
"جاري الاتصال بالسائق...",
style: TextStyle(
color: primaryColor,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
],
),
],
),
),
),
),
],
);
}
// --- Helper Widgets ---
Widget _buildPointMarker(Color color) {
return Container(
decoration: BoxDecoration(
color: color.withOpacity(0.3),
shape: BoxShape.circle,
),
child: Center(
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.5),
blurRadius: 6,
spreadRadius: 1,
)
],
),
),
),
);
}
Widget _buildModernInfoBadge(IconData icon, String text, Color iconColor) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 16, color: iconColor),
),
const SizedBox(width: 8),
Text(
text,
style: TextStyle(
color: textPrimary,
fontSize: 14,
fontWeight: FontWeight.bold,
),
textDirection: TextDirection.ltr, // للحفاظ على اتجاه الأرقام
),
],
);
}
}
@@ -0,0 +1,564 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/employee_controller/employee_controller.dart';
import 'package:siro_admin/controller/functions/upload_image copy.dart'; // تأكد من مسار الملف الصحيح
import 'package:url_launcher/url_launcher.dart';
class EmployeePage extends StatelessWidget {
const EmployeePage({super.key});
@override
Widget build(BuildContext context) {
// حقن الكنترولر
Get.put(EmployeeController());
// ألوان الثيم
const Color bgColor = Color(0xFF0A0E27);
const Color cardColor = Color(0xFF1A1F3A);
const Color primaryAccent = Color(0xFF6366F1);
return Scaffold(
backgroundColor: bgColor,
body: GetBuilder<EmployeeController>(
builder: (controller) {
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
// 1. App Bar
SliverAppBar(
expandedHeight: 100,
floating: true,
pinned: true,
backgroundColor: bgColor,
elevation: 0,
flexibleSpace: FlexibleSpaceBar(
titlePadding: const EdgeInsets.only(bottom: 16),
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: primaryAccent.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.badge_rounded,
color: Colors.white, size: 18),
),
const SizedBox(width: 10),
const Text(
'الموظفون',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: Colors.white,
fontFamily: 'Segoe UI',
),
),
],
),
centerTitle: true,
background: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
primaryAccent.withOpacity(0.15),
bgColor,
],
),
),
),
),
),
// 2. قائمة الموظفين
if (controller.employee.isEmpty)
const SliverFillRemaining(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.people_outline,
size: 60, color: Colors.white24),
SizedBox(height: 16),
Text("لا يوجد موظفين حالياً",
style: TextStyle(color: Colors.white54)),
],
),
),
)
else
SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final employee = controller.employee[index];
return _EmployeeCard(
employee: employee,
index: index,
cardColor: cardColor,
primaryAccent: primaryAccent,
);
},
childCount: controller.employee.length,
),
),
),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
final controller = Get.find<EmployeeController>();
controller.id = controller.generateRandomId(8);
Get.to(() => _EmployeeFormScreen(controller: controller));
},
backgroundColor: primaryAccent,
child: const Icon(Icons.person_add_rounded, color: Colors.white),
),
);
}
}
// === بطاقة الموظف (تصميم جديد ومحسن) ===
class _EmployeeCard extends StatelessWidget {
final Map<String, dynamic> employee;
final int index;
final Color cardColor;
final Color primaryAccent;
const _EmployeeCard({
required this.employee,
required this.index,
required this.cardColor,
required this.primaryAccent,
});
@override
Widget build(BuildContext context) {
bool isExcellent = employee['status'].toString().contains('ممتاز');
Color statusColor = isExcellent ? const Color(0xFF10B981) : Colors.amber;
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: cardColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.05)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => Get.to(() => EmployeeDetails(index: index)),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// الصف العلوي: الحالة + أيقونة
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.05),
shape: BoxShape.circle,
),
child: Icon(Icons.person,
color: Colors.white.withOpacity(0.7), size: 20),
),
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border:
Border.all(color: statusColor.withOpacity(0.3)),
),
child: Text(
employee['status'] ?? 'Unknown',
style: TextStyle(
color: statusColor,
fontSize: 12,
fontWeight: FontWeight.bold,
height: 1.5,
),
textAlign: TextAlign.center,
),
),
),
],
),
const SizedBox(height: 12),
// الاسم في سطر كامل ومميز
Text(
employee['name'] ?? 'Unknown',
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
height: 1.3,
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Divider(height: 1, color: Colors.white10),
),
// تفاصيل التعليم والهاتف والموقع (مع دعم تعدد الأسطر)
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(Icons.phone_iphone_rounded,
employee['phone'] ?? '', Colors.white54),
const SizedBox(height: 12), // مسافة أكبر بين العناصر
_buildInfoRow(
Icons.school_outlined,
employee['education'] ?? 'غير محدد',
primaryAccent),
const SizedBox(height: 12), // مسافة أكبر
_buildInfoRow(Icons.location_on_outlined,
employee['site'] ?? 'غير محدد', Colors.blueGrey),
],
),
),
// زر الاتصال الجانبي
const SizedBox(width: 16),
Material(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: () =>
_makePhoneCall(employee['phone'].toString()),
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(14),
child: const Icon(Icons.call,
color: Colors.green, size: 24),
),
),
),
],
),
],
),
),
),
),
);
}
Widget _buildInfoRow(IconData icon, String text, Color iconColor) {
return Row(
crossAxisAlignment:
CrossAxisAlignment.start, // محاذاة الأيقونة مع بداية النص
children: [
Padding(
padding: const EdgeInsets.only(top: 2), // ضبط بسيط لموقع الأيقونة
child: Icon(icon, size: 16, color: iconColor.withOpacity(0.8)),
),
const SizedBox(width: 10),
Expanded(
child: Text(
text,
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 13,
height: 1.5, // تباعد الأسطر لسهولة القراءة
),
// تم إزالة maxLines و overflow للسماح بالنص بالنزول لأسطر متعددة
),
),
],
);
}
Future<void> _makePhoneCall(String phoneNumber) async {
final Uri launchUri = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(launchUri)) {
await launchUrl(launchUri);
}
}
}
// === شاشة إضافة موظف ===
class _EmployeeFormScreen extends StatelessWidget {
final EmployeeController controller;
const _EmployeeFormScreen({required this.controller});
@override
Widget build(BuildContext context) {
const Color bgColor = Color(0xFF0A0E27);
const Color inputColor = Color(0xFF1A1F3A);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title: const Text("إضافة موظف جديد",
style: TextStyle(color: Colors.white)),
backgroundColor: bgColor,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Get.back(),
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Form(
key: controller.formKey,
child: Column(
children: [
Row(
children: [
Expanded(
child: _UploadButton(
title: "الهوية (أمام)",
icon: Icons.credit_card,
onPressed: () async {
await ImageController().choosImage(AppLink.uploadEgypt,
'idFrontEmployee', controller.id);
},
),
),
const SizedBox(width: 16),
Expanded(
child: _UploadButton(
title: "الهوية (خلف)",
icon: Icons.credit_card_outlined,
onPressed: () async {
await ImageController().choosImage(AppLink.uploadEgypt,
'idbackEmployee', controller.id);
},
),
),
],
),
const SizedBox(height: 24),
_buildModernTextField(
controller.name, "الاسم الكامل", Icons.person, inputColor),
const SizedBox(height: 16),
_buildModernTextField(
controller.phone, "رقم الهاتف", Icons.phone, inputColor,
type: TextInputType.phone),
const SizedBox(height: 16),
_buildModernTextField(controller.education, "التعليم / الملاحظات",
Icons.school, inputColor),
const SizedBox(height: 16),
_buildModernTextField(controller.site, "الموقع / العنوان",
Icons.location_on, inputColor),
const SizedBox(height: 16),
_buildModernTextField(controller.status, "الحالة (مثال: ممتاز)",
Icons.star, inputColor),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () async {
if (controller.formKey.currentState!.validate()) {
await controller.addEmployee();
Get.back();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6366F1),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: const Text("حفظ البيانات",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold)),
),
),
],
),
),
),
);
}
Widget _buildModernTextField(TextEditingController controller, String hint,
IconData icon, Color fillColor,
{TextInputType type = TextInputType.text}) {
return Container(
decoration: BoxDecoration(
color: fillColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.1)),
),
child: TextFormField(
controller: controller,
keyboardType: type,
style: const TextStyle(color: Colors.white),
maxLines: null, // السماح بتعدد الأسطر عند الإدخال أيضاً
decoration: InputDecoration(
labelText: hint,
labelStyle: TextStyle(color: Colors.white.withOpacity(0.5)),
prefixIcon: Icon(icon, color: Colors.white38, size: 20),
border: InputBorder.none,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
validator: (value) =>
value == null || value.isEmpty ? 'حقل مطلوب' : null,
),
);
}
}
class _UploadButton extends StatelessWidget {
final String title;
final IconData icon;
final VoidCallback onPressed;
const _UploadButton(
{required this.title, required this.icon, required this.onPressed});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onPressed,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 20),
decoration: BoxDecoration(
color: const Color(0xFF1A1F3A),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0xFF6366F1).withOpacity(0.3),
style: BorderStyle.solid),
),
child: Column(
children: [
Icon(icon, color: const Color(0xFF6366F1), size: 30),
const SizedBox(height: 8),
Text(title,
style: const TextStyle(color: Colors.white70, fontSize: 12)),
const SizedBox(height: 4),
const Text("اضغط للرفع",
style: TextStyle(color: Colors.white38, fontSize: 10)),
],
),
),
);
}
}
// === شاشة التفاصيل ===
class EmployeeDetails extends StatelessWidget {
final int index;
const EmployeeDetails({super.key, required this.index});
@override
Widget build(BuildContext context) {
const Color bgColor = Color(0xFF0A0E27);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title:
const Text('تفاصيل الموظف', style: TextStyle(color: Colors.white)),
backgroundColor: bgColor,
iconTheme: const IconThemeData(color: Colors.white),
elevation: 0,
),
body: GetBuilder<EmployeeController>(
builder: (controller) {
final employeeId = controller.employee[index]['id'];
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("الهوية الأمامية",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
_buildImageViewer(
'${AppLink.server}/card_image/idFrontEmployee-$employeeId.jpg',
),
const SizedBox(height: 30),
const Text("الهوية الخلفية",
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
_buildImageViewer(
'${AppLink.server}/card_image/idbackEmployee-$employeeId.jpg',
),
],
),
);
},
),
);
}
Widget _buildImageViewer(String url) {
return Container(
width: double.infinity,
height: 220,
decoration: BoxDecoration(
color: const Color(0xFF1A1F3A),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withOpacity(0.1)),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.network(
url,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.broken_image_rounded,
color: Colors.white24, size: 50),
SizedBox(height: 8),
Text("فشل تحميل الصورة",
style: TextStyle(color: Colors.white24)),
],
),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return const Center(
child: CircularProgressIndicator(color: Color(0xFF6366F1)));
},
),
),
);
}
}
@@ -0,0 +1,266 @@
// ═══════════════════════════════════════════════════════════════
// driver_fingerprint_migration.dart
// ───────────────────────────────────────────────────────────────
// المنطق ببساطة:
// 1. خذ البصمة كما هي من DB
// 2. split('_') → احذف آخر جزء (OS version)
// 3. join('_') → encrypt → رفع
//
// مثال:
// "abc123_SamsungA51_13" → "abc123_SamsungA51" → encrypt
// "TECNO_LH7n-GL_14" → "TECNO_LH7n-GL" → encrypt
// "unknown_2412DPC0AG_15" → "unknown_2412DPC0AG" → encrypt
// ═══════════════════════════════════════════════════════════════
import 'package:flutter/material.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
import '../../../print.dart';
class DriverFingerprintMigrationTool extends StatefulWidget {
const DriverFingerprintMigrationTool({super.key});
@override
State<DriverFingerprintMigrationTool> createState() =>
_DriverFingerprintMigrationToolState();
}
class _DriverFingerprintMigrationToolState
extends State<DriverFingerprintMigrationTool> {
bool _isRunning = false;
bool _isDone = false;
int _total = 0;
int _processed = 0;
int _updated = 0;
int _failed = 0;
String _currentLog = '';
static const int _batchSize = 50;
// ─────────────────────────────────────────────────────────────
// المنطق الأساسي — حذف آخر جزء بعد "_"
// ─────────────────────────────────────────────────────────────
String _removeLastSegment(String raw) {
final parts = raw.split('_');
if (parts.length <= 1) return raw; // جزء واحد — ما في شيء نحذفه
parts.removeLast();
return parts.join('_');
}
Future<void> _startMigration() async {
setState(() {
_isRunning = true;
_isDone = false;
_processed = 0;
_updated = 0;
_failed = 0;
_currentLog = 'جارٍ جلب بصمات السائقين...';
});
try {
final records = await _fetchAll();
if (records == null) {
_log('❌ فشل في جلب البيانات');
setState(() => _isRunning = false);
return;
}
_total = records.length;
_log('✅ تم جلب $_total بصمة — بدء المعالجة...');
for (int i = 0; i < records.length; i += _batchSize) {
final batch = records.skip(i).take(_batchSize).toList();
_log('⚙️ معالجة ${i + 1} → ${i + batch.length} من $_total');
await Future.wait(batch.map(_processSingle));
if (i + _batchSize < records.length) {
await Future.delayed(const Duration(milliseconds: 300));
}
}
_log('🎉 اكتمل!\nمحدَّث: $_updated | فاشل: $_failed');
setState(() {
_isDone = true;
_isRunning = false;
});
} catch (e) {
_log('❌ خطأ: $e');
setState(() => _isRunning = false);
}
}
Future<List<Map<String, dynamic>>?> _fetchAll() async {
try {
final response = await CRUD().post(
link: AppLink.getAllDriverFingerprints,
payload: {'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd'},
);
if (response == 'failure' || response == null) return null;
final data = response['data'];
if (data is! List) return null;
return List<Map<String, dynamic>>.from(data);
} catch (e) {
Log.print('fetchAll error: $e');
return null;
}
}
Future<void> _processSingle(Map<String, dynamic> record) async {
final captainId = record['captain_id']?.toString() ?? '';
final rawFp = record['fingerPrint']?.toString() ?? '';
if (captainId.isEmpty || rawFp.isEmpty) {
setState(() {
_failed++;
_processed++;
});
return;
}
try {
// ── حذف آخر جزء (OS version) ─────────────────────────────
final String newRaw = _removeLastSegment(rawFp);
final String encrypted = EncryptionHelper.instance.encryptData(newRaw);
Log.print('🔄 [$captainId] "$rawFp" → "$newRaw" → encrypted');
// ── رفع للسيرفر ──────────────────────────────────────────
final res = await CRUD().post(
link: AppLink.updateDriverFingerprintAdmin,
payload: {
'captain_id': captainId,
'fingerprint': encrypted,
'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd',
},
);
if (res != 'failure' && res?['status'] == 'success') {
setState(() {
_updated++;
_processed++;
});
} else {
setState(() {
_failed++;
_processed++;
});
}
} catch (e) {
Log.print('❌ [$captainId]: $e');
setState(() {
_failed++;
_processed++;
});
}
}
void _log(String msg) {
Log.print(msg);
setState(() => _currentLog = msg);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Driver FP Migration')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.orange.shade200),
),
child: const Text(
'⚠️ تُستخدم مرة واحدة فقط\n\n'
'"abc123_Samsung_13" → "abc123_Samsung" → encrypt\n'
'"TECNO_LH7n_14" → "TECNO_LH7n" → encrypt',
style:
TextStyle(fontSize: 13, height: 1.7, fontFamily: 'monospace'),
),
),
const SizedBox(height: 24),
if (_total > 0) ...[
Text('التقدم: $_processed / $_total',
style: const TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
LinearProgressIndicator(
value: _total > 0 ? _processed / _total : 0,
backgroundColor: Colors.grey.shade200,
color: _isDone ? Colors.green : Colors.blue,
minHeight: 8,
),
const SizedBox(height: 16),
],
if (_processed > 0)
Row(children: [
_chip('محدَّث', _updated, Colors.green),
const SizedBox(width: 8),
_chip('فاشل', _failed, Colors.red),
]),
const SizedBox(height: 16),
if (_currentLog.isNotEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Text(_currentLog,
style:
const TextStyle(fontFamily: 'monospace', fontSize: 12)),
),
const Spacer(),
SizedBox(
width: double.infinity,
height: 52,
child: ElevatedButton(
onPressed: (_isRunning || _isDone) ? null : _startMigration,
style: ElevatedButton.styleFrom(
backgroundColor: _isDone ? Colors.green : Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: _isRunning
? const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2)),
SizedBox(width: 12),
Text('جارٍ الترحيل...',
style:
TextStyle(color: Colors.white, fontSize: 16)),
],
)
: Text(
_isDone ? '✅ اكتمل الترحيل' : 'بدء ترحيل بصمات السائقين',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
),
),
]),
),
);
}
Widget _chip(String label, int value, Color color) => Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Text('$label: $value',
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
);
}
@@ -0,0 +1,855 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/main.dart';
import '../../../constant/box_name.dart';
// ─── Custom Colors ────────────────────────────────────────────────────────────
class _AppColors {
static const bg = Color(0xFF0A0D14);
static const surface = Color(0xFF111622);
static const card = Color(0xFF161D2E);
static const border = Color(0xFF1F2D4A);
static const accent = Color(0xFF00E5FF);
static const accentDim = Color(0xFF0097A7);
static const accentGlow = Color(0x2200E5FF);
static const accentDecrypt = Color(0xFF7C4DFF);
static const accentDecryptDim = Color(0xFF512DA8);
static const accentDecryptGlow = Color(0x227C4DFF);
static const textPrimary = Color(0xFFE8F0FE);
static const textSec = Color(0xFF7A8BAA);
static const success = Color(0xFF00E676);
static const error = Color(0xFFFF5252);
}
class EncryptToolPage extends StatefulWidget {
final String adminToken;
const EncryptToolPage({Key? key, required this.adminToken}) : super(key: key);
@override
State<EncryptToolPage> createState() => _EncryptToolPageState();
}
class _EncryptToolPageState extends State<EncryptToolPage>
with SingleTickerProviderStateMixin {
final TextEditingController _inputController = TextEditingController();
final TextEditingController _outputController = TextEditingController();
String _output = '';
bool _loading = false;
String? _error;
bool _isInputCopied = false;
bool _isOutputCopied = false;
late final AnimationController _glowController;
late final Animation<double> _glowAnimation;
@override
void initState() {
super.initState();
_glowController = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
)..repeat(reverse: true);
_glowAnimation = Tween<double>(begin: 0.4, end: 1.0).animate(
CurvedAnimation(parent: _glowController, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_inputController.dispose();
_outputController.dispose();
_glowController.dispose();
super.dispose();
}
// ─── Logic (unchanged) ──────────────────────────────────────────────────────
Future<void> _callTool(String action) async {
FocusScope.of(context).unfocus();
setState(() {
_loading = true;
_error = null;
_output = '';
_outputController.clear();
_isInputCopied = false;
_isOutputCopied = false;
});
try {
final response = await CRUD().post(
link: '${AppLink.server}/ggg.php',
payload: {
'action': action,
'text': _inputController.text,
'admin_phone': box.read(BoxName.adminPhone) ?? '',
},
);
if (response == 'failure') {
setState(() => _error = 'حدث خطأ في الاتصال بالخادم. حاول مرة أخرى.');
} else {
if (response['status'] == 'success') {
setState(() {
_output = (response['result'] ?? '').toString();
_outputController.text = _output;
});
} else {
setState(() =>
_error = response['message']?.toString() ?? 'حدث خطأ غير معروف.');
}
}
} catch (e) {
setState(() => _error = 'مشكلة في الشبكة: $e');
} finally {
setState(() => _loading = false);
}
}
void _showSnackBar(String message, {bool isError = false}) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
Icon(isError ? Icons.error_outline : Icons.check_circle,
color: isError ? _AppColors.error : _AppColors.success),
const SizedBox(width: 12),
Text(
message,
style: const TextStyle(
fontFamily: 'Cairo',
fontWeight: FontWeight.bold,
color: _AppColors.textPrimary,
),
),
],
),
backgroundColor:
isError ? const Color(0xFF1A0808) : const Color(0xFF081A0F),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
side: BorderSide(
color: isError ? _AppColors.error : _AppColors.success,
width: 1,
),
),
margin: const EdgeInsets.all(16),
duration: const Duration(seconds: 2),
),
);
}
Future<void> _copyText(bool isInput) async {
final textToCopy = isInput ? _inputController.text : _outputController.text;
if (textToCopy.isEmpty) {
_showSnackBar('لا يوجد نص لنسخه!', isError: true);
return;
}
await Clipboard.setData(ClipboardData(text: textToCopy));
if (isInput) {
setState(() => _isInputCopied = true);
Future.delayed(const Duration(seconds: 2), () {
if (mounted) setState(() => _isInputCopied = false);
});
} else {
setState(() => _isOutputCopied = true);
Future.delayed(const Duration(seconds: 2), () {
if (mounted) setState(() => _isOutputCopied = false);
});
}
_showSnackBar('تم النسخ بنجاح!');
}
Future<void> _pasteText(bool isInput) async {
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
final textToPaste = clipboardData?.text ?? '';
if (textToPaste.isEmpty) {
_showSnackBar('الحافظة فارغة!', isError: true);
return;
}
setState(() {
if (isInput) {
_inputController.text = textToPaste;
} else {
_output = textToPaste;
_outputController.text = textToPaste;
}
});
_showSnackBar('تم اللصق بنجاح!');
}
// ─── UI Helpers ─────────────────────────────────────────────────────────────
Widget _buildActionButtons({
required bool isInput,
required bool isCopied,
Color accentColor = _AppColors.accent,
}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_MiniIconButton(
label: 'لصق',
icon: Icons.content_paste_rounded,
color: accentColor,
onTap: () => _pasteText(isInput),
),
const SizedBox(width: 6),
_MiniIconButton(
label: isCopied ? 'تم!' : 'نسخ',
icon: isCopied ? Icons.check_circle_rounded : Icons.copy_rounded,
color: isCopied ? _AppColors.success : accentColor,
onTap: () => _copyText(isInput),
),
],
);
}
InputDecoration _buildInputDecoration(String hint) => InputDecoration(
hintText: hint,
hintStyle: const TextStyle(color: _AppColors.textSec, fontSize: 14),
filled: true,
fillColor: const Color(0xFF0C1120),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: _AppColors.border, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(color: _AppColors.accent, width: 1.5),
),
contentPadding: const EdgeInsets.all(16),
);
// ─── Build ──────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _AppColors.bg,
body: Stack(
children: [
// ── Ambient background glow ──────────────────────────────────────────
Positioned(
top: -120,
left: -80,
child: AnimatedBuilder(
animation: _glowAnimation,
builder: (_, __) => Opacity(
opacity: _glowAnimation.value * 0.25,
child: Container(
width: 380,
height: 380,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [Color(0xFF00E5FF), Colors.transparent],
),
),
),
),
),
),
Positioned(
bottom: -100,
right: -60,
child: AnimatedBuilder(
animation: _glowAnimation,
builder: (_, __) => Opacity(
opacity: (1 - _glowAnimation.value) * 0.2,
child: Container(
width: 320,
height: 320,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [Color(0xFF7C4DFF), Colors.transparent],
),
),
),
),
),
),
// ── Content ──────────────────────────────────────────────────────────
SafeArea(
child: Column(
children: [
// ── AppBar ─────────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 16, 0),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded,
color: _AppColors.textSec, size: 20),
onPressed: () => Navigator.pop(context),
),
const Spacer(),
Row(
children: [
Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: _AppColors.accent,
boxShadow: [
BoxShadow(
color: _AppColors.accentGlow,
blurRadius: 8,
spreadRadius: 2,
),
],
),
),
const SizedBox(width: 10),
const Text(
'أداة التشفير',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: _AppColors.textPrimary,
letterSpacing: 0.5,
),
),
],
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: _AppColors.accentGlow,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: _AppColors.accent.withOpacity(0.3)),
),
child: const Text(
'AES-256',
style: TextStyle(
color: _AppColors.accent,
fontSize: 11,
fontWeight: FontWeight.bold,
letterSpacing: 1.5,
),
),
),
],
),
),
// ── Scrollable body ───────────────────────────────────────────
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 32),
physics: const BouncingScrollPhysics(),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 650),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ─── Input Card ─────────────────────────────────────
_GlassCard(
borderColor: _AppColors.accent.withOpacity(0.2),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _AppColors.accentGlow,
borderRadius:
BorderRadius.circular(10),
),
child: const Icon(
Icons.text_fields_rounded,
color: _AppColors.accent,
size: 18),
),
const SizedBox(width: 12),
const Text(
'النص الأصلي',
style: TextStyle(
color: _AppColors.textPrimary,
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
],
),
_buildActionButtons(
isInput: true,
isCopied: _isInputCopied,
accentColor: _AppColors.accent,
),
],
),
const SizedBox(height: 16),
// Input field
TextField(
controller: _inputController,
maxLines: 5,
minLines: 3,
textDirection: TextDirection.ltr,
style: const TextStyle(
color: _AppColors.textPrimary,
fontSize: 15,
height: 1.6,
fontFamily: 'monospace',
),
onTap: () {
if (_inputController.text.isNotEmpty) {
_inputController.selection =
TextSelection(
baseOffset: 0,
extentOffset:
_inputController.text.length,
);
}
},
decoration: _buildInputDecoration(
'اكتب أو الصق النص هنا...'),
),
const SizedBox(height: 24),
// Action buttons row
Row(
children: [
// Encrypt
Expanded(
child: _ActionButton(
label: 'تشفير',
icon: Icons.lock_rounded,
isLoading: _loading,
onPressed: _loading
? null
: () => _callTool('encrypt'),
gradient: const LinearGradient(
colors: [
Color(0xFF00B4D8),
Color(0xFF00E5FF)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
glowColor: _AppColors.accentGlow,
borderColor: _AppColors.accent,
),
),
const SizedBox(width: 14),
// Decrypt
Expanded(
child: _ActionButton(
label: 'فك التشفير',
icon: Icons.lock_open_rounded,
isLoading: _loading,
onPressed: _loading
? null
: () => _callTool('decrypt'),
gradient: const LinearGradient(
colors: [
Color(0xFF5B2EA6),
Color(0xFF7C4DFF)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
glowColor:
_AppColors.accentDecryptGlow,
borderColor: _AppColors.accentDecrypt,
),
),
],
),
],
),
),
// ─── Error message ───────────────────────────────────
AnimatedSize(
duration: const Duration(milliseconds: 300),
child: _error != null
? Container(
margin: const EdgeInsets.only(top: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1A0808),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: _AppColors.error
.withOpacity(0.4)),
),
child: Row(
children: [
const Icon(
Icons.error_outline_rounded,
color: _AppColors.error,
size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(
_error!,
style: const TextStyle(
color: _AppColors.error,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
],
),
)
: const SizedBox.shrink(),
),
// ─── Output Card ─────────────────────────────────────
const SizedBox(height: 20),
_GlassCard(
borderColor:
_AppColors.accentDecrypt.withOpacity(0.25),
headerWidget: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 14),
decoration: const BoxDecoration(
color: Color(0xFF16102A),
borderRadius: BorderRadius.vertical(
top: Radius.circular(20)),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _AppColors.accentDecryptGlow,
borderRadius:
BorderRadius.circular(10),
),
child: const Icon(
Icons.shield_rounded,
color: _AppColors.accentDecrypt,
size: 18),
),
const SizedBox(width: 12),
const Text(
'النتيجة',
style: TextStyle(
color: _AppColors.textPrimary,
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
],
),
_buildActionButtons(
isInput: false,
isCopied: _isOutputCopied,
accentColor: _AppColors.accentDecrypt,
),
],
),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: TextField(
controller: _outputController,
readOnly: true,
maxLines: 5,
minLines: 3,
textDirection: TextDirection.ltr,
style: const TextStyle(
color: _AppColors.textPrimary,
fontSize: 15,
height: 1.6,
fontFamily: 'monospace',
letterSpacing: 0.5,
),
decoration: const InputDecoration(
hintText: 'ستظهر النتيجة هنا...',
hintStyle: TextStyle(
color: _AppColors.textSec,
fontSize: 14),
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
onTap: () {
if (_outputController.text.isNotEmpty) {
_outputController.selection =
TextSelection(
baseOffset: 0,
extentOffset:
_outputController.text.length,
);
}
},
),
),
),
// ─── Footer hint ─────────────────────────────────────
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.info_outline_rounded,
color: _AppColors.textSec, size: 13),
const SizedBox(width: 6),
const Text(
'البيانات مشفرة بالكامل ولا تُخزَّن على الخادم',
style: TextStyle(
color: _AppColors.textSec,
fontSize: 12,
),
),
],
),
],
),
),
),
),
),
],
),
),
],
),
);
}
}
// ─── Reusable Widgets ──────────────────────────────────────────────────────────
/// Glass card with optional custom header widget
class _GlassCard extends StatelessWidget {
final Widget child;
final Widget? headerWidget;
final Color borderColor;
const _GlassCard({
required this.child,
this.headerWidget,
this.borderColor = _AppColors.border,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: _AppColors.card,
borderRadius: BorderRadius.circular(22),
border: Border.all(color: borderColor, width: 1.2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.35),
blurRadius: 24,
offset: const Offset(0, 8),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(22),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (headerWidget != null) headerWidget!,
if (headerWidget == null)
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 0),
child: child,
)
else
child,
if (headerWidget == null) const SizedBox(height: 20),
],
),
),
);
}
}
/// Gradient action button with glow
class _ActionButton extends StatefulWidget {
final String label;
final IconData icon;
final bool isLoading;
final VoidCallback? onPressed;
final Gradient gradient;
final Color glowColor;
final Color borderColor;
const _ActionButton({
required this.label,
required this.icon,
required this.isLoading,
required this.onPressed,
required this.gradient,
required this.glowColor,
required this.borderColor,
});
@override
State<_ActionButton> createState() => _ActionButtonState();
}
class _ActionButtonState extends State<_ActionButton>
with SingleTickerProviderStateMixin {
late final AnimationController _ctrl;
late final Animation<double> _scale;
@override
void initState() {
super.initState();
_ctrl = AnimationController(
vsync: this, duration: const Duration(milliseconds: 120));
_scale = Tween<double>(begin: 1.0, end: 0.95)
.animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeOut));
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => _ctrl.forward(),
onTapUp: (_) => _ctrl.reverse(),
onTapCancel: () => _ctrl.reverse(),
onTap: widget.onPressed,
child: AnimatedBuilder(
animation: _scale,
builder: (_, child) =>
Transform.scale(scale: _scale.value, child: child),
child: AnimatedOpacity(
opacity: widget.onPressed == null ? 0.4 : 1.0,
duration: const Duration(milliseconds: 200),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
gradient: widget.gradient,
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: widget.glowColor,
blurRadius: 16,
spreadRadius: 1,
offset: const Offset(0, 4),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: widget.isLoading
? [
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2),
),
]
: [
Icon(widget.icon, color: Colors.white, size: 18),
const SizedBox(width: 8),
Text(
widget.label,
style: const TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w700,
letterSpacing: 0.3,
),
),
],
),
),
),
),
);
}
}
/// Small inline icon button (copy/paste)
class _MiniIconButton extends StatelessWidget {
final String label;
final IconData icon;
final Color color;
final VoidCallback onTap;
const _MiniIconButton({
required this.label,
required this.icon,
required this.color,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.25)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 14),
const SizedBox(width: 5),
Text(
label,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
),
),
),
);
}
}
@@ -0,0 +1,366 @@
// ═══════════════════════════════════════════════════════════════
// fingerprint_migration.dart
// ───────────────────────────────────────────────────────────────
// أداة ترحيل البصمات القديمة للنظام الجديد
// ───────────────────────────────────────────────────────────────
// المشكلة:
// البصمة القديمة = encrypt(androidId_model_osVersion)
// البصمة الجديدة = encrypt(androidId_model)
//
// الحل:
// 1. نجيب كل البصمات من السيرفر (batch 50 في المرة)
// 2. نفك تشفير كل بصمة بـ EncryptionHelper
// 3. نحذف آخر جزء (osVersion) مع الـ _ قبله
// 4. نعيد التشفير
// 5. نرفع البصمة المحدّثة للسيرفر
//
// يُستخدم مرة واحدة فقط ثم يُحذف من التطبيق
// ═══════════════════════════════════════════════════════════════
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:siro_admin/controller/functions/encrypt_decrypt.dart' as X;
import '../../../constant/char_map.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
import '../../../print.dart';
class FingerprintMigrationTool extends StatefulWidget {
const FingerprintMigrationTool({super.key});
@override
State<FingerprintMigrationTool> createState() =>
_FingerprintMigrationToolState();
}
class _FingerprintMigrationToolState extends State<FingerprintMigrationTool> {
// ── حالة الترحيل ──────────────────────────────────────────
bool _isRunning = false;
bool _isDone = false;
int _total = 0;
int _processed = 0;
int _updated = 0; // بصمات تم تحديثها
int _skipped = 0; // بصمات كانت بالفعل بالنظام الجديد
int _failed = 0; // فشل في المعالجة
String _currentLog = '';
static const int _batchSize = 50;
// ─────────────────────────────────────────────────────────────
// الدالة الرئيسية للترحيل
// ─────────────────────────────────────────────────────────────
Future<void> _startMigration() async {
setState(() {
_isRunning = true;
_isDone = false;
_processed = 0;
_updated = 0;
_skipped = 0;
_failed = 0;
_currentLog = 'جارٍ جلب البصمات من السيرفر...';
});
try {
// ── 1. جلب كل البصمات من السيرفر ──────────────────────
final allFingerprints = await _fetchAllFingerprints();
if (allFingerprints == null) {
_log('❌ فشل في جلب البيانات من السيرفر');
setState(() => _isRunning = false);
return;
}
_total = allFingerprints.length;
_log('✅ تم جلب $_total بصمة — بدء المعالجة...');
// ── 2. معالجة على batches ──────────────────────────────
for (int i = 0; i < allFingerprints.length; i += _batchSize) {
final batch = allFingerprints.skip(i).take(_batchSize).toList();
_log('⚙️ معالجة ${i + 1} → ${i + batch.length} من $_total');
// معالجة الـ batch بالتوازي
await Future.wait(
batch.map((record) => _processSingleRecord(record)),
);
// استراحة قصيرة بين الـ batches لحماية السيرفر
if (i + _batchSize < allFingerprints.length) {
await Future.delayed(const Duration(milliseconds: 300));
}
}
_log('🎉 اكتمل الترحيل!\n'
'محدَّث: $_updated | متجاوز: $_skipped | فاشل: $_failed');
setState(() {
_isDone = true;
_isRunning = false;
});
} catch (e) {
_log('❌ خطأ عام: $e');
setState(() => _isRunning = false);
}
}
// ─────────────────────────────────────────────────────────────
// جلب كل البصمات من السيرفر
// ─────────────────────────────────────────────────────────────
Future<List<Map<String, dynamic>>?> _fetchAllFingerprints() async {
try {
final response = await CRUD().post(
link: AppLink.getAllFingerprints, // أضفه في AppLink
payload: {
'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd'
}, // مفتاح أمان للـ endpoint
);
if (response == 'failure' || response == null) return null;
final data = response['data'];
if (data is! List) return null;
return List<Map<String, dynamic>>.from(data);
} catch (e) {
Log.print('fetchAllFingerprints error: $e');
return null;
}
}
// ─────────────────────────────────────────────────────────────
// معالجة بصمة واحدة
// ─────────────────────────────────────────────────────────────
Future<void> _processSingleRecord(Map<String, dynamic> record) async {
final String passengerID = record['passengerID']?.toString() ?? '';
final String encryptedFp = record['fingerPrint']?.toString() ?? '';
final String userType = record['userType']?.toString() ?? 'passenger';
if (passengerID.isEmpty || encryptedFp.isEmpty) {
setState(() {
_failed++;
_processed++;
});
return;
}
try {
// ── فك التشفير ────────────────────────────────────────
final String rawFp = EncryptionHelper.instance.decryptData(encryptedFp);
// ── تحليل البصمة ──────────────────────────────────────
// الشكل القديم: "androidId_model_osVersion" (3 أجزاء أو أكثر)
// الشكل الجديد: "androidId_model" (جزءان فقط)
final List<String> parts = rawFp.split('_');
if (parts.length <= 2) {
// البصمة بالفعل بالنظام الجديد — تجاوزها
setState(() {
_skipped++;
_processed++;
});
return;
}
// ── حذف آخر جزء (osVersion) ──────────────────────────
// مثال: "abc123_SamsungA51_13" → "abc123_SamsungA51"
// نأخذ أول جزأين فقط بغض النظر عن عدد الأجزاء
final String newRawFp = '${parts[0]}_${parts[1]}';
// ── إعادة التشفير ─────────────────────────────────────
final String newEncryptedFp =
EncryptionHelper.instance.encryptData(newRawFp);
// ── رفع البصمة الجديدة للسيرفر ───────────────────────
final response = await CRUD().post(
link: AppLink.updateFingerprintAdmin, // أضفه في AppLink
payload: {
'passengerID': passengerID,
'fingerprint': newEncryptedFp,
'userType': userType,
'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd',
},
);
if (response != 'failure' && response?['status'] == 'success') {
setState(() {
_updated++;
_processed++;
});
Log.print('✅ Updated: $passengerID | $rawFp → $newRawFp');
} else {
setState(() {
_failed++;
_processed++;
});
Log.print('❌ Failed update: $passengerID');
}
} catch (e) {
// فشل فك التشفير أو إعادة التشفير
setState(() {
_failed++;
_processed++;
});
Log.print('❌ Process error for $passengerID: $e');
}
}
void _log(String message) {
Log.print(message);
setState(() => _currentLog = message);
}
// ─────────────────────────────────────────────────────────────
// UI
// ─────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Fingerprint Migration Tool')),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── شرح الأداة ──────────────────────────────────
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.orange.shade200),
),
child: const Text(
'⚠️ هذه الأداة تُستخدم مرة واحدة فقط\n'
'تقوم بتحديث بصمات الأجهزة القديمة\n'
'لتكون متوافقة مع النظام الجديد (بدون OS version)',
style: TextStyle(fontSize: 14, height: 1.6),
),
),
Container(
child: TextButton(
onPressed: () {
print(EncryptionHelper.instance.decryptData('hbgbitbXrXrBr'));
},
child: Text(
"Decrypt Test",
),
),
),
Container(
child: TextButton(
onPressed: () {
print(EncryptionHelper.instance.encryptData(
'1B501143-C579-461C-B556-4E8B390EEFE1_iPhone'));
},
child: Text(
"Encrypt Test",
),
),
),
Container(
child: TextButton(
onPressed: () {
print(r('hbgbitbXrXrBr'));
},
child: Text(
"decrypt X.r",
),
),
),
const SizedBox(height: 24),
// ── شريط التقدم ─────────────────────────────────
if (_total > 0) ...[
Text('التقدم: $_processed / $_total'),
const SizedBox(height: 8),
LinearProgressIndicator(
value: _total > 0 ? _processed / _total : 0,
backgroundColor: Colors.grey.shade200,
color: _isDone ? Colors.green : Colors.blue,
),
const SizedBox(height: 16),
],
// ── إحصائيات ────────────────────────────────────
if (_processed > 0)
Row(children: [
_statChip('محدَّث', _updated, Colors.green),
const SizedBox(width: 8),
_statChip('متجاوز', _skipped, Colors.blue),
const SizedBox(width: 8),
_statChip('فاشل', _failed, Colors.red),
]),
const SizedBox(height: 16),
// ── السجل الحالي ─────────────────────────────────
if (_currentLog.isNotEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Text(_currentLog,
style: const TextStyle(fontFamily: 'monospace')),
),
const Spacer(),
// ── زر التشغيل ──────────────────────────────────
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: (_isRunning || _isDone) ? null : _startMigration,
style: ElevatedButton.styleFrom(
backgroundColor: _isDone ? Colors.green : Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: _isRunning
? const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2),
),
SizedBox(width: 12),
Text('جارٍ الترحيل...',
style: TextStyle(color: Colors.white)),
],
)
: Text(
_isDone ? '✅ اكتمل الترحيل' : 'بدء الترحيل',
style:
const TextStyle(color: Colors.white, fontSize: 16),
),
),
),
],
),
),
);
}
Widget _statChip(String label, int value, Color color) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Text('$label: $value',
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
);
}
}
@@ -0,0 +1,659 @@
import 'package:flutter/material.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
class ErrorLog {
final String id;
final String error;
final String userId;
final String userType;
final String phone;
final String createdAt;
final String device;
final String details;
final String status;
ErrorLog({
required this.id,
required this.error,
required this.userId,
required this.userType,
required this.phone,
required this.createdAt,
required this.device,
required this.details,
required this.status,
});
factory ErrorLog.fromJson(Map<String, dynamic> j) => ErrorLog(
id: (j['id'] ?? '').toString(),
error: (j['error'] ?? '').toString(),
userId: (j['userId'] ?? '').toString(),
userType: (j['userType'] ?? '').toString(),
phone: (j['phone'] ?? '').toString(),
createdAt: (j['created_at'] ?? '').toString(),
device: (j['device'] ?? '').toString(),
details: (j['details'] ?? '').toString(),
status: (j['status'] ?? '').toString(),
);
}
class ErrorListPage extends StatefulWidget {
const ErrorListPage({Key? key}) : super(key: key);
@override
State<ErrorListPage> createState() => _ErrorListPageState();
}
class _ErrorListPageState extends State<ErrorListPage> {
static String baseUrl = '${AppLink.server}/Admin/error';
static const String listEndpoint = "error_list_last20.php";
static const String searchEndpoint = "error_search_by_phone.php";
final TextEditingController _phoneCtrl = TextEditingController();
bool _loading = false;
String? _errorMsg;
List<ErrorLog> _items = [];
bool _searchMode = false;
@override
void initState() {
super.initState();
_fetchLast20();
}
Future<void> _fetchLast20() async {
setState(() {
_loading = true;
_errorMsg = null;
_searchMode = false;
});
try {
final res =
await CRUD().post(link: "$baseUrl/$listEndpoint", payload: {});
final map = (res);
if (map['status'] == 'success') {
final List data = (map['message'] ?? []) as List;
final items = data.map((e) => ErrorLog.fromJson(e)).toList();
setState(() {
_items = items.cast<ErrorLog>();
});
} else {
setState(() => _errorMsg = map['message']?.toString() ?? 'Failed');
}
} catch (e) {
setState(() => _errorMsg = e.toString());
} finally {
if (mounted) setState(() => _loading = false);
}
}
Future<void> _searchByPhone() async {
final phone = _phoneCtrl.text.trim();
if (phone.isEmpty) {
return _fetchLast20();
}
setState(() {
_loading = true;
_errorMsg = null;
_searchMode = true;
});
try {
final res = await CRUD()
.post(link: "$baseUrl/$searchEndpoint", payload: {"phone": phone});
final map = (res);
if (map['status'] == 'success') {
final List data = (map['message'] ?? []) as List;
final items = data.map((e) => ErrorLog.fromJson(e)).toList();
setState(() {
_items = items.cast<ErrorLog>();
});
} else {
setState(() => _errorMsg = map['message']?.toString() ?? 'Failed');
}
} catch (e) {
setState(() => _errorMsg = e.toString());
} finally {
if (mounted) setState(() => _loading = false);
}
}
void _clearSearch() {
_phoneCtrl.clear();
FocusScope.of(context).unfocus();
_fetchLast20();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF0F4F8),
appBar: _buildAppBar(),
body: Column(
children: [
_SearchBar(
controller: _phoneCtrl,
onSearch: _searchByPhone,
onClear: _clearSearch,
),
Expanded(
child: _buildBody(),
),
],
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
elevation: 0,
backgroundColor: const Color(0xFF0F172A),
foregroundColor: Colors.white,
centerTitle: true,
title: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.2)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, color: Colors.red, size: 22),
const SizedBox(width: 8),
const Text(
"سجل الأخطاء",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
Widget _buildBody() {
if (_loading) {
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF0F172A)),
),
);
}
if (_errorMsg != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red.shade300),
const SizedBox(height: 16),
Text(
_errorMsg!,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.red.shade700,
fontSize: 14,
),
),
],
),
);
}
if (_items.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.check_circle_outline,
size: 64,
color: Colors.green.shade300,
),
const SizedBox(height: 16),
const Text(
'لا توجد سجلات أخطاء',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
],
),
);
}
return RefreshIndicator(
onRefresh: () async {
if (_searchMode) {
await _searchByPhone();
} else {
await _fetchLast20();
}
},
color: const Color(0xFF0F172A),
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
itemCount: _items.length,
itemBuilder: (context, index) {
return _ErrorTile(_items[index], index);
},
),
);
}
@override
void dispose() {
_phoneCtrl.dispose();
super.dispose();
}
}
class _SearchBar extends StatefulWidget {
final TextEditingController controller;
final VoidCallback onSearch;
final VoidCallback onClear;
const _SearchBar({
required this.controller,
required this.onSearch,
required this.onClear,
});
@override
State<_SearchBar> createState() => _SearchBarState();
}
class _SearchBarState extends State<_SearchBar> {
bool _isFocused = false;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.white, Colors.grey.shade50],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 16,
spreadRadius: 2,
)
],
),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Focus(
onFocusChange: (focused) {
setState(() => _isFocused = focused);
},
child: TextField(
controller: widget.controller,
keyboardType: TextInputType.phone,
textDirection: TextDirection.rtl,
onSubmitted: (_) => widget.onSearch(),
style: const TextStyle(fontSize: 15),
decoration: InputDecoration(
hintText: 'بحث برقم الهاتف',
hintStyle: TextStyle(color: Colors.grey.shade400),
prefixIcon: Icon(
Icons.search,
color: _isFocused
? const Color(0xFF0F172A)
: Colors.grey.shade400,
),
suffixIcon: widget.controller.text.isNotEmpty
? InkWell(
onTap: () {
widget.controller.clear();
setState(() {});
},
child: Icon(Icons.close,
color: Colors.grey.shade400, size: 20),
)
: null,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
BorderSide(color: Colors.grey.shade300, width: 1),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
BorderSide(color: Colors.grey.shade300, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: Color(0xFF0F172A),
width: 2,
),
),
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
),
onChanged: (_) => setState(() {}),
),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: widget.onSearch,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0F172A),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Row(
children: [
Icon(Icons.search, size: 18),
SizedBox(width: 6),
Text("بحث"),
],
),
),
const SizedBox(width: 8),
OutlinedButton(
onPressed: widget.onClear,
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF0F172A),
side: const BorderSide(
color: Color(0xFF0F172A),
width: 1.5,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 12),
),
child: const Icon(Icons.refresh, size: 20),
),
],
),
],
),
);
}
}
class _ErrorTile extends StatelessWidget {
final ErrorLog item;
final int index;
const _ErrorTile(this.item, this.index);
@override
Widget build(BuildContext context) {
// تحديد الألوان والأيقونات بناءً على نوع المستخدم
final isDriver = item.userType.toLowerCase().contains('driver') ||
item.userType.toLowerCase().contains('سائق');
final userTypeColor =
isDriver ? const Color(0xFF10B981) : const Color(0xFFF59E0B);
final userTypeIcon = isDriver ? Icons.directions_car : Icons.person;
final userTypeLabel = isDriver ? "سائق" : "راكب";
final userTypeBgColor = isDriver
? const Color(0xFF10B981).withOpacity(0.1)
: const Color(0xFFF59E0B).withOpacity(0.1);
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.white, Colors.grey.shade50],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200, width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 12,
spreadRadius: 1,
)
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Stack(
children: [
// خط علوي ملون
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 4,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFFEF4444),
Colors.red.shade400,
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// الصف الأول: رقم الخطأ ونوع المستخدم
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
decoration: BoxDecoration(
color: userTypeBgColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: userTypeColor, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(userTypeIcon, size: 14, color: userTypeColor),
const SizedBox(width: 4),
Text(
userTypeLabel,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: userTypeColor,
),
),
],
),
),
Text(
'#${item.id}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 12),
// عنوان الخطأ (قابل للنسخ)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.red.shade200,
width: 1,
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.warning_amber_rounded,
size: 18, color: Colors.red.shade600),
const SizedBox(width: 8),
Expanded(
child: SelectableText(
item.error.isEmpty ? '(بدون عنوان)' : item.error,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.red.shade900,
height: 1.4,
),
),
),
],
),
),
const SizedBox(height: 12),
// التفاصيل (إن وجدت)
if (item.details.isNotEmpty) ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.grey.shade200,
width: 1,
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.info_outline,
size: 16, color: Colors.grey.shade600),
const SizedBox(width: 8),
Expanded(
child: SelectableText(
item.details,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
height: 1.4,
),
),
),
],
),
),
const SizedBox(height: 12),
],
// معلومات تقنية
Wrap(
spacing: 6,
runSpacing: 6,
alignment: WrapAlignment.end,
children: [
_buildInfoBadge(
icon: Icons.phone,
label: 'الهاتف',
value: item.phone,
color: Colors.blue,
),
_buildInfoBadge(
icon: Icons.person_outline,
label: 'المعرف',
value: item.userId,
color: Colors.purple,
),
_buildInfoBadge(
icon: Icons.devices,
label: 'Path',
value: item.device,
color: Colors.orange,
),
_buildInfoBadge(
icon: Icons.schedule,
label: 'التاريخ',
value: item.createdAt,
color: Colors.teal,
),
],
),
],
),
),
],
),
),
);
}
Widget _buildInfoBadge({
required IconData icon,
required String label,
required String value,
required Color color,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.08),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.3), width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 13, color: color),
const SizedBox(width: 4),
Flexible(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: "$label: ",
style: TextStyle(
fontSize: 10,
color: color.withOpacity(0.7),
fontWeight: FontWeight.w500,
),
),
TextSpan(
text: value.isEmpty ? '—' : value,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
),
),
],
),
);
}
}
@@ -0,0 +1,278 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/controller/admin/financial_v2_controller.dart';
class FinancialV2Page extends StatelessWidget {
const FinancialV2Page({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(FinancialV2Controller());
return Scaffold(
backgroundColor: AppColor.bg,
appBar: AppBar(
title: const Text('الإدارة المالية المتقدمة',
style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: AppColor.surface,
elevation: 0,
centerTitle: true,
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded),
onPressed: () => controller.fetchAllFinancials(),
)
],
),
body: GetBuilder<FinancialV2Controller>(
builder: (ctrl) {
if (ctrl.isLoading) {
return const Center(
child: CircularProgressIndicator(color: AppColor.accent));
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMainFinancialStats(ctrl.stats),
const SizedBox(height: 24),
_buildSectionTitle('طرق الدفع'),
_buildPaymentMethodBreakdown(ctrl.stats),
const SizedBox(height: 32),
_buildSectionTitle('تسويات الكباتن (مستحقات معلقة)'),
_buildSettlementsList(ctrl.settlements),
const SizedBox(height: 40),
],
),
);
},
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
title,
style: const TextStyle(
color: AppColor.textPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
);
}
Widget _buildMainFinancialStats(Map<String, dynamic> stats) {
return Column(
children: [
_buildFinancialCard(
'إجمالي عمولة المنصة',
'${stats['total_platform_commission'] ?? 0} ج.م',
Icons.account_balance_rounded,
AppColor.accent,
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildFinancialCard(
'إجمالي دخل الكباتن',
'${stats['total_driver_pay'] ?? 0}',
Icons.person_pin_rounded,
AppColor.info,
isSmall: true,
),
),
const SizedBox(width: 12),
Expanded(
child: _buildFinancialCard(
'إجمالي الإيرادات',
'${stats['total_revenue'] ?? 0}',
Icons.payments_rounded,
AppColor.success,
isSmall: true,
),
),
],
),
],
);
}
Widget _buildFinancialCard(
String title, String value, IconData icon, Color color,
{bool isSmall = false}) {
return Container(
padding: EdgeInsets.all(isSmall ? 16 : 24),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.2)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColor.surface, color.withOpacity(0.05)],
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(14),
),
child: Icon(icon, color: color, size: isSmall ? 20 : 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 12)),
const SizedBox(height: 4),
Text(value,
style: TextStyle(
color: AppColor.textPrimary,
fontSize: isSmall ? 18 : 24,
fontWeight: FontWeight.bold)),
],
),
),
],
),
);
}
Widget _buildPaymentMethodBreakdown(Map<String, dynamic> stats) {
double cash = double.tryParse(stats['cash_payments'].toString()) ?? 0;
double digital = double.tryParse(stats['digital_payments'].toString()) ?? 0;
double total = cash + digital;
if (total == 0) total = 1;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
_buildPaymentBar('نقدي (Cash)', cash, total, AppColor.warning),
const SizedBox(height: 16),
_buildPaymentBar('إلكتروني / محفظة', digital, total, AppColor.info),
],
),
);
}
Widget _buildPaymentBar(
String label, double value, double total, Color color) {
double percent = value / total;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label,
style:
const TextStyle(color: AppColor.textPrimary, fontSize: 13)),
Text('${value.toStringAsFixed(0)} ج.م',
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: percent,
backgroundColor: AppColor.divider,
color: color,
minHeight: 8,
),
),
],
);
}
Widget _buildSettlementsList(List<dynamic> settlements) {
if (settlements.isEmpty)
return const Center(child: Text('لا توجد تسويات معلقة'));
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: settlements.length,
itemBuilder: (ctx, i) {
final s = settlements[i];
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColor.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColor.divider),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${s['first_name']} ${s['last_name']}',
style: const TextStyle(
color: AppColor.textPrimary,
fontWeight: FontWeight.bold)),
Text(s['phone'] ?? '',
style: const TextStyle(
color: AppColor.textSecondary, fontSize: 12)),
const SizedBox(height: 4),
Text('${s['total_rides']} رحلة مكتملة',
style: const TextStyle(
color: AppColor.info, fontSize: 11)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Text('المستحقات',
style: TextStyle(
color: AppColor.textSecondary, fontSize: 10)),
Text('${s['total_earned']} ج.م',
style: const TextStyle(
color: AppColor.accent,
fontSize: 18,
fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.accent.withOpacity(0.1),
foregroundColor: AppColor.accent,
elevation: 0,
minimumSize: const Size(80, 32),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
),
child: const Text('تسوية',
style: TextStyle(
fontSize: 11, fontWeight: FontWeight.bold)),
),
],
),
],
),
);
},
);
}
}
+603
View File
@@ -0,0 +1,603 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'dart:convert';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/views/widgets/my_textField.dart';
import '../../print.dart';
// ══════════════════════════════════════════════════════════════
// DESIGN TOKENS (same as AdminHomePage)
// ══════════════════════════════════════════════════════════════
const Color _bg = Color(0xFF0D1117);
const Color _surface = Color(0xFF161B22);
const Color _surfaceElevated = Color(0xFF1C2333);
const Color _accent = Color(0xFF00D4AA);
const Color _danger = Color(0xFFFF5370);
const Color _warning = Color(0xFFFFCB6B);
const Color _info = Color(0xFF82AAFF);
const Color _textPrimary = Color(0xFFE6EDF3);
const Color _textSecondary = Color(0xFF7D8590);
const Color _divider = Color(0xFF21262D);
class PackageUpdateScreen extends StatelessWidget {
PackageUpdateScreen({super.key});
final PackageController packageController = Get.put(PackageController());
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _bg,
appBar: _buildAppBar(),
body: GetBuilder<PackageController>(
builder: (controller) {
if (controller.isLoading.value) {
return const Center(
child: CircularProgressIndicator(color: _accent, strokeWidth: 2),
);
}
if (controller.packages.isEmpty) {
return _buildEmptyState();
}
return ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 40),
itemCount: controller.packages.length,
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final package = controller.packages[index];
return _buildPackageCard(context, package, controller);
},
);
},
),
);
}
// ─────────────────────────── APP BAR ───────────────────────────
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: _bg,
elevation: 0,
surfaceTintColor: Colors.transparent,
leading: GestureDetector(
onTap: () => Get.back(),
child: Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: _surface,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: _divider),
),
child: const Icon(Icons.arrow_back_ios_new_rounded,
color: _textSecondary, size: 16),
),
),
title: Row(
children: [
Container(
padding: const EdgeInsets.all(7),
decoration: BoxDecoration(
color: _accent.withOpacity(0.12),
borderRadius: BorderRadius.circular(9),
border: Border.all(color: _accent.withOpacity(0.25)),
),
child: const Icon(Icons.system_update_rounded,
color: _accent, size: 16),
),
const SizedBox(width: 10),
const Text(
'تحديث التطبيق',
style: TextStyle(
color: _textPrimary,
fontSize: 17,
fontWeight: FontWeight.w600,
),
),
],
),
actions: [
GestureDetector(
onTap: () => packageController.fetchPackages(),
child: Container(
margin: const EdgeInsets.only(right: 16),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _surface,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: _divider),
),
child: const Icon(Icons.refresh_rounded,
color: _textSecondary, size: 18),
),
),
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1),
child: Container(height: 1, color: _divider),
),
);
}
// ─────────────────────────── PACKAGE CARD ───────────────────────────
Widget _buildPackageCard(
BuildContext context, dynamic package, PackageController controller) {
final platform = package['platform']?.toString() ?? '';
final isAndroid = platform.toLowerCase().contains('android');
final isIOS = platform.toLowerCase().contains('ios');
final Color platformColor = isAndroid
? const Color(0xFF4CAF50)
: isIOS
? _info
: _warning;
final IconData platformIcon = isAndroid
? Icons.android_rounded
: isIOS
? Icons.apple_rounded
: Icons.devices_rounded;
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _showUpdateDialog(context, package, controller),
borderRadius: BorderRadius.circular(16),
splashColor: _accent.withOpacity(0.06),
highlightColor: Colors.transparent,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _divider),
),
child: Row(
children: [
// Platform Icon
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
platformColor.withOpacity(0.20),
platformColor.withOpacity(0.06),
],
),
borderRadius: BorderRadius.circular(13),
border: Border.all(color: platformColor.withOpacity(0.25)),
),
child: Icon(platformIcon, color: platformColor, size: 22),
),
const SizedBox(width: 14),
// Info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
package['appName']?.toString() ?? '—',
style: const TextStyle(
color: _textPrimary,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Row(
children: [
_buildTag(platform, platformColor),
const SizedBox(width: 6),
_buildVersionBadge(
package['version']?.toString() ?? '?'),
],
),
],
),
),
// Update button
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
decoration: BoxDecoration(
color: _accent.withOpacity(0.10),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: _accent.withOpacity(0.25)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.edit_rounded, color: _accent, size: 13),
SizedBox(width: 5),
Text(
'تعديل',
style: TextStyle(
color: _accent,
fontSize: 11,
fontWeight: FontWeight.w600,
),
),
],
),
),
],
),
),
),
);
}
Widget _buildTag(String label, Color color) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: color.withOpacity(0.10),
borderRadius: BorderRadius.circular(6),
),
child: Text(
label,
style: TextStyle(
color: color,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),
);
}
Widget _buildVersionBadge(String version) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: _divider,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'v$version',
style: const TextStyle(
color: _textSecondary,
fontSize: 10,
fontWeight: FontWeight.w500,
fontFamily: 'monospace',
),
),
);
}
// ─────────────────────────── EMPTY STATE ───────────────────────────
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: _surface,
shape: BoxShape.circle,
border: Border.all(color: _divider),
),
child: const Icon(Icons.inventory_2_outlined,
color: _textSecondary, size: 32),
),
const SizedBox(height: 16),
const Text('لا توجد حزم متاحة',
style: TextStyle(
color: _textPrimary,
fontSize: 15,
fontWeight: FontWeight.w600)),
const SizedBox(height: 6),
const Text('اسحب للأسفل لإعادة التحميل',
style: TextStyle(color: _textSecondary, fontSize: 12)),
],
),
);
}
// ─────────────────────────── UPDATE DIALOG ───────────────────────────
void _showUpdateDialog(
BuildContext context, dynamic package, PackageController controller) {
controller.versionController.clear();
Get.dialog(
Dialog(
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: _surfaceElevated,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: _divider),
boxShadow: const [
BoxShadow(
color: Colors.black54,
blurRadius: 30,
offset: Offset(0, 12),
),
],
),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: _accent.withOpacity(0.12),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _accent.withOpacity(0.25)),
),
child: const Icon(Icons.system_update_rounded,
color: _accent, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'تحديث الإصدار',
style: TextStyle(
color: _textPrimary,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
Text(
package['appName']?.toString() ?? '',
style: const TextStyle(
color: _textSecondary, fontSize: 11),
),
],
),
),
],
),
const SizedBox(height: 20),
Container(height: 1, color: _divider),
const SizedBox(height: 20),
// Current info
Row(
children: [
_buildInfoChip(Icons.devices_rounded,
package['platform']?.toString() ?? '', _info),
const SizedBox(width: 8),
_buildInfoChip(Icons.tag_rounded,
'الحالي: ${package['version']}', _warning),
],
),
const SizedBox(height: 18),
// Input label
const Text(
'الإصدار الجديد',
style: TextStyle(
color: _textSecondary,
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 0.8,
),
),
const SizedBox(height: 8),
// Text input
Container(
decoration: BoxDecoration(
color: _bg,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _divider),
),
child: TextField(
controller: controller.versionController,
keyboardType:
const TextInputType.numberWithOptions(decimal: true),
style: const TextStyle(
color: _textPrimary,
fontSize: 15,
fontFamily: 'monospace',
fontWeight: FontWeight.w600,
),
decoration: InputDecoration(
hintText: package['version'].toString(),
hintStyle: const TextStyle(
color: _textSecondary,
fontFamily: 'monospace',
),
prefixIcon:
const Icon(Icons.tag_rounded, color: _accent, size: 18),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 14),
),
),
),
const SizedBox(height: 24),
// Actions
Row(
children: [
Expanded(
child: TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: _divider),
),
),
child: const Text(
'إلغاء',
style: TextStyle(color: _textSecondary, fontSize: 13),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Obx(() => ElevatedButton.icon(
icon: controller.isLoading.value
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Icon(Icons.check_rounded, size: 16),
label: Text(
controller.isLoading.value ? 'جاري...' : 'تحديث',
style: const TextStyle(fontSize: 13),
),
style: ElevatedButton.styleFrom(
backgroundColor: _accent,
foregroundColor: _bg,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
onPressed: controller.isLoading.value
? null
: () async {
await controller.updatePackages(
package['id'].toString(),
controller.versionController.text,
);
},
)),
),
],
),
],
),
),
),
);
}
Widget _buildInfoChip(IconData icon, String label, Color color) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: color.withOpacity(0.08),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withOpacity(0.2)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: color, size: 13),
const SizedBox(width: 5),
Text(
label,
style: TextStyle(
color: color,
fontSize: 11,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}
// ══════════════════════════════════════════════════════════════
// CONTROLLER
// ══════════════════════════════════════════════════════════════
class PackageController extends GetxController {
List packages = [];
var isLoading = false.obs;
final versionController = TextEditingController();
final formKey = GlobalKey<FormState>();
@override
void onInit() {
super.onInit();
fetchPackages();
}
fetchPackages() async {
isLoading.value = true;
var response = await CRUD().get(link: AppLink.getPackages, payload: {});
if (response is String && (response == 'failure' || response == 'token_expired')) {
isLoading.value = false;
return;
}
try {
var jsonData = response is String ? jsonDecode(response) : response;
packages = jsonData['message'] ?? [];
Log.print('✅ Decoded packages: ${packages.length} items');
update();
} catch (e) {
Log.print('❌ Error parsing packages: $e');
}
isLoading.value = false;
}
updatePackages(String id, String version) async {
if (version.trim().isEmpty) {
Get.snackbar(
'تنبيه',
'يرجى إدخال رقم الإصدار',
backgroundColor: _warning.withOpacity(0.15),
colorText: _textPrimary,
borderRadius: 12,
margin: const EdgeInsets.all(16),
icon: const Icon(Icons.warning_rounded, color: _warning),
);
return;
}
isLoading.value = true;
var response = await CRUD().post(
link: AppLink.updatePackages,
payload: {"id": id, "version": version},
);
Log.print('response: $response');
isLoading.value = false;
if (response != 'failure') {
Get.back();
Get.snackbar(
'تم التحديث',
'تم تحديث الإصدار بنجاح',
backgroundColor: _accent.withOpacity(0.15),
colorText: _textPrimary,
borderRadius: 12,
margin: const EdgeInsets.all(16),
icon: const Icon(Icons.check_circle_rounded, color: _accent),
);
fetchPackages();
} else {
Get.snackbar(
'خطأ',
'فشل التحديث، يرجى المحاولة مجدداً',
backgroundColor: _danger.withOpacity(0.15),
colorText: _textPrimary,
borderRadius: 12,
margin: const EdgeInsets.all(16),
icon: const Icon(Icons.error_rounded, color: _danger),
);
}
}
}
@@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/admin/passenger_admin_controller.dart';
import '../../widgets/elevated_btn.dart';
import 'passenger_details_page.dart';
GetBuilder<PassengerAdminController> formSearchPassengers() {
// DbSql sql = DbSql.instance;
return GetBuilder<PassengerAdminController>(
builder: (controller) => Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Container(
decoration:
const BoxDecoration(color: AppColor.secondaryColor),
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(
borderRadius: BorderRadius.only(),
gapPadding: 4,
borderSide: BorderSide(
color: AppColor.redColor,
width: 2,
)),
suffixIcon: InkWell(
onTap: () async {
if (controller.passengerController.text.length >
4) {
await controller.getPassengers();
Get.defaultDialog(
title: controller.passengers['message'][0]
['email'],
titleStyle: AppStyle.title,
content: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Name is ${controller.passengers['message'][0]['first_name']} ${controller.passengers['message'][0]['last_name']}',
style: AppStyle.title,
),
Text(
'phone is ${controller.passengers['message'][0]['phone']}',
style: AppStyle.title,
),
],
),
confirm: MyElevatedButton(
title: 'Go To Details'.tr,
onPressed: () {
Get.to(
() => const PassengerDetailsPage(),
arguments: {
'data': controller
.passengers['message'][0],
});
}));
}
},
child: const Icon(Icons.search)),
hintText: 'Search for Passenger'.tr,
hintStyle: AppStyle.title,
hintMaxLines: 1,
prefixIcon: IconButton(
onPressed: () async {
controller.passengerController.clear();
controller.clearPlaces();
},
icon: Icon(
Icons.clear,
color: Colors.red[300],
),
),
),
controller: controller.passengerController,
),
),
)
],
));
}

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