feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل

قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة
ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ.

الخريطة:
  backend · payment_server · loction_server · ride_server ·
  passenger_server · docker · dashboard · stress_test  → الجذر
  siro_rider  → apps/rider          siro_driver  → apps/driver
  siro_admin  → dashboards/admin    siro_service → dashboards/service
  android_bot → apps/android_bot    socialBot    → apps/socialBot

نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب)
لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً:
كل ما يلي يصير فرقاً مقروءاً مقابل المصدر.

لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز،
سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh
(ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في
مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و
dashboards/transit-web).

⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة:
1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر):
   كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner.
2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist)
   يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً.
3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع →
   يجب ضمّ الحزم داخله أسوة بـ apps/rider.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:14:13 +03:00
co-authored by Claude Opus 5
parent 9909d9b4c1
commit 4d8414c96b
3198 changed files with 766859 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);
}
}
@@ -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);
}
@@ -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';
}
@@ -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"
};
+42
View File
@@ -0,0 +1,42 @@
import 'package:flutter/material.dart';
class AppColor {
AppColor._();
// ─── Dark Mode Palette (static fallback / design tokens) ─────────────────
static const Color bg = Color(0xFF0A0A0B);
static const Color surface = Color(0xFF161618);
static const Color surfaceElevated = Color(0xFF222225);
static const Color surfaceGlass = Color(0xCC161618);
static const Color accent = Color(0xFF6366F1);
static const Color accentSoft = Color(0x266366F1);
static const Color accentBorder = Color(0x4D6366F1);
static const Color glow = Color(0xFF818CF8);
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 warningSoft = Color(0x26F59E0B);
static const Color info = Color(0xFF3B82F6);
static const Color infoSoft = Color(0x263B82F6);
static const Color textPrimary = Color(0xFFF3F4F6);
static const Color textSecondary = Color(0xFF9CA3AF);
static const Color textMuted = Color(0xFF6B7280);
static const Color divider = Color(0xFF2D2D30);
static const Color cardShadow = Color(0x66000000);
// ─── Legacy Mappings ─────────────────────────────────────────────────────
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;
}
@@ -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
+477
View File
@@ -0,0 +1,477 @@
import '../env/env.dart';
import '../main.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 String get currentCountry => box.read('countryCode') ?? 'Jordan';
static String get server {
switch (currentCountry) {
case 'Syria':
return 'https://api-syria.siromove.com/siro_v3';
case 'Egypt':
return 'https://api-egypt.siromove.com/siro_v3';
case 'Jordan':
return 'https://jordan-siro.intaleqapp.com/backend';
default:
return 'https://api.siromove.com/siro_v3';
}
}
static String get endPoint => server;
static String get syria => 'https://api-syria.siromove.com/siro';
static String get paymentServer {
switch (currentCountry) {
case 'Syria':
return 'https://wallet-syria.siromove.com/v1/main';
case 'Egypt':
return 'https://wallet-egypt.siromove.com/v1/main';
case 'Jordan':
// TODO: Change to new subdomain when fully migrated
return 'https://walletintaleq.intaleq.xyz/v2/main';
default:
return 'https://wallet.siromove.com/v1/main';
}
}
static String get locationServer {
switch (currentCountry) {
case 'Syria':
return 'https://location-syria.siromove.com/siro/ride/location';
case 'Egypt':
return 'https://location-egypt.siromove.com/siro/ride/location';
case 'Jordan':
return 'https://jordan-siro.intaleqapp.com/loction_server/siro/ride/location';
default:
return 'https://location.siromove.com/siro/ride/location';
}
}
static String get locationServerSide => locationServer;
static String get mapSaasRoute {
switch (currentCountry) {
case 'Syria':
return 'https://map-syria.siromove.com/api/maps/route';
case 'Egypt':
return 'https://map-egypt.siromove.com/api/maps/route';
case 'Jordan':
// TODO: Change to new subdomain when fully migrated
return 'https://map-saas.intaleqapp.com/api/maps/route';
default:
return 'https://map-saas.intaleqapp.com/api/maps/route';
}
}
static String get mapSaasPlaces {
switch (currentCountry) {
case 'Syria':
return 'https://map-syria.siromove.com/api/geocoding/places';
case 'Egypt':
return 'https://map-egypt.siromove.com/api/geocoding/places';
case 'Jordan':
// TODO: Change to new subdomain when fully migrated
return 'https://map-saas.intaleqapp.com/api/geocoding/places';
default:
return 'https://map-saas.intaleqapp.com/api/geocoding/places';
}
}
static String get routeApiBaseUrl {
switch (currentCountry) {
case 'Syria':
return 'https://routes-syria.siromove.com/route/v1/driving';
case 'Egypt':
return 'https://routes-egypt.siromove.com/route/v1/driving';
case 'Jordan':
// TODO: Change to new subdomain when fully migrated
return 'https://routesjo.intaleq.xyz';
default:
return 'https://routes.siromove.com/route/v1/driving';
}
}
static String loginJwtDriver = "$server/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 = "$server/Admin/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/driver/drivers_pending_list.php";
static String getDriverDetails =
"$server/auth/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";
// Marketing & Price Anomalies Endpoints
static String getMarketAnomalies =
"$server/Admin/marketing/get_market_anomalies.php";
static String triggerCampaign =
"$server/Admin/marketing/trigger_campaign.php";
static String getCampaignsLog =
"$server/Admin/marketing/get_campaigns_log.php";
static String getTelemetry = "$server/Admin/marketing/get_telemetry.php";
static String getPriceComparison =
"$server/Admin/marketing/get_price_comparison.php";
static String whatIfSimulator =
"$server/Admin/marketing/what_if_simulator.php";
static String getPriceGapHeatmap =
"$server/Admin/marketing/get_price_gap_heatmap.php";
static String winbackHotspotTargets =
"$server/Admin/marketing/winback_hotspot_targets.php";
static String getMarketShareAnalytics =
"$server/Admin/marketing/get_market_share_analytics.php";
static String aiPricePrediction =
"$server/Admin/marketing/ai_price_prediction.php";
static String saveDriverDestination =
"$server/ride/location/save_driver_destination.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 analyticsDashboardData =
"$server/Admin/v2/analytics/dashboard_data.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 updatePhoneInvalidSMSPassenger =
"$server/auth/sms/updatePhoneInvalidSMSPassenger.php";
static String detectCountryFromPhone(String phone) {
final clean = phone.replaceAll(RegExp(r'[^0-9]'), '');
if (clean.startsWith('962')) return 'Jordan';
if (clean.startsWith('963')) return 'Syria';
if (clean.startsWith('20')) return 'Egypt';
if (clean.startsWith('07') || clean.startsWith('7')) return 'Jordan';
if (clean.startsWith('09') || clean.startsWith('9')) return 'Syria';
if (clean.startsWith('01') || clean.startsWith('1')) return 'Egypt';
return '';
}
}
+173
View File
@@ -0,0 +1,173 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'colors.dart';
class AppStyle {
AppStyle._();
// ─── Static typography (dark mode defaults, backward compatible) ───────────
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,
);
// ─── Context-aware typography (adapts to theme) ───────────────────────────
static TextStyle displayFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w800,
fontSize: 32,
color: Theme.of(context).colorScheme.onSurface,
letterSpacing: -1,
);
static TextStyle headTitleFor(BuildContext context) => GoogleFonts.cairo(
fontWeight: FontWeight.bold,
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface,
);
static TextStyle titleFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
);
static TextStyle subtitleFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w500,
fontSize: 14,
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
static TextStyle bodyFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Theme.of(context).colorScheme.onSurface,
);
static TextStyle captionFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w400,
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
);
static TextStyle numberFor(BuildContext context) => GoogleFonts.jetBrainsMono(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Theme.of(context).colorScheme.primary,
);
// ─── Context-aware decorations ────────────────────────────────────────────
static BoxDecoration cardDecorationFor(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline, width: 1),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: context.isDark ? 0.4 : 0.05),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
);
}
static BoxDecoration elevatedCardFor(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: cs.primary.withValues(alpha: context.isDark ? 0.3 : 0.2),
width: 1,
),
);
}
static BoxDecoration glassDecorationFor(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return BoxDecoration(
color: cs.surface.withValues(alpha: context.isDark ? 0.8 : 0.9),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: cs.outline, width: 1),
);
}
// ─── Legacy static 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;
}
extension _Brightness on BuildContext {
bool get isDark => Theme.of(this).brightness == Brightness.dark;
}
@@ -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";
}
+608
View File
@@ -0,0 +1,608 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'colors.dart';
class AppTheme {
AppTheme._();
// ─── Dark Theme (Premium SaaS) ─────────────────────────────────────────────
static ThemeData get darkTheme {
final darkScheme = ColorScheme.dark(
brightness: Brightness.dark,
primary: AppColor.accent,
onPrimary: Colors.white,
primaryContainer: AppColor.accentSoft,
onPrimaryContainer: AppColor.accent,
secondary: AppColor.glow,
onSecondary: Colors.white,
secondaryContainer: const Color(0x26818CF8),
onSecondaryContainer: AppColor.glow,
tertiary: AppColor.info,
onTertiary: Colors.white,
surface: AppColor.surface,
onSurface: AppColor.textPrimary,
onSurfaceVariant: AppColor.textSecondary,
error: AppColor.danger,
onError: Colors.white,
errorContainer: AppColor.dangerSoft,
onErrorContainer: AppColor.danger,
outline: AppColor.divider,
outlineVariant: const Color(0xFF3D3D42),
shadow: Colors.black,
scrim: Colors.black54,
inverseSurface: AppColor.textPrimary,
onInverseSurface: AppColor.bg,
inversePrimary: AppColor.glow,
);
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
fontFamily: GoogleFonts.tajawal().fontFamily,
scaffoldBackgroundColor: AppColor.bg,
colorScheme: darkScheme,
dividerColor: AppColor.divider,
dividerTheme: const DividerThemeData(
color: AppColor.divider,
thickness: 1,
space: 1,
),
appBarTheme: AppBarTheme(
backgroundColor: AppColor.bg,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
titleTextStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
iconTheme: const IconThemeData(color: AppColor.textPrimary),
),
cardTheme: CardThemeData(
color: AppColor.surface,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(color: AppColor.divider, width: 1),
),
),
dialogTheme: DialogThemeData(
backgroundColor: AppColor.surface,
elevation: 24,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: AppColor.divider, width: 1),
),
titleTextStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
contentTextStyle: GoogleFonts.tajawal(
color: AppColor.textSecondary,
fontSize: 14,
),
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: AppColor.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColor.surfaceElevated,
contentTextStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 14,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
behavior: SnackBarBehavior.floating,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColor.surfaceElevated,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColor.accent, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColor.danger, width: 1),
),
labelStyle: const TextStyle(color: AppColor.textSecondary),
hintStyle: const TextStyle(color: AppColor.textMuted),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.accent,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.textPrimary,
side: const BorderSide(color: AppColor.divider),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColor.accent,
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return AppColor.success;
return AppColor.textMuted;
}),
trackColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return AppColor.successSoft;
return AppColor.surfaceElevated;
}),
),
popupMenuTheme: PopupMenuThemeData(
color: AppColor.surface,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: AppColor.divider),
),
),
tooltipTheme: TooltipThemeData(
decoration: BoxDecoration(
color: AppColor.surfaceElevated,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.divider),
),
textStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 12,
),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColor.accent,
foregroundColor: Colors.white,
elevation: 4,
),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: AppColor.accent,
linearTrackColor: AppColor.surfaceElevated,
),
tabBarTheme: TabBarThemeData(
labelColor: AppColor.accent,
unselectedLabelColor: AppColor.textSecondary,
indicatorColor: AppColor.accent,
dividerColor: AppColor.divider,
),
chipTheme: ChipThemeData(
backgroundColor: AppColor.surfaceElevated,
labelStyle: GoogleFonts.tajawal(color: AppColor.textPrimary, fontSize: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(color: AppColor.divider),
),
),
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.windows: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.macOS: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.linux: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.fuchsia: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.android: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: const FadeUpwardsPageTransitionsBuilder(),
},
),
);
}
// ─── Light Theme (Premium SaaS) ────────────────────────────────────────────
static ThemeData get lightTheme {
const bgLight = Color(0xFFF8F9FC);
const surfaceLight = Color(0xFFFFFFFF);
const surfaceElevatedLight = Color(0xFFF1F3F9);
const dividerLight = Color(0xFFE2E5ED);
const textPrimaryLight = Color(0xFF111827);
const textSecondaryLight = Color(0xFF6B7280);
const textMutedLight = Color(0xFF9CA3AF);
const accentLight = Color(0xFF6366F1);
const accentSoftLight = Color(0x1A6366F1);
const glowLight = Color(0xFF4F46E5);
const dangerLight = Color(0xFFDC2626);
const dangerSoftLight = Color(0x1ADC2626);
const successLight = Color(0xFF059669);
const successSoftLight = Color(0x1A059669);
const infoLight = Color(0xFF2563EB);
final lightScheme = ColorScheme.light(
brightness: Brightness.light,
primary: accentLight,
onPrimary: Colors.white,
primaryContainer: accentSoftLight,
onPrimaryContainer: accentLight,
secondary: glowLight,
onSecondary: Colors.white,
secondaryContainer: const Color(0x1A4F46E5),
onSecondaryContainer: glowLight,
tertiary: infoLight,
onTertiary: Colors.white,
surface: surfaceLight,
onSurface: textPrimaryLight,
onSurfaceVariant: textSecondaryLight,
error: dangerLight,
onError: Colors.white,
errorContainer: dangerSoftLight,
onErrorContainer: dangerLight,
outline: dividerLight,
outlineVariant: const Color(0xFFD1D5DB),
shadow: Colors.black26,
scrim: Colors.black38,
inverseSurface: textPrimaryLight,
onInverseSurface: surfaceLight,
inversePrimary: const Color(0xFFC7D2FE),
);
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
fontFamily: GoogleFonts.tajawal().fontFamily,
scaffoldBackgroundColor: bgLight,
colorScheme: lightScheme,
dividerColor: dividerLight,
dividerTheme: const DividerThemeData(
color: dividerLight,
thickness: 1,
space: 1,
),
appBarTheme: AppBarTheme(
backgroundColor: bgLight,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
titleTextStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 18,
fontWeight: FontWeight.bold,
),
iconTheme: const IconThemeData(color: textPrimaryLight),
),
cardTheme: CardThemeData(
color: surfaceLight,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(color: dividerLight, width: 1),
),
),
dialogTheme: DialogThemeData(
backgroundColor: surfaceLight,
elevation: 24,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: dividerLight, width: 1),
),
titleTextStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 18,
fontWeight: FontWeight.bold,
),
contentTextStyle: GoogleFonts.tajawal(
color: textSecondaryLight,
fontSize: 14,
),
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: surfaceLight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: surfaceElevatedLight,
contentTextStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 14,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
behavior: SnackBarBehavior.floating,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: surfaceElevatedLight,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: accentLight, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: dangerLight, width: 1),
),
labelStyle: const TextStyle(color: textSecondaryLight),
hintStyle: const TextStyle(color: textMutedLight),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: accentLight,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: textPrimaryLight,
side: const BorderSide(color: dividerLight),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: accentLight,
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return successLight;
return textMutedLight;
}),
trackColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return successSoftLight;
return surfaceElevatedLight;
}),
),
popupMenuTheme: PopupMenuThemeData(
color: surfaceLight,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: dividerLight),
),
),
tooltipTheme: TooltipThemeData(
decoration: BoxDecoration(
color: surfaceElevatedLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: dividerLight),
),
textStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 12,
),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: accentLight,
foregroundColor: Colors.white,
elevation: 4,
),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: accentLight,
linearTrackColor: surfaceElevatedLight,
),
tabBarTheme: TabBarThemeData(
labelColor: accentLight,
unselectedLabelColor: textSecondaryLight,
indicatorColor: accentLight,
dividerColor: dividerLight,
),
chipTheme: ChipThemeData(
backgroundColor: surfaceElevatedLight,
labelStyle: GoogleFonts.tajawal(color: textPrimaryLight, fontSize: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(color: dividerLight),
),
),
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.windows: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.macOS: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.linux: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.fuchsia: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.android: const FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: const FadeUpwardsPageTransitionsBuilder(),
},
),
);
}
}
// ─── Context Extensions for Theme Access ──────────────────────────────────────
extension ThemeExtensions on BuildContext {
ColorScheme get colors => Theme.of(this).colorScheme;
TextTheme get textStyles => Theme.of(this).textTheme;
Brightness get brightness => Theme.of(this).brightness;
bool get isDark => brightness == Brightness.dark;
}
// ─── Semantic Color Getters (light/dark adaptive) ─────────────────────────────
// These replace direct AppColor.* usage in views. They automatically adapt
// based on the current theme brightness.
class AppColors {
AppColors._();
static Color bg(BuildContext context) => context.isDark
? const Color(0xFF0A0A0B)
: const Color(0xFFF8F9FC);
static Color surface(BuildContext context) => context.isDark
? const Color(0xFF161618)
: const Color(0xFFFFFFFF);
static Color surfaceElevated(BuildContext context) => context.isDark
? const Color(0xFF222225)
: const Color(0xFFF1F3F9);
static Color surfaceGlass(BuildContext context) => context.isDark
? const Color(0xCC161618)
: const Color(0xCCFFFFFF);
static Color accent(BuildContext context) => context.isDark
? const Color(0xFF6366F1)
: const Color(0xFF6366F1);
static Color accentSoft(BuildContext context) => context.isDark
? const Color(0x266366F1)
: const Color(0x1A6366F1);
static Color accentBorder(BuildContext context) => context.isDark
? const Color(0x4D6366F1)
: const Color(0x336366F1);
static Color glow(BuildContext context) => context.isDark
? const Color(0xFF818CF8)
: const Color(0xFF4F46E5);
static Color danger(BuildContext context) => context.isDark
? const Color(0xFFEF4444)
: const Color(0xFFDC2626);
static Color dangerSoft(BuildContext context) => context.isDark
? const Color(0x26EF4444)
: const Color(0x1ADC2626);
static Color success(BuildContext context) => context.isDark
? const Color(0xFF10B981)
: const Color(0xFF059669);
static Color successSoft(BuildContext context) => context.isDark
? const Color(0x2610B981)
: const Color(0x1A059669);
static Color warning(BuildContext context) => context.isDark
? const Color(0xFFF59E0B)
: const Color(0xFFD97706);
static Color warningSoft(BuildContext context) => context.isDark
? const Color(0x26F59E0B)
: const Color(0x1AD97706);
static Color info(BuildContext context) => context.isDark
? const Color(0xFF3B82F6)
: const Color(0xFF2563EB);
static Color infoSoft(BuildContext context) => context.isDark
? const Color(0x263B82F6)
: const Color(0x1A2563EB);
static Color textPrimary(BuildContext context) => context.isDark
? const Color(0xFFF3F4F6)
: const Color(0xFF111827);
static Color textSecondary(BuildContext context) => context.isDark
? const Color(0xFF9CA3AF)
: const Color(0xFF6B7280);
static Color textMuted(BuildContext context) => context.isDark
? const Color(0xFF6B7280)
: const Color(0xFF9CA3AF);
static Color divider(BuildContext context) => context.isDark
? const Color(0xFF2D2D30)
: const Color(0xFFE2E5ED);
static Color cardShadow(BuildContext context) => context.isDark
? const Color(0x66000000)
: const Color(0x0D000000);
// Gradient helpers for charts
static LinearGradient accentGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
accent(context).withValues(alpha: 0.4),
accent(context).withValues(alpha: 0.0),
],
);
static LinearGradient successGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
success(context).withValues(alpha: 0.4),
success(context).withValues(alpha: 0.0),
],
);
static LinearGradient dangerGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
danger(context).withValues(alpha: 0.4),
danger(context).withValues(alpha: 0.0),
],
);
static LinearGradient infoGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
info(context).withValues(alpha: 0.4),
info(context).withValues(alpha: 0.0),
],
);
}
@@ -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,139 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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 = {};
mySnackbarError('No captain found with this phone number');
}
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();
mySnackbarSuccess('Prize Added');
} else {
// Authentication failed, handle accordingly
mySnackbarError('Authentication failed');
// 'Authentication failed');
}
} else {
// Local authentication not available, proceed with payment without authentication
await addCaptainPrizeToWallet();
mySnackbarSuccess('Prize Added');
}
} 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,71 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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) {
mySnackbarError("فشل جلب الشكاوى: $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) {
mySnackbarError("فشل تحديث الشكوى: $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,98 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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) {
mySnackbarError("فشل جلب السائقين: $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) {
mySnackbarError("فشل جلب تفاصيل السائق: $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) {
mySnackbarError("فشل اعتماد السائق: $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,90 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class KazanController extends GetxController {
var kazanData = {}.obs;
var isLoading = false.obs;
var selectedCountry = 'سوريا'.obs;
final CRUD _crud = CRUD();
final List<Map<String, String>> countries = [
{'code': 'Syria', 'name': 'سوريا', 'flag': '🇸🇾'},
{'code': 'Jordan', 'name': 'الأردن', 'flag': '🇯🇴'},
{'code': 'Egypt', 'name': 'مصر', 'flag': '🇪🇬'},
];
String get selectedCountryCode =>
countries.firstWhere(
(c) => c['name'] == selectedCountry.value,
orElse: () => countries.first,
)['code']!;
@override
void onInit() {
super.onInit();
getKazan();
}
void setCountry(String countryName) {
selectedCountry.value = countryName;
getKazan();
}
Future<void> getKazan() async {
isLoading.value = true;
try {
final countryParam = selectedCountryCode;
var response = await _crud.get(link: "${AppLink.getKazanPercent}?country=$countryParam");
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) {
final match = message.cast<Map<String, dynamic>>().firstWhere(
(row) =>
(row['country'] ?? '').toString().toLowerCase() ==
countryParam.toLowerCase(),
orElse: () => message[0] as Map<String, dynamic>,
);
kazanData.value = Map<String, dynamic>.from(match);
kazanData['country'] = selectedCountry.value;
} else {
kazanData.value = {'country': selectedCountry.value};
}
}
}
} catch (e) {
mySnackbarError('فشل جلب بيانات التسعير: $e');
} finally {
isLoading.value = false;
}
}
Future<bool> updateKazan(Map<String, dynamic> data) async {
isLoading.value = true;
try {
data['country'] = selectedCountryCode;
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) {
mySnackbarError('فشل تحديث التسعير: $e');
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -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 LiveAnalyticsController extends GetxController {
bool isLoading = true;
String selectedDate = '';
Map<String, dynamic> allData = {};
Map<String, dynamic> get realtime => _section('realtime');
Map<String, dynamic> get gap => _section('gap');
Map<String, dynamic> get heatmap => _section('heatmap');
Map<String, dynamic> get pricing => _section('pricing');
Map<String, dynamic> get revenue => _section('revenue');
Map<String, dynamic> get growth => _section('growth');
Map<String, dynamic> get market => _section('market');
Map<String, dynamic> get complaints => _section('complaints');
Map<String, dynamic> get funnel => _section('funnel');
Map<String, dynamic> get hourly => _section('hourly');
Map<String, dynamic> get weekly => _section('weekly');
Map<String, dynamic> get zones => _section('zones');
Map<String, dynamic> get retention => _section('retention');
Map<String, dynamic> get competitor => _section('competitor');
List<String> availableDates = [];
Map<String, dynamic> _section(String key) {
final d = allData[key];
if (d is Map<String, dynamic>) return d;
return {};
}
@override
void onInit() {
super.onInit();
final now = DateTime.now();
selectedDate =
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
fetchAll();
}
Future<void> fetchAll() async {
isLoading = true;
update();
try {
var res = await CRUD().get(
link: AppLink.analyticsDashboardData,
payload: {'date': selectedDate, 'section': 'all'},
);
if (res != 'failure' && res != null) {
var d = res is String ? jsonDecode(res) : res;
if (d['status'] == 'success' && d['data'] != null) {
allData = Map<String, dynamic>.from(d['data']);
availableDates =
List<String>.from(d['available_dates'] ?? []);
}
}
} catch (e) {
Log.print('Error fetching live analytics: $e');
}
isLoading = false;
update();
}
void changeDate(String date) {
selectedDate = date;
fetchAll();
}
}
@@ -0,0 +1,336 @@
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class MarketingController extends GetxController {
bool isLoading = false;
List priceAnomalies = [];
List campaignsLog = [];
// --- Dynamic Dashboard Configurations ---
String selectedCountry = 'All'; // 'All', 'SY', 'JO', 'EG', 'IQ'
bool isAutopilotEnabled = true;
String systemPrompt = "أنت المحلل الذكي لتطبيق Siro لخدمات نقل الركاب. قمنا برصد قراءات أسعار المنافسين لتقديم عروض وحملات تسويقية ملائمة ومخصصة.";
// --- AI API Token & Cost Telemetry ---
int apiRequestsCount = 0;
int totalTokensUsed = 0;
double estimatedCostUSD = 0.0;
// --- Price Comparison Data ---
List hourlyPriceData = [];
List pciRegions = [];
Map siroBasePrices = {};
// --- Country Selection Handler ---
void changeCountry(String countryCode) {
selectedCountry = countryCode;
fetchAnomalies();
fetchCampaignsLog();
fetchPriceComparison();
fetchPriceGapHeatmap();
fetchMarketShareAnalytics();
fetchAiPricePrediction();
fetchWinbackTargets();
}
// --- Autopilot Status Toggle ---
void toggleAutopilot(bool value) {
isAutopilotEnabled = value;
update();
mySnackbarInfo(value ? "Full Autopilot mode enabled".tr : "Approval Mode enabled".tr);
}
// --- System Prompt Configuration Saver ---
void savePrompt(String newPrompt) {
systemPrompt = newPrompt;
update();
mySnackbarSuccess("Gemini system instructions updated successfully".tr);
}
Future<void> fetchAnomalies() async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getMarketAnomalies,
payload: params,
);
if (res is Map && res['status'] == 'success') {
priceAnomalies = res['message'] ?? [];
} else {
mySnackbarError("Failed to fetch price anomalies");
}
} catch (e) {
mySnackbarError("Network error while loading anomalies");
} finally {
isLoading = false;
update();
}
}
Future<void> fetchTelemetry() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getTelemetry,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
apiRequestsCount = data['api_requests_count'] ?? 0;
totalTokensUsed = data['total_tokens_used'] ?? 0;
estimatedCostUSD = (data['estimated_cost_usd'] ?? 0.0).toDouble();
update();
}
}
} catch (e) {
// Silently fail — telemetry is non-critical
}
}
Future<void> fetchCampaignsLog() async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getCampaignsLog,
payload: params,
);
if (res is Map && res['status'] == 'success') {
campaignsLog = res['message'] ?? [];
} else {
mySnackbarError("Failed to fetch campaign logs");
}
} catch (e) {
mySnackbarError("Network error while loading campaign logs");
} finally {
isLoading = false;
update();
}
}
Future<void> fetchPriceComparison() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.getPriceComparison,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
hourlyPriceData = data['hourly_competitor_prices'] ?? [];
pciRegions = data['pci_regions'] ?? [];
siroBasePrices = data['siro_base_prices'] is Map
? data['siro_base_prices'] as Map
: {};
update();
}
}
} catch (e) {
// Silently fail
}
}
Future<void> triggerAICampaign() async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
}
var res = await CRUD().post(
link: AppLink.triggerCampaign,
payload: params,
);
if (res is Map) {
if (res['status'] == 'success') {
mySnackbarSuccess("AI campaign triggered successfully! Promos sent.");
fetchCampaignsLog();
fetchTelemetry();
} else {
mySnackbarWarning(res['message'] ?? "Campaign rate limited.");
}
} else {
mySnackbarError("Failed to trigger AI campaign");
}
} catch (e) {
mySnackbarError("Network error while triggering campaign");
} finally {
isLoading = false;
update();
}
}
// --- What-If Simulator State ---
double? simulatedPci;
double? simulatedMarketShare;
String? simulatorRecommendationStatus;
String? simulatorRecommendationMessage;
Future<void> runWhatIfSimulation(String proposedSpeedPrice) async {
isLoading = true;
update();
try {
Map<String, dynamic> params = {
'speed_price': proposedSpeedPrice
};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY'; // Fallback
}
var res = await CRUD().post(
link: AppLink.whatIfSimulator,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
simulatedPci = (data['simulated_pci'] ?? 0.0).toDouble();
simulatedMarketShare = (data['simulated_market_share_percent'] ?? 0.0).toDouble();
simulatorRecommendationStatus = data['recommendation_status'];
simulatorRecommendationMessage = data['recommendation_message'];
}
} else {
mySnackbarError(res['message'] ?? "Failed to run simulation");
}
} catch (e) {
mySnackbarError("Network error during simulation");
} finally {
isLoading = false;
update();
}
}
// --- Heatmap State ---
List heatmapData = [];
double currentSiroPriceHeatmap = 0.0;
Future<void> fetchPriceGapHeatmap() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY'; // Fallback
}
var res = await CRUD().post(
link: AppLink.getPriceGapHeatmap,
payload: params,
);
if (res is Map && res['status'] == 'success') {
final data = res['message'];
if (data is Map) {
heatmapData = data['heatmap_data'] ?? [];
currentSiroPriceHeatmap = (data['current_siro_price_per_km'] ?? 0.0).toDouble();
update();
}
}
} catch (e) {
// Silently fail
}
}
// --- Market Share Analytics ---
List marketShareData = [];
Future<void> fetchMarketShareAnalytics() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY';
}
var res = await CRUD().post(
link: AppLink.getMarketShareAnalytics,
payload: params,
);
if (res is Map && res['status'] == 'success') {
marketShareData = res['historical_data'] ?? [];
update();
}
} catch (e) {
// Ignore
}
}
// --- AI Price Prediction ---
String? aiPredictionMessage;
List predictedSurgeHours = [];
Future<void> fetchAiPricePrediction() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY';
}
var res = await CRUD().post(
link: AppLink.aiPricePrediction,
payload: params,
);
if (res is Map && res['status'] == 'success') {
aiPredictionMessage = res['ai_analysis_message'];
predictedSurgeHours = res['predicted_surge_hours'] ?? [];
update();
}
} catch (e) {
// Ignore
}
}
// --- Win-Back Targets ---
List winbackTargets = [];
int winbackTotalCount = 0;
Future<void> fetchWinbackTargets() async {
try {
Map<String, dynamic> params = {};
if (selectedCountry != 'All') {
params['country_code'] = selectedCountry;
} else {
params['country_code'] = 'SY';
}
var res = await CRUD().post(
link: AppLink.winbackHotspotTargets,
payload: params,
);
if (res is Map && res['status'] == 'success') {
winbackTargets = res['targets'] ?? [];
winbackTotalCount = res['total_targets'] ?? 0;
update();
}
} catch (e) {
// Ignore
}
}
}
@@ -0,0 +1,169 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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) {
// (اختياري) حدّث الكاش/الواجهة — مثلاً أعد الجلب
mySnackbarSuccess(d['message']?.toString() ?? 'Passenger updated successfully');
// 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();
mySnackbarSuccess('Prize Added');
} else {
// Authentication failed, handle accordingly
mySnackbarError('Authentication failed');
// 'Authentication failed');
}
} else {
// Local authentication not available, proceed with payment without authentication
await addPassengerPrizeToWallet();
mySnackbarSuccess('Prize Added');
}
} 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,84 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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) {
mySnackbarError("فشل جلب أكواد الخصم: $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) {
mySnackbarError("فشل إضافة كود الخصم: $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) {
mySnackbarError("فشل حذف كود الخصم: $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) {
mySnackbarError("فشل تحديث كود الخصم: $e");
return false;
} finally {
isLoading.value = false;
}
}
}
@@ -0,0 +1,97 @@
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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 {
mySnackbarError("Failed to fetch blacklist");
}
} catch (e) {
mySnackbarError("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') {
mySnackbarSuccess("Driver unblocked successfully");
fetchBlacklist(); // Refresh
} else {
mySnackbarError(res['message'] ?? "Failed to unblock driver");
}
} catch (e) {
mySnackbarError("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') {
mySnackbarSuccess("Passenger unblocked successfully");
fetchBlacklist(); // Refresh
} else {
mySnackbarError(res['message'] ?? "Failed to unblock passenger");
}
} catch (e) {
mySnackbarError("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 {
mySnackbarError("Failed to fetch scorecard");
scorecardData = {};
}
} catch (e) {
mySnackbarError("Network error");
scorecardData = {};
} finally {
isLoading = false;
update();
}
}
}
@@ -0,0 +1,582 @@
import 'dart:convert';
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/info.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../../views/widgets/snackbar.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 {
mySnackbarError(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) {
mySnackbarSuccess('added'); // 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;
mySnackbarSuccess('Driver data saved successfully');
} else {
mySnackbarError('Failed to save driver data');
}
}
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') {
mySnackbarSuccess('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;
mySnackbarSuccess('message');
}
} 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
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 {
mySnackbarError("JSON string not found");
}
// Rest of your code...
} else {}
}
}
@@ -0,0 +1,72 @@
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,56 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.dart';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/views/widgets/snackbar.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');
mySnackbarError("السيرفر أرجع: $res");
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']}');
mySnackbarError("${d['message'] ?? d['status']}");
}
}
} catch (e) {
Log.print('Error fetching audit logs: $e');
mySnackbarError("$e");
}
isLoading = false;
update();
}
}
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../functions/crud.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'
String selectedCountry = 'Jordan';
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", // القيمة الافتراضية للفرع
"country": selectedCountry,
},
);
if (response != "failure") {
mySnackbarSuccess('تمت إضافة الموظف بنجاح');
_clearFields();
Get.back();
} else {
mySnackbarError('فشل في إضافة الموظف. يرجى المحاولة لاحقاً');
}
} catch (e) {
mySnackbarError('حدث خطأ: $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,514 @@
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 '../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 {
// 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();
} else {
driversWalletPoints = [];
isLoading = false;
update();
}
}
}
@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'otp_helper.dart';
class OtpVerificationAdmin extends StatefulWidget {
final String phone;
const OtpVerificationAdmin({super.key, 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,342 @@
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:get/get.dart';
import 'package:firebase_messaging/firebase_messaging.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}/auth/otp/request.php';
static final String _verifyOtpUrl = '${AppLink.server}/auth/otp/verify.php';
static final String _checkAdminLogin =
'${AppLink.server}/Admin/auth/login.php';
var selectedCountry = 'Jordan'.obs;
bool isFirstRun = box.read(BoxName.countryCode) == null;
void changeCountry(String country) {
selectedCountry.value = country;
box.write(BoxName.countryCode, country);
}
/// إرسال OTP
static Future<bool> sendOtp(String phoneNumber) async {
try {
final detectedCountry = AppLink.detectCountryFromPhone(phoneNumber);
if (detectedCountry.isNotEmpty) {
await box.write(BoxName.countryCode, detectedCountry);
}
final response = await CRUD().post(
link: _sendOtpUrl,
payload: {'receiver': phoneNumber, 'user_type': 'admin'},
);
// Log.print('_sendOtpUrl: ${_sendOtpUrl}');
// Log.print('response: ${response}');
if (response != 'failure') {
mySnackbarSuccess('تم إرسال رمز التحقق إلى رقمك عبر WhatsApp');
return true;
} else {
mySnackbarError('حدث خطأ من الخادم. حاول مجددًا.');
return false;
}
} catch (e) {
Log.print('OTP SEND ERROR: $e');
mySnackbarError('حدث خطأ أثناء الإرسال: $e');
return false;
}
}
/// التحقق من OTP
Future<void> verifyOtp(String phoneNumber, String otp) async {
try {
final response = await CRUD().post(
link: _verifyOtpUrl,
payload: {
'phone_number': phoneNumber,
'token_code': otp,
'user_type': 'admin',
'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 {
mySnackbarError(response['message'] ?? 'فشل في التحقق.');
}
} else {
mySnackbarError('فشل من الخادم. حاول مرة أخرى.');
}
} catch (e) {
Log.print('OTP VERIFY ERROR: $e');
mySnackbarError('خطأ في التحقق: $e');
}
}
/// تسجيل الدخول بكلمة المرور والبصمة
Future<bool> loginWithPassword(String password, [String phone = '']) async {
try {
final fingerprint = box.read(BoxName.fingerPrint);
final response = await CRUD().post(
link: _checkAdminLogin,
payload: {
'fingerprint': fingerprint,
'password': password,
'phone': phone,
},
);
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');
mySnackbarError('حدث خطأ أثناء تسجيل الدخول: $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');
mySnackbarError('خطأ في التحقق من الرمز: $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 (role != 'super_admin' && data['country'] != null) {
await box.write(BoxName.countryCode, data['country']);
}
}
if (data['phone'] != null) {
await box.write(BoxName.adminPhone, data['phone']);
}
}
await box.write(BoxName.phoneVerified, true);
await box.write('admin_password', password);
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
try {
FirebaseMessaging.instance.subscribeToTopic('admin_alerts');
} catch (e) {
Log.print('FCM Subscribe error: $e');
}
}
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 {
mySnackbarError('الرجاء إدخال رمز صحيح');
}
},
);
}
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)');
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
try {
FirebaseMessaging.instance.subscribeToTopic('admin_alerts');
} catch (e) {
Log.print('FCM Subscribe error: $e');
}
}
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']));
}
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
try {
FirebaseMessaging.instance.subscribeToTopic('admin_alerts');
} catch (e) {
Log.print('FCM Subscribe error: $e');
}
}
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() {
if (box.read(BoxName.countryCode) == null) {
box.write(BoxName.countryCode, 'Jordan');
}
selectedCountry.value = box.read(BoxName.countryCode) ?? 'Jordan';
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,56 @@
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/main.dart';
import 'package:siro_admin/views/widgets/snackbar.dart';
class AdminRegisterController extends GetxController {
final nameCtrl = TextEditingController();
final phoneCtrl = TextEditingController();
final passCtrl = TextEditingController();
final formKey = GlobalKey<FormState>();
var isLoading = false.obs;
Future<void> register() async {
if (!formKey.currentState!.validate()) return;
isLoading.value = true;
try {
// جلب البصمة، وإذا لم تكن موجودة يمكن محاولة توليدها أو طلبها
String fingerprint = box.read('fingerprint') ?? '';
final response = await CRUD().post(
link: '${AppLink.server}/Admin/auth/register.php',
payload: {
'name': nameCtrl.text.trim(),
'phone': phoneCtrl.text.trim(),
'password': passCtrl.text.trim(),
'fingerprint': fingerprint,
},
);
if (response != 'failure') {
if (response['status'] == 'pending') {
mySnackbarSuccess(response['message'] ?? 'تم تقديم الطلب بنجاح');
Future.delayed(const Duration(seconds: 2), () => Get.back());
} else {
mySnackbarError('حدث خطأ غير متوقع');
}
}
} catch (e) {
mySnackbarError('فشل في الاتصال بالخادم');
} finally {
isLoading.value = false;
}
}
@override
void dispose() {
nameCtrl.dispose();
phoneCtrl.dispose();
passCtrl.dispose();
super.dispose();
}
}
@@ -0,0 +1,110 @@
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 '../../main.dart';
import '../../views/widgets/snackbar.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(),
});
mySnackbarSuccess('${'Transaction successful'.tr} ${dec['amount']}');
// Get.find<CaptainWalletController>().getCaptainWalletFromRide();
} else if (dec['disbursement_status'] == 'failed') {
mySnackbarError('Transaction failed'.tr);
}
}
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"
};
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,44 @@
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.dart';
import '../functions/crud.dart';
class DriverController extends GetxController {
bool isLoading = false;
List drivers = [];
Map driverDetails = {};
// جلب السائقين pending
getDriversPending() async {
isLoading = true;
update();
var res = await CRUD().post(
link: AppLink.getDriversPending,
payload: {},
);
if (res != 'failure') {
drivers = (res)['message'];
isLoading = false;
update(['drivers']);
} else {
mySnackbarError('Failed to load drivers');
isLoading = false;
update();
}
}
// جلب تفاصيل سائق واحد
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 {
mySnackbarError('Failed to load driver details');
}
}
}
@@ -0,0 +1,68 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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 {
mySnackbarError('error');
}
}
@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 {
mySnackbarError('error');
}
}
@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 {
mySnackbarError('error');
}
}
@override
void onInit() {
getBestDriver();
super.onInit();
}
}
@@ -0,0 +1,80 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/widgets.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/views/widgets/snackbar.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')) {
mySnackbarError('Failed to load employees');
return;
}
try {
var jsonData = res is String ? jsonDecode(res) : res;
employee = jsonData['message'];
update();
} catch (e) {
mySnackbarError('Invalid server response');
}
}
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();
mySnackbarSuccess('Employee added successfully');
name.clear();
education.clear();
site.clear();
phone.clear();
status.clear();
fetchEmployee();
} else {
// Handle the error case by showing a snackbar with an error message
mySnackbarError('Failed to add employee');
}
}
@override
void onInit() {
fetchEmployee();
super.onInit();
}
}
@@ -0,0 +1,406 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:firebase_messaging/firebase_messaging.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 '../../constant/api_key.dart';
import '../../constant/box_name.dart';
import '../../constant/char_map.dart';
import '../../constant/links.dart';
import '../../constant/style.dart';
import '../../main.dart';
import '../../print.dart';
import '../../views/widgets/elevated_btn.dart';
import '../../views/widgets/snackbar.dart';
import '../notification_controller.dart';
import 'notification_service.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 (kIsWeb) {
await messaging.requestPermission();
} else if (Platform.isAndroid) {
// Request permission for Android
await messaging.requestPermission();
} else if (Platform.isIOS) {
// Request permission for iOS
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;
mySnackbarSuccess("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;
mySnackbarSuccess("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
// if (message.notification != null) {
if (message.data.isNotEmpty && message.notification != null) {
fireBaseTitles(message);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data['type'] == 'marketing_report') {
Get.toNamed('/social-intelligence');
}
});
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage? message) {
if (message != null && message.data['type'] == 'marketing_report') {
Future.delayed(const Duration(seconds: 2), () {
Get.toNamed('/social-intelligence');
});
}
});
}
void fireBaseTitles(RemoteMessage message) {
if (message.notification!.title! == 'Order') {
if (kIsWeb) {
// Handle web push payload
} else 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();
}
}
void driverAppliedTripSnakBar() {
mySnackbarSuccess('Driver Applied the Ride for You'.tr);
}
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,
// ));
// }
// 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
void sendNotificationAll(String title, body) async {
// Deprecated: Admin panel notifications are sent via NotificationService.
}
void sendNotificationToAnyWithoutData(
String title, String body, String token, String tone) async {
await NotificationService.sendNotification(
target: token,
title: title,
body: body,
category: 'fromAdmin',
tone: tone,
);
}
}
@@ -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,57 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../constant/links.dart';
class NotificationService {
// تأكد من أن هذا هو الرابط الصحيح لملف الإرسال
static String get _serverUrl => '${AppLink.server.replaceAll('_v3', '')}/ride/firebase/send_fcm.php';
static Future<void> sendNotification({
required String target,
required String title,
required String body,
required String? category, // <-- [الإضافة الأولى]
String? tone,
List<String>? driverList,
bool isTopic = false,
}) async {
try {
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,51 @@
// import 'dart:convert';
// import '../../print.dart';
// class AccessTokenManager {
// static final AccessTokenManager _instance = AccessTokenManager._internal();
// late final String serviceAccountJsonKey;
// 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,791 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
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 '../../views/widgets/snackbar.dart';
import 'device_info.dart';
import 'encrypt_decrypt.dart';
import 'ssl_pinning.dart';
class CRUD {
var dev = '';
final _client = SslPinning.createPinnedClient();
getJWT() async {
// إذا كان الأدمن مسجل دخوله بالفعل، لا تقم بتوليد توكن "ضيف" قديم
if (box.read(BoxName.driverID) != null) {
Log.print('Admin session active. Skipping guest JWT.');
return;
}
// Note: We send 'android' instead of 'web' because the backend's loginAdmin.php
// explicitly validates the audience against 'allowedDriver1' (android) and 'allowedDriver2' (ios).
// Sending 'web' will cause a 400 Bad Request error.
dev = kIsWeb ? 'android' : (Platform.isAndroid ? 'android' : 'ios');
var payload = {
'id': 'admin',
'password': AK.passnpassenger,
'aud': '${AK.allowed}$dev',
};
Log.print('payload: $payload');
var response1 = await _client.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 storage.write(
key: BoxName.jwt, value: 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 _client.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}');
String preview = response.body.length > 500
? '${response.body.substring(0, 500)}... (truncated)'
: response.body;
Log.print('Response Body: $preview');
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 _client.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}');
String preview = response.body.length > 500
? '${response.body.substring(0, 500)}... (truncated)'
: response.body;
Log.print('Response Body: $preview');
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 _client.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}${kIsWeb ? 'android' : (Platform.isAndroid ? 'android' : 'ios')}',
'fingerPrint': fingerPrint
};
var fallbackRes = await _client.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 _client.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 _client.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') {
mySnackbarSuccess('Message sent successfully');
} else {
mySnackbarError('Failed to send message');
}
}
Future<dynamic> getAgoraToken({
required String channelName,
required String uid,
}) async {
var res = await _client.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 _client.post(
url,
body: data,
headers: headers,
);
if (response.statusCode == 200) {
return response.body;
}
return response.statusCode;
}
Future allMethodForAI(String prompt, driverID, imagePath) async {}
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 _client.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> 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 _client.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 _client.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 _client.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 _client.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 _client.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,148 @@
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:jailbreak_root_detection/jailbreak_root_detection.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../../main.dart';
import '../../print.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
class DeviceHelper {
static Future<String> getDeviceFingerprint() async {
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
Map<String, dynamic> deviceData;
try {
if (kIsWeb) {
WebBrowserInfo webInfo = await deviceInfoPlugin.webBrowserInfo;
return '${webInfo.vendor}_${webInfo.browserName.name}';
}
if (Platform.isAndroid) {
// Fetch Android-specific device information
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
deviceData = androidInfo.toMap(); // Convert to a map for easier access
} 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 {
return 'unknown_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 isTampered = false;
String bundleId = "";
try {
isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
isJailBroken = await JailbreakRootDetection.instance.isJailBroken;
isRealDevice = await JailbreakRootDetection.instance.isRealDevice;
PackageInfo packageInfo = await PackageInfo.fromPlatform();
bundleId = packageInfo.packageName;
if (bundleId.isNotEmpty) {
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
}
} catch (e) {
Log.print("Error during security checks: $e");
}
await box.write('isNotTrust', isNotTrust);
await box.write('isTampered', isTampered);
await box.write('isJailBroken', isJailBroken);
if (isJailBroken || isRealDevice == false || isTampered) {
_showSecurityWarning();
}
}
static Future<void> clearAllData() async {
await box.erase();
exit(0);
}
static void _showSecurityWarning() {
final secondsRemaining = RxInt(10);
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({
'seconds': secondsRemaining.value.toString(),
}),
)),
SizedBox(height: 24),
Obx(() => SizedBox(
width: double.infinity,
child: CupertinoActivityIndicator(
radius: 15,
animating: true,
))),
SizedBox(height: 8),
Obx(() => ClipRRect(
borderRadius: BorderRadius.circular(8),
child: LinearProgressIndicator(
value: secondsRemaining.value / 10,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation<Color>(
CupertinoColors.systemRed),
minHeight: 8,
),
)),
],
),
),
barrierDismissible: false,
);
Timer.periodic(Duration(seconds: 1), (timer) {
secondsRemaining.value--;
if (secondsRemaining.value <= 0) {
timer.cancel();
_clearDataAndExit();
}
});
}
static Future<void> _clearDataAndExit() async {
await box.erase();
exit(0);
}
}
@@ -0,0 +1,40 @@
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 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,77 @@
import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:flutter/foundation.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import '../../constant/char_map.dart';
import '../../env/env.dart';
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,179 @@
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 '../../views/widgets/snackbar.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) => mySnackbarWarning('Your Account is Deleted'.tr));
}
checkBeforeDelete() async {
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: WidgetStateProperty.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: WidgetStateProperty.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 {
mySnackbarError('Email you inserted is Wrong.'.tr);
}
}
}
}
@@ -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;
}
}
@@ -0,0 +1,51 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class SecurityChecks {
static const platform = MethodChannel(
'com.siro.siro_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 false; // Platform not supported → treat as secure
} catch (e) {
print("Security check error: $e");
return false;
}
}
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,47 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart' as http_io;
class SslPinning {
SslPinning._();
static final Map<String, List<String>> _pins = {
'intaleq.xyz': [
'/tNRUeeLxUhQU5gbgdpVWC6QBGAqc/ujg8Kcf0wQiAM=',
'Hlx/0EWNDH5Xkt2KzvqxUzbw0vvEsyZSlibialSyGqI=',
],
'siromove.com': [
'C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=',
'diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=',
],
};
static final List<String> _globalPins = [
'Ex/Od4QBaJmloAIDqe/IDxjrvXVYBxftwVU1gJMINuw=',
'lrzsBiZJdvN0YHeazyjFp8/oo8Cq4RqP/O4FwL3fCMY=',
'aXKbjhWobvwXelevtxcd/GSt0owvyozxUH40RTzLFHA=',
];
static http.Client createPinnedClient() {
if (kIsWeb) {
return http.Client();
}
final httpClient = HttpClient()
..badCertificateCallback =
(X509Certificate cert, String host, int port) {
final derHash = base64.encode(sha256.convert(cert.der).bytes);
for (final entry in _pins.entries) {
if (host == entry.key || host.endsWith('.${entry.key}')) {
if (entry.value.contains(derHash)) return true;
}
}
if (_globalPins.contains(derHash)) return true;
return false;
};
return http_io.IOClient(httpClient);
}
}
@@ -0,0 +1,439 @@
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: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 '../../views/widgets/snackbar.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');
mySnackbarError('Image Upload Failed'.tr);
} finally {
isloading = false;
update();
}
}
// choosFaceFromDriverLicense(String link, String imageType) async {
// final pickedImage = await picker.pickImage(
// source: ImageSource.camera,
// preferredCameraDevice: CameraDevice.rear,
// );
// if (pickedImage == null) return;
// image = File(pickedImage.path);
// File? processedImage;
// // For face images, use face detection and cropping
// processedImage = await detectAndCropFace(image!);
// if (processedImage == null) {
// Get.snackbar('Face Detection Failed', 'No face detected in the image.');
// return;
// }
// isloading = true;
// update();
// File compressedImage = await compressImage(processedImage);
// try {
// await uploadImage(
// compressedImage,
// {
// 'driverID': box.read(BoxName.driverID).toString(),
// 'imageType': imageType
// },
// link,
// );
// } catch (e) {
// Get.snackbar('Image Upload Failed'.tr, e.toString(),
// backgroundColor: AppColor.redColor);
// } finally {
// isloading = false;
// update();
// }
// }
choosFace(String link, String imageType) async {
final pickedImage = await picker.pickImage(
source: ImageSource.camera,
preferredCameraDevice: CameraDevice.front,
);
if (pickedImage != null) {
image = File(pickedImage.path);
isloading = true;
update();
// Compress the image
File compressedImage = await compressImage(File(pickedImage.path));
// Save the picked image directly
// File savedImage = File(pickedImage.path);
print('link =$link');
try {
await uploadImage(
compressedImage,
{
'driverID':
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
'imageType': imageType
},
link,
);
} catch (e) {
mySnackbarError('Image Upload Failed'.tr);
} 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');
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!') {
mySnackbarSuccess('Image uploaded successfully!'.tr);
}
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) {
mySnackbarError('Image Upload Failed'.tr);
} 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());
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,90 @@
import 'dart:convert';
import 'package:get/get.dart';
import '../../constant/links.dart';
import '../../views/widgets/snackbar.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',
// );
mySnackbarSuccess('addPaymentToDriver');
} else {
mySnackbarError('addPaymentToDriver');
}
}
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') {
mySnackbarSuccess('addDrivergift300');
} else {
mySnackbarError(res);
}
}
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') {
mySnackbarSuccess('addSeferWallet');
} else {
mySnackbarError('addSeferWallet');
}
}
}
@@ -0,0 +1,216 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../views/widgets/snackbar.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) {
mySnackbarWarning("الرجاء تعبئة جميع الحقول");
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 {
// إظهار تنبيه بدء العملية
mySnackbarInfo("يتم إرسال الإشعار لـ $target...");
try {
// استدعاء خدمة الإرسال
await NotificationService.sendNotification(
target: target,
title: titleController.text,
body: bodyController.text,
isTopic: true,
category: 'fromAdmin',
);
mySnackbarSuccess("تم إرسال الإشعار بنجاح");
} catch (e) {
mySnackbarError("فشل إرسال الإشعار: $e");
}
}
/// تصميم حقل الإدخال المخصص (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,143 @@
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
// مطابقة لحالات الرحلة الفعلية في قاعدة البيانات
final List<String> statusOptions = const [
'New', // جديد - بانتظار سائق
'waiting', // في انتظار سائق
'Apply', // سائق قبل الرحلة
'Arrived', // السائق وصل
'Begin', // الرحلة بدأت
'Finished', // مكتملة
'Cancel', // إلغاء
];
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') {
// يستجيب API الجديد بـ user_type, user, rides
final message = d['message'] ?? d;
passenger = message['user']; // user يمكن أن يكون سائقاً أو راكباً
ride = (message['rides'] is List && message['rides'].isNotEmpty)
? message['rides'][0]
: null;
// 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,149 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:siro_admin/constant/links.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);
}
}
}
@@ -0,0 +1,182 @@
// transit_admin_controller.dart — تحكم شاشات مواصلاتي (لوحة إدارة سيرو)
import 'package:get/get.dart';
import 'transit_admin_models.dart';
import 'transit_admin_service.dart';
class TransitAdminController extends GetxController {
bool isLoadingList = false;
List<TransitOrgSummary> orgs = [];
String? filterCountry;
String? filterType;
String? filterStatus;
String searchQuery = '';
bool isLoadingDetails = false;
TransitOrgDetails? selectedOrgDetails;
bool isCreating = false;
// ── مشرفو المؤسسة ────────────────────────────────────────────
bool isLoadingAdmins = false;
List<Map<String, dynamic>> orgAdmins = [];
bool isSavingAdmin = false;
@override
void onInit() {
super.onInit();
fetchOrgs();
}
Future<void> fetchOrgs() async {
isLoadingList = true;
update();
final res = await TransitAdminService.listOrgs(
country: filterCountry,
type: filterType,
contractStatus: filterStatus,
search: searchQuery,
);
if (res.success) orgs = res.data ?? [];
isLoadingList = false;
update();
}
Future<void> loadOrgDetails(int orgId) async {
isLoadingDetails = true;
selectedOrgDetails = null;
update();
final res = await TransitAdminService.getOrgDetails(orgId);
if (res.success) selectedOrgDetails = res.data;
isLoadingDetails = false;
update();
}
Future<bool> createOrg({
required String type,
required String country,
required String city,
required String nameAr,
required String nameEn,
required String adminName,
required String adminPhone,
}) async {
isCreating = true;
update();
final res = await TransitAdminService.createOrg(
type: type,
country: country,
city: city,
nameAr: nameAr,
nameEn: nameEn,
adminName: adminName,
adminPhone: adminPhone,
);
isCreating = false;
update();
if (res.success) {
await fetchOrgs();
return true;
}
Get.snackbar('مواصلاتي', res.message);
return false;
}
Future<void> fetchOrgAdmins(int orgId) async {
isLoadingAdmins = true;
update();
final res = await TransitAdminService.listOrgAdmins(orgId);
if (res.success) orgAdmins = res.data ?? [];
isLoadingAdmins = false;
update();
}
Future<bool> addOrgAdmin({
required int orgId,
required String name,
required String phone,
required String role,
}) async {
isSavingAdmin = true;
update();
final res = await TransitAdminService.addOrgAdmin(
orgId: orgId,
name: name,
phone: phone,
role: role,
);
isSavingAdmin = false;
update();
if (res.success) {
await fetchOrgAdmins(orgId);
return true;
}
Get.snackbar('مواصلاتي', res.message);
return false;
}
Future<void> toggleOrgAdmin(int orgId, int adminId, bool isActive) async {
final res = await TransitAdminService.toggleOrgAdmin(adminId: adminId, isActive: isActive);
if (res.success) {
await fetchOrgAdmins(orgId);
} else {
Get.snackbar('مواصلاتي', res.message);
}
}
// ── إدارة العقد ───────────────────────────────────────────────
bool isUpdatingContract = false;
Future<bool> updateContractStatus(int orgId, String contractStatus) async {
isUpdatingContract = true;
update();
final res = await TransitAdminService.updateContractStatus(
orgId: orgId, contractStatus: contractStatus);
isUpdatingContract = false;
if (res.success) {
await fetchOrgs();
return true;
}
Get.snackbar('مواصلاتي', res.message);
update();
return false;
}
// ── اعتماد الخطوط ──────────────────────────────────────────────
bool isLoadingPendingRoutes = false;
List<Map<String, dynamic>> pendingRoutes = [];
bool isApprovingRoute = false;
Future<void> fetchPendingRoutes({int? orgId}) async {
isLoadingPendingRoutes = true;
update();
final res = await TransitAdminService.listPendingRoutes(orgId: orgId);
if (res.success) pendingRoutes = res.data ?? [];
isLoadingPendingRoutes = false;
update();
}
Future<bool> approveRoute(int routeId, String action) async {
isApprovingRoute = true;
update();
final res = await TransitAdminService.approveRoute(
routeId: routeId, action: action);
isApprovingRoute = false;
if (res.success) {
pendingRoutes.removeWhere((r) => int.tryParse(r['id'].toString()) == routeId);
update();
return true;
}
Get.snackbar('مواصلاتي', res.message);
update();
return false;
}
}
@@ -0,0 +1,70 @@
// transit_admin_models.dart — نماذج بيانات مواصلاتي (لوحة إدارة سيرو)
class TransitOrgSummary {
final int id;
final String type;
final String country;
final String city;
final String nameAr;
final String nameEn;
final String? logoUrl;
final String contractStatus;
final String? trialEndsAt;
final int driversCount;
final int vehiclesCount;
final int activeRoutes;
final int activeEnrollments;
TransitOrgSummary({
required this.id,
required this.type,
required this.country,
required this.city,
required this.nameAr,
required this.nameEn,
required this.contractStatus,
this.logoUrl,
this.trialEndsAt,
this.driversCount = 0,
this.vehiclesCount = 0,
this.activeRoutes = 0,
this.activeEnrollments = 0,
});
factory TransitOrgSummary.fromJson(Map<String, dynamic> j) => TransitOrgSummary(
id: int.tryParse(j['id'].toString()) ?? 0,
type: j['type']?.toString() ?? '',
country: j['country']?.toString() ?? '',
city: j['city']?.toString() ?? '',
nameAr: j['name_ar']?.toString() ?? '',
nameEn: j['name_en']?.toString() ?? '',
logoUrl: j['logo_url']?.toString(),
contractStatus: j['contract_status']?.toString() ?? '',
trialEndsAt: j['trial_ends_at']?.toString(),
driversCount: int.tryParse(j['drivers_count']?.toString() ?? '0') ?? 0,
vehiclesCount: int.tryParse(j['vehicles_count']?.toString() ?? '0') ?? 0,
activeRoutes: int.tryParse(j['active_routes']?.toString() ?? '0') ?? 0,
activeEnrollments: int.tryParse(j['active_enrollments']?.toString() ?? '0') ?? 0,
);
}
class TransitOrgDetails {
final Map<String, dynamic> org;
final Map<String, dynamic> counts;
final Map<String, dynamic> trips;
final List<dynamic> routes;
TransitOrgDetails({
required this.org,
required this.counts,
required this.trips,
required this.routes,
});
factory TransitOrgDetails.fromJson(Map<String, dynamic> j) => TransitOrgDetails(
org: Map<String, dynamic>.from(j['org'] ?? {}),
counts: Map<String, dynamic>.from(j['counts'] ?? {}),
trips: Map<String, dynamic>.from(j['trips'] ?? {}),
routes: (j['routes'] is List) ? List<dynamic>.from(j['routes']) : [],
);
}
@@ -0,0 +1,191 @@
// transit_admin_service.dart — طبقة الاتصال بـ backend/Admin/transit (لوحة إدارة سيرو)
import '../functions/crud.dart';
import '../../constant/links.dart';
import 'transit_admin_models.dart';
class TransitApiResult<T> {
final bool success;
final T? data;
final String message;
TransitApiResult(this.success, this.data, this.message);
}
class TransitAdminService {
static String get _base => '${AppLink.server}/Admin/transit';
static String _errMsg(dynamic res) {
if (res == 'no_internet') return 'تحقق من اتصالك بالإنترنت';
if (res == 'token_expired') return 'انتهت الجلسة، حاول مجدداً';
if (res is Map && res['message'] is String) return res['message'];
return 'حدث خطأ، حاول مجدداً';
}
static Future<TransitApiResult<List<TransitOrgSummary>>> listOrgs({
String? country,
String? type,
String? contractStatus,
String? search,
}) async {
final payload = <String, dynamic>{};
if (country != null && country.isNotEmpty) payload['country'] = country;
if (type != null && type.isNotEmpty) payload['type'] = type;
if (contractStatus != null && contractStatus.isNotEmpty) {
payload['contract_status'] = contractStatus;
}
if (search != null && search.isNotEmpty) payload['search'] = search;
final res = await CRUD().post(link: '$_base/org/list.php', payload: payload);
if (res is Map && res['status'] == 'success' && res['message'] is Map) {
final msg = res['message'] as Map;
final list = (msg['orgs'] is List)
? (msg['orgs'] as List)
.map((o) => TransitOrgSummary.fromJson(Map<String, dynamic>.from(o)))
.toList()
: <TransitOrgSummary>[];
return TransitApiResult(true, list, 'ok');
}
return TransitApiResult(false, null, _errMsg(res));
}
static Future<TransitApiResult<TransitOrgDetails>> getOrgDetails(int orgId) async {
final res = await CRUD().post(
link: '$_base/org/details.php',
payload: {'org_id': orgId.toString()},
);
if (res is Map && res['status'] == 'success' && res['message'] is Map) {
return TransitApiResult(
true, TransitOrgDetails.fromJson(Map<String, dynamic>.from(res['message'])), 'ok');
}
return TransitApiResult(false, null, _errMsg(res));
}
static Future<TransitApiResult<Map<String, dynamic>>> createOrg({
required String type,
required String country,
required String city,
required String nameAr,
required String nameEn,
required String adminName,
required String adminPhone,
String? contactPhone,
String? contactEmail,
String? website,
}) async {
final res = await CRUD().post(link: '$_base/org/create.php', payload: {
'type': type,
'country': country,
'city': city,
'name_ar': nameAr,
'name_en': nameEn,
'admin_name': adminName,
'admin_phone': adminPhone,
if (contactPhone != null) 'contact_phone': contactPhone,
if (contactEmail != null) 'contact_email': contactEmail,
if (website != null) 'website': website,
});
if (res is Map && res['status'] == 'success') {
return TransitApiResult(true, Map<String, dynamic>.from(res['message'] ?? {}), 'ok');
}
return TransitApiResult(false, null, _errMsg(res));
}
// ── مشرفو المؤسسة ────────────────────────────────────────────
static Future<TransitApiResult<List<Map<String, dynamic>>>> listOrgAdmins(
int orgId) async {
final res = await CRUD().post(
link: '$_base/org/admins_list.php',
payload: {'org_id': orgId.toString()},
);
if (res is Map && res['status'] == 'success' && res['message'] is Map) {
final msg = res['message'] as Map;
final list = (msg['admins'] is List)
? (msg['admins'] as List).map((a) => Map<String, dynamic>.from(a)).toList()
: <Map<String, dynamic>>[];
return TransitApiResult(true, list, 'ok');
}
return TransitApiResult(false, null, _errMsg(res));
}
static Future<TransitApiResult<void>> addOrgAdmin({
required int orgId,
required String name,
required String phone,
required String role,
}) async {
final res = await CRUD().post(link: '$_base/org/admin_add.php', payload: {
'org_id': orgId.toString(),
'name': name,
'phone': phone,
'role': role,
});
if (res is Map && res['status'] == 'success') return TransitApiResult(true, null, 'ok');
return TransitApiResult(false, null, _errMsg(res));
}
static Future<TransitApiResult<void>> toggleOrgAdmin({
required int adminId,
required bool isActive,
}) async {
final res = await CRUD().post(link: '$_base/org/admin_toggle.php', payload: {
'admin_id': adminId.toString(),
'is_active': isActive ? '1' : '0',
});
if (res is Map && res['status'] == 'success') return TransitApiResult(true, null, 'ok');
return TransitApiResult(false, null, _errMsg(res));
}
// ── اعتماد الخطوط ──────────────────────────────────────────────
static Future<TransitApiResult<List<Map<String, dynamic>>>> listPendingRoutes({
int? orgId,
}) async {
final payload = <String, dynamic>{};
if (orgId != null) payload['org_id'] = orgId.toString();
final res =
await CRUD().post(link: '$_base/route/pending.php', payload: payload);
if (res is Map && res['status'] == 'success' && res['message'] is Map) {
final msg = res['message'] as Map;
final list = (msg['routes'] is List)
? (msg['routes'] as List)
.map((r) => Map<String, dynamic>.from(r))
.toList()
: <Map<String, dynamic>>[];
return TransitApiResult(true, list, 'ok');
}
return TransitApiResult(false, null, _errMsg(res));
}
static Future<TransitApiResult<Map<String, dynamic>>> updateContractStatus({
required int orgId,
required String contractStatus, // active | trial | suspended | terminated
}) async {
final res = await CRUD().post(link: '$_base/org/update.php', payload: {
'org_id': orgId.toString(),
'contract_status': contractStatus,
});
if (res is Map && res['status'] == 'success') {
return TransitApiResult(
true, Map<String, dynamic>.from(res['message'] ?? {}), 'ok');
}
return TransitApiResult(false, null, _errMsg(res));
}
static Future<TransitApiResult<Map<String, dynamic>>> approveRoute({
required int routeId,
required String action, // approve | suspend | reject
}) async {
final res = await CRUD().post(link: '$_base/route/approve.php', payload: {
'route_id': routeId.toString(),
'action': action,
});
if (res is Map && res['status'] == 'success') {
return TransitApiResult(
true, Map<String, dynamic>.from(res['message'] ?? {}), 'ok');
}
return TransitApiResult(false, null, _errMsg(res));
}
}
+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!");
}
}
+313
View File
@@ -0,0 +1,313 @@
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: '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;
}
@@ -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',
);
}
+78
View File
@@ -0,0 +1,78 @@
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 'constant/theme.dart';
import 'controller/firebase/firbase_messge.dart';
import 'controller/functions/encrypt_decrypt.dart';
import 'firebase_options.dart';
import 'models/db_sql.dart';
import 'routes.dart';
import 'binding/initial_binding.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
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 (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
List<Future> initializationTasks = [
FirebaseMessagesController().getNotificationSettings(),
FirebaseMessagesController().getToken(),
];
await Future.wait(initializationTasks);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Siro Admin',
locale: const Locale('ar'),
fallbackLocale: const Locale('en'),
themeMode: ThemeMode.system,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
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);
}
}
@@ -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);
}
}
+24
View File
@@ -0,0 +1,24 @@
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';
import 'views/admin/marketing/marketing_page.dart';
import 'views/admin/marketing/heatmap_page.dart';
import 'views/admin/marketing/social_intelligence_screen.dart';
List<GetPage<dynamic>> routes = [
GetPage(name: "/", page: () => const AdminHomePage()),
GetPage(name: "/login", page: () => const AdminLoginPage()),
GetPage(name: "/register", page: () => const AdminRegisterPage()),
GetPage(name: "/promo", page: () => PromoManagementPage()),
GetPage(name: "/kazan", page: () => KazanEditorPage()),
GetPage(name: "/complaints", page: () => ComplaintListPage()),
GetPage(name: "/driver-docs", page: () => DriverDocsReviewPage()),
GetPage(name: "/marketing", page: () => const MarketingPage()),
GetPage(name: "/heatmap", page: () => const HeatmapPage()),
GetPage(name: "/social-intelligence", page: () => const SocialIntelligenceScreen()),
];
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,395 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constant/box_name.dart';
import '../../../controller/admin/captain_admin_controller.dart';
import '../../../main.dart';
import 'captain_details.dart';
class CaptainsPage extends StatelessWidget {
CaptainsPage({super.key});
final CaptainAdminController captainController =
Get.put(CaptainAdminController());
final TextEditingController searchController = TextEditingController();
final String myPhone = box.read(BoxName.adminPhone).toString();
bool get isSuperAdmin =>
myPhone == '963942542053' || myPhone == '963992952235';
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
body: Column(
children: [
_buildAppBar(context, cs),
_buildSearchSection(context, cs),
Expanded(
child: GetBuilder<CaptainAdminController>(
builder: (controller) {
if (controller.isLoading) {
return _buildLoadingState(cs);
}
final message = controller.captainData['message'];
if (message == null) {
return _buildEmptyState(cs);
}
final List<dynamic> captains =
message is List ? message : [message];
if (captains.isEmpty) {
return _buildEmptyState(cs);
}
return _buildResultsList(context, captains, cs);
},
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: cs.primary.withValues(alpha: 0.25)),
),
child: Icon(Icons.drive_eta_rounded,
color: cs.primary, size: 18),
),
const SizedBox(width: 10),
Text(
'قائمة الكباتن',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
Widget _buildSearchSection(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
child: Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.outline),
),
child: Row(
children: [
Expanded(
child: TextField(
controller: searchController,
keyboardType: TextInputType.phone,
style: TextStyle(color: cs.onSurface, fontSize: 14),
cursorColor: cs.primary,
decoration: InputDecoration(
hintText: '0990000000',
hintStyle: TextStyle(
color: cs.onSurfaceVariant, fontSize: 13),
prefixIcon: Icon(Icons.phone_android_rounded,
color: cs.onSurfaceVariant, size: 20),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 14),
),
onSubmitted: (_) => _performSearch(),
),
),
Padding(
padding: const EdgeInsets.all(6.0),
child: Material(
color: cs.primary,
borderRadius: BorderRadius.circular(10),
child: InkWell(
onTap: _performSearch,
borderRadius: BorderRadius.circular(10),
child: const Padding(
padding: EdgeInsets.all(10),
child: Icon(Icons.search_rounded,
color: Colors.white, size: 20),
),
),
),
),
],
),
),
);
}
void _performSearch() {
final phone = searchController.text.trim();
if (phone.isNotEmpty) {
captainController.find_driver_by_phone(phone);
}
}
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري التحميل...',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildEmptyState(ColorScheme cs) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.person_off_outlined,
size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('لا يوجد كباتن',
style: TextStyle(
color: cs.onSurfaceVariant, fontSize: 14)),
],
),
);
}
Widget _buildResultsList(
BuildContext context, List<dynamic> captains, ColorScheme cs) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
child: Row(
children: [
Container(
width: 3,
height: 14,
decoration: BoxDecoration(
color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
'نتائج البحث',
style: TextStyle(
color: cs.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 10),
Expanded(
child: Container(height: 1, color: cs.outline),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: cs.primary.withValues(alpha: 0.2)),
),
child: Text(
'${captains.length}',
style: TextStyle(
color: cs.primary,
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
),
],
),
),
Expanded(
child: ListView.separated(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 0, 16, 80),
itemCount: captains.length,
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final captain = captains[index] as Map<String, dynamic>;
return _buildCaptainCard(context, captain, cs);
},
),
),
],
);
}
Widget _buildCaptainCard(
BuildContext context,
Map<String, dynamic> captain,
ColorScheme cs) {
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: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
Get.to(() => const CaptainDetailsPage(),
arguments: {'data': captain});
},
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
Container(
width: 52,
height: 52,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
cs.primary.withValues(alpha: 0.8),
cs.primary,
],
),
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: cs.primary.withValues(alpha: 0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: const Icon(
Icons.person_rounded,
color: Colors.white,
size: 26,
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fullName,
style: TextStyle(
color: cs.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 6),
Row(
children: [
Icon(Icons.phone_rounded,
size: 13, color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(
phone,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontFamily: 'monospace',
),
),
],
),
if (isSuperAdmin && email != null) ...[
const SizedBox(height: 3),
Row(
children: [
Icon(Icons.email_outlined,
size: 12,
color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(
email,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
],
],
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 14, color: cs.onSurfaceVariant),
],
),
),
),
),
);
}
}
@@ -0,0 +1,424 @@
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 '../../../main.dart'; // Import main to access myPhone
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/my_textField.dart';
import '../../widgets/snackbar.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();
mySnackbarSuccess("Notification Sent");
}
},
),
),
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();
mySnackbarWarning("Captain has been removed");
},
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 '../../../controller/admin/captain_admin_controller.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/my_scafold.dart';
import '../../widgets/my_textField.dart';
import '../../widgets/snackbar.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
mySnackbarSuccess('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 {
const 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,261 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/controller/drivers/driver_not_active_controller.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) {
final cs = Theme.of(context).colorScheme;
controller.getDriversPending();
return Scaffold(
backgroundColor: cs.surface,
body: Column(
children: [
_buildAppBar(context, cs),
Expanded(
child: GetBuilder<DriverController>(
id: 'drivers',
builder: (c) {
if (c.isLoading) {
return _buildLoadingState(cs);
}
if (c.drivers.isEmpty) {
return _buildEmptyState(cs);
}
return RefreshIndicator(
onRefresh: () => controller.getDriversPending(),
color: cs.primary,
child: ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 80),
physics: const BouncingScrollPhysics(),
itemCount: c.drivers.length,
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: (ctx, i) {
final d = c.drivers[i];
return _buildDriverCard(d, cs);
},
),
);
},
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFFF59E0B).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
),
child: Icon(Icons.hourglass_top_rounded,
color: const Color(0xFFF59E0B), size: 18),
),
const SizedBox(width: 10),
Text(
'طلبات الكباتن المعلقة',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: cs.primary,
strokeWidth: 2,
backgroundColor: cs.primary.withValues(alpha: 0.1),
),
),
const SizedBox(height: 16),
Text('جاري تحميل الطلبات...',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
Widget _buildEmptyState(ColorScheme cs) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
shape: BoxShape.circle,
),
child: Icon(Icons.how_to_reg_rounded,
size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('لا يوجد طلبات تسجيل حالياً',
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)),
],
),
);
}
Widget _buildDriverCard(dynamic driver, ColorScheme cs) {
final String fullName =
'${driver['first_name'] ?? ''} ${driver['last_name'] ?? ''}'.trim();
final String phone = driver['phone'] ?? '';
return Container(
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
Get.to(() => DriverDetailsPage(driverId: driver['id'].toString()));
},
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFFF59E0B).withValues(alpha: 0.20),
const Color(0xFFF59E0B).withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.2)),
),
child: Center(
child: Text(
fullName.isNotEmpty ? fullName[0].toUpperCase() : 'D',
style: const TextStyle(
color: Color(0xFFF59E0B),
fontWeight: FontWeight.w800,
fontSize: 18,
),
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fullName,
style: TextStyle(
color: cs.onSurface,
fontWeight: FontWeight.w600,
fontSize: 15,
),
),
const SizedBox(height: 4),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFF59E0B)
.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.hourglass_top_rounded,
size: 11,
color: const Color(0xFFF59E0B)),
const SizedBox(width: 3),
const Text(
'معلق',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Color(0xFFF59E0B),
),
),
],
),
),
const SizedBox(width: 8),
Icon(Icons.phone_rounded,
size: 12, color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(
phone,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontFamily: 'monospace',
),
),
],
),
],
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 14, color: cs.onSurfaceVariant),
],
),
),
),
),
);
}
}
@@ -0,0 +1,328 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:get/get.dart';
import '../../../controller/admin/complaint_controller.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) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
body: Column(
children: [
Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('إدارة الشكاوى'.tr,
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
],
),
),
Expanded(
child: Column(
children: [
Obx(() {
if (controller.delayedComplaints.isNotEmpty) {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: cs.error.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.error.withValues(alpha: 0.3)),
),
child: Column(
children: [
Row(
children: [
Icon(Icons.warning_amber_rounded, color: cs.error),
const SizedBox(width: 12),
Expanded(
child: Text(
'هناك ${controller.delayedComplaints.length} شكاوى لم يتم حلها منذ أكثر من أسبوع!',
style: GoogleFonts.inter(color: cs.error, fontSize: 14, fontWeight: FontWeight.bold),
),
),
],
),
const SizedBox(height: 12),
Obx(() => MyElevatedButton(
title: controller.showOnlyDelayed.value ? 'عرض جميع الشكاوى' : 'عرض الشكاوى المتأخرة فقط',
onPressed: () => controller.showOnlyDelayed.toggle(),
kolor: cs.error,
)),
],
),
);
}
return const SizedBox.shrink();
}),
Expanded(
child: Obx(() {
final list = controller.showOnlyDelayed.value ? controller.delayedComplaints : controller.complaintList;
if (controller.isLoading.value && list.isEmpty) {
return Center(child: CircularProgressIndicator(color: cs.primary));
}
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) {
final cs = Theme.of(context).colorScheme;
Color statusColor = _getStatusColor(c['statusComplaint'], cs);
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: ExpansionTile(
tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: _buildStatusIndicator(c['statusComplaint'], statusColor, cs),
title: Text(
c['description']?.toString() ?? 'بدون وصف',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
'النوع: ${c['complaint_type'] ?? 'عام'} | الرحلة: ${c['ride_id']}',
style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12),
),
const SizedBox(height: 4),
Row(
children: [
Icon(Icons.person_rounded, size: 12, color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(c['passengerName'] ?? 'غير معروف', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
const SizedBox(width: 12),
Icon(Icons.drive_eta_rounded, size: 12, color: cs.onSurfaceVariant),
const SizedBox(width: 4),
Text(c['driverName'] ?? 'غير معروف', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
],
),
],
),
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(color: cs.outline),
_buildInfoRow('الوصف', c['description'] ?? 'لا يوجد وصف', cs),
const SizedBox(height: 12),
_buildInfoRow('الحل الحالي', c['resolution'] ?? 'لم يتم الحل بعد', cs),
const SizedBox(height: 12),
_buildRideDetails(c, cs),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: MyElevatedButton(
title: 'تحديث الحالة / حل الشكوى',
onPressed: () => _showResolveDialog(context, c),
kolor: cs.primary,
),
),
],
),
],
),
),
],
),
);
}
Widget _buildStatusIndicator(String? status, Color color, ColorScheme cs) {
return Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
shape: BoxShape.circle,
border: Border.all(color: color.withValues(alpha: 0.25)),
),
child: Icon(
status == 'Resolved' ? Icons.check_circle_rounded : Icons.pending_rounded,
color: color,
size: 20,
),
);
}
Widget _buildInfoRow(String label, String value, ColorScheme cs) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: GoogleFonts.inter(color: cs.primary, fontSize: 12)),
const SizedBox(height: 4),
Text(value, style: GoogleFonts.inter(color: cs.onSurface, fontSize: 14)),
],
);
}
Widget _buildRideDetails(dynamic c, ColorScheme cs) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: cs.outline),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildSmallStat('السعر', '${c['priceOfRide']} ل.س', cs),
_buildSmallStat('التقييم', '${c['avgRatingDriverFromPassengers'] ?? 0}★', cs),
_buildSmallStat('النوع', c['ascarType'] ?? 'N/A', cs),
],
),
],
),
);
}
Widget _buildSmallStat(String label, String value, ColorScheme cs) {
return Column(
children: [
Text(label, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 10)),
const SizedBox(height: 2),
Text(value, style: GoogleFonts.jetBrainsMono(fontWeight: FontWeight.bold, fontSize: 12, color: cs.onSurface)),
],
);
}
Color _getStatusColor(String? status, ColorScheme cs) {
switch (status) {
case 'Open': return cs.error;
case 'In Progress': return cs.tertiary;
case 'Resolved': return const Color(0xFF10B981);
default: return cs.onSurfaceVariant;
}
}
void _showResolveDialog(BuildContext context, dynamic c) {
final cs = Theme.of(context).colorScheme;
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: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('تحديث حالة الشكوى', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)),
const SizedBox(height: 20),
_buildStatusDropdown(selectedStatus, cs, (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, ColorScheme cs, Function(String?) onChanged) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: cs.outline),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: current,
isExpanded: true,
dropdownColor: cs.surfaceContainerHighest,
items: ['Open', 'In Progress', 'Resolved']
.map((s) => DropdownMenuItem(value: s, child: Text(s.tr, style: GoogleFonts.inter(color: cs.onSurface, fontSize: 14))))
.toList(),
onChanged: onChanged,
),
),
);
}
}
@@ -0,0 +1,327 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:siro_admin/views/widgets/glass_container.dart';
class DashboardV2Widget extends StatelessWidget {
const DashboardV2Widget({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return GetBuilder<DashboardV2Controller>(
init: DashboardV2Controller(),
builder: (ctrl) {
if (ctrl.isLoading) {
return SliverToBoxAdapter(
child: SizedBox(
height: 150,
child: Center(
child: CircularProgressIndicator(color: cs.primary),
),
),
);
}
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('مركز العمليات الحي (Real-time)', cs),
_buildRealtimeStats(ctrl.realtimeData, cs),
const SizedBox(height: 20),
if (ctrl.smartAlerts.isNotEmpty) ...[
_buildSectionTitle(
'التنبيهات الذكية (${ctrl.smartAlerts.length})', cs),
_buildSmartAlerts(ctrl.smartAlerts, cs),
const SizedBox(height: 10),
]
],
),
);
},
);
}
Widget _buildSectionTitle(String title, ColorScheme cs) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 10),
child: Row(
children: [
Container(
width: 3,
height: 14,
decoration: BoxDecoration(
color: cs.error,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
title,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
),
),
],
),
);
}
Widget _buildRealtimeStats(Map<String, dynamic> data, ColorScheme cs) {
final stats = [
{
'title': 'رحلات نشطة',
'value': data['active_rides']?.toString() ?? '0',
'icon': Icons.directions_car_rounded,
'color': cs.tertiary,
},
{
'title': 'سائقون أونلاين',
'value': data['online_drivers']?.toString() ?? '0',
'icon': Icons.wifi_tethering,
'color': const Color(0xFF10B981),
},
{
'title': 'إيرادات اليوم',
'value': '${data['revenue_today'] ?? 0}',
'icon': Icons.monetization_on_rounded,
'color': const Color(0xFFF59E0B),
},
{
'title': 'إيرادات الأمس',
'value': '${data['revenue_yesterday'] ?? 0}',
'icon': Icons.history_rounded,
'color': cs.onSurfaceVariant,
},
{
'title': 'شكاوى مفتوحة',
'value': data['new_complaints']?.toString() ?? '0',
'icon': Icons.warning_rounded,
'color': cs.error,
},
{
'title': 'رخص تنتهي قريبًا',
'value': data['expiring_licenses']?.toString() ?? '0',
'icon': Icons.sd_card_alert_rounded,
'color': const Color(0xFFF97316),
},
];
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, cs),
);
},
),
);
}
Widget _buildStatCard(Map<String, dynamic> stat, ColorScheme cs) {
final color = stat['color'] as Color;
return GlassContainer(
width: 150,
padding: const EdgeInsets.all(16),
borderRadius: 16,
gradientColors: [
color.withValues(alpha: 0.15),
color.withValues(alpha: 0.05),
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(8),
),
child: Icon(stat['icon'] as IconData, color: color, size: 16),
),
const Spacer(),
if (stat['title'] == 'رحلات نشطة' ||
stat['title'] == 'سائقون أونلاين')
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.5),
blurRadius: 4,
spreadRadius: 1,
)
])),
],
),
const Spacer(),
Text(
stat['value'].toString(),
style: TextStyle(
color: cs.onSurface,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 2),
Text(
stat['title'].toString(),
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
}
Widget _buildSmartAlerts(List<dynamic> alerts, ColorScheme cs) {
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: alerts.length > 5 ? 5 : alerts.length,
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, cs),
),
),
);
},
);
}
Widget _buildAlertItem(Map<String, dynamic> alert, ColorScheme cs) {
Color getSeverityColor(String severity) {
switch (severity) {
case 'high':
return cs.error;
case 'medium':
return const Color(0xFFF59E0B);
case 'warning':
return const Color(0xFFF97316);
default:
return cs.tertiary;
}
}
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: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withValues(alpha: 0.4)),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 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.withValues(alpha: 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: TextStyle(
color: cs.onSurface,
fontSize: 13,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
alert['description'] ?? '',
style: TextStyle(
color: cs.onSurfaceVariant,
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: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
),
const SizedBox(height: 10),
Icon(Icons.arrow_forward_ios_rounded,
color: cs.onSurfaceVariant, 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({
super.key,
required this.title,
required this.value,
this.icon,
this.iconColor,
this.backgroundColor,
this.valueColor,
});
@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,270 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:get/get.dart';
import '../../../controller/admin/driver_docs_controller.dart';
import '../../widgets/elevated_btn.dart';
import '../../widgets/snackbar.dart';
import '../../../constant/links.dart';
class DriverDocsReviewPage extends StatelessWidget {
DriverDocsReviewPage({super.key});
final DriverDocsController controller = Get.put(DriverDocsController());
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
body: Column(
children: [
Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('مراجعة طلبات التسجيل'.tr, style: TextStyle(color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.w700)),
],
),
),
Expanded(
child: Obx(() => controller.isLoading.value && controller.pendingDrivers.isEmpty
? Center(child: CircularProgressIndicator(color: cs.primary))
: controller.pendingDrivers.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(shape: BoxShape.circle, color: cs.onSurfaceVariant.withValues(alpha: 0.1)),
child: Icon(Icons.how_to_reg_rounded, size: 48, color: cs.onSurfaceVariant),
),
const SizedBox(height: 16),
Text('لا يوجد طلبات تسجيل حالياً', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 14)),
],
),
)
: 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 Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator(color: cs.primary)),
);
}
final driver = controller.pendingDrivers[index];
return _buildDriverCard(context, driver);
},
),
),
)),
),
],
),
);
}
Widget _buildDriverCard(BuildContext context, dynamic driver) {
final cs = Theme.of(context).colorScheme;
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: CircleAvatar(
backgroundColor: cs.primary.withValues(alpha: 0.1),
child: Text(driver['first_name']?[0] ?? 'D', style: TextStyle(color: cs.primary)),
),
title: Text('${driver['first_name']} ${driver['last_name']}', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
subtitle: Text(driver['phone'] ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16, color: cs.onSurfaceVariant),
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(() => Builder(builder: (ctx) {
final cs = Theme.of(ctx).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
appBar: AppBar(
backgroundColor: cs.surface,
elevation: 0,
centerTitle: true,
title: Text('تفاصيل السائق', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
leading: GestureDetector(
onTap: () => Get.back(),
child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface),
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDriverHeader(driver, cs),
const SizedBox(height: 24),
Text('الوثائق المرفوعة', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)),
const SizedBox(height: 12),
...docs.map((doc) => _buildDocCard(doc, cs)),
const SizedBox(height: 32),
MyElevatedButton(
title: 'اعتماد وتفعيل الحساب',
icon: Icons.check_circle_rounded,
kolor: const Color(0xFF10B981),
onPressed: () async {
bool success = await controller.approveDriver(id);
if (success) {
Get.back();
mySnackbarSuccess('تم تفعيل حساب السائق بنجاح');
}
},
),
const SizedBox(height: 100),
],
),
),
);
}));
}
Widget _buildDriverHeader(dynamic driver, ColorScheme cs) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${driver['first_name']} ${driver['last_name']}', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)),
Text(driver['phone'] ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant)),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: cs.tertiary.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text('Pending', style: GoogleFonts.inter(color: cs.tertiary, fontSize: 12)),
),
],
),
Divider(height: 32, color: cs.outline),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildSmallInfo('الرقم الوطني', driver['national_number'] ?? 'N/A', cs),
_buildSmallInfo('الجنس', driver['gender'] ?? 'N/A', cs),
_buildSmallInfo('تاريخ الميلاد', driver['birthdate'] ?? 'N/A', cs),
],
),
],
),
);
}
Widget _buildSmallInfo(String label, String value, ColorScheme cs) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 10)),
const SizedBox(height: 2),
Text(value, style: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface)),
],
);
}
Widget _buildDocCard(dynamic doc, ColorScheme cs) {
String imageUrl = doc['link'] ?? '';
if (!imageUrl.startsWith('http')) {
imageUrl = '${AppLink.server}/upload/drivers/$imageUrl';
}
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Icon(Icons.file_present_rounded, color: cs.primary, size: 20),
const SizedBox(width: 8),
Text(doc['doc_type'] ?? 'وثيقة', style: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 14, color: cs.onSurface)),
],
),
),
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: cs.surfaceContainerHighest,
child: Center(child: Icon(Icons.broken_image_rounded, size: 48, color: cs.onSurfaceVariant)),
),
),
),
],
),
);
}
}
@@ -0,0 +1,333 @@
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 'package:siro_admin/views/widgets/snackbar.dart';
import 'package:siro_admin/constant/links.dart';
class DriverGiftCheckerController extends GetxController {
final TextEditingController phoneController = TextEditingController();
var statusLog = "".obs;
var isLoading = false.obs;
List<dynamic> driversCache = [];
Future<void> fetchDriverCache() async {
try {
final response = await CRUD().post(
link: '${AppLink.server}/Admin/driver/getDriverGiftPayment.php',
payload: {'phone': phoneController.text.trim()},
);
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) {
mySnackbarWarning("يرجى إدخال رقم الهاتف");
return;
}
await fetchDriverCache();
isLoading.value = true;
statusLog.value = "جاري البحث عن السائق...";
try {
var driver = driversCache.firstWhere(
(d) {
String dbPhone =
d['phone'].toString().replaceAll(RegExp(r'[^0-9]'), '');
String inputPhone = phoneInput.replaceAll(RegExp(r'[^0-9]'), '');
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جاري فحص رصيد الهدايا...";
statusLog.value += "\n🎁 الهدية غير موجودة. جاري الإضافة...";
await _addGiftToDriver(driverId, phoneInput, "300");
} catch (e) {
statusLog.value = "حدث خطأ غير متوقع: $e";
} finally {
isLoading.value = false;
}
}
Future<void> _addGiftToDriver(
String driverId, String phone, String amount) async {
final wallet = Get.put(WalletController());
await wallet.addDrivergift300('new driver', driverId, amount, phone);
}
}
class DriverGiftCheckPage extends StatelessWidget {
const DriverGiftCheckPage({super.key});
@override
Widget build(BuildContext context) {
final controller = Get.put(DriverGiftCheckerController());
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
body: Column(
children: [
_buildAppBar(context, cs),
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildInputCard(context, controller, cs),
const SizedBox(height: 24),
_buildLogSection(controller, cs),
],
),
),
),
],
),
);
}
Widget _buildAppBar(BuildContext context, ColorScheme cs) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
decoration: BoxDecoration(
color: cs.surface,
border: Border(bottom: BorderSide(color: cs.outline)),
),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFFF59E0B).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
),
child: Icon(Icons.card_giftcard_rounded,
color: const Color(0xFFF59E0B), size: 18),
),
const SizedBox(width: 10),
Text(
'هدايا وعروض الكباتن',
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
Widget _buildInputCard(
BuildContext context, DriverGiftCheckerController controller, ColorScheme cs) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: cs.outline),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 0.06),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFFF59E0B).withValues(alpha: 0.20),
const Color(0xFFF59E0B).withValues(alpha: 0.08),
],
),
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFFF59E0B).withValues(alpha: 0.25)),
),
child: const Icon(Icons.card_giftcard_rounded,
size: 40, color: Color(0xFFF59E0B)),
),
const SizedBox(height: 16),
Text(
"أدخل رقم الهاتف للتحقق",
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 6),
Text(
"سيتم التحقق ومنح هدية الافتتاح (300)",
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
),
),
const SizedBox(height: 20),
Container(
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: cs.outline),
),
child: TextField(
controller: controller.phoneController,
keyboardType: TextInputType.phone,
style: TextStyle(color: cs.onSurface, fontSize: 14),
cursorColor: cs.primary,
decoration: InputDecoration(
hintText: 'مثال: 0912345678',
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
prefixIcon: Icon(Icons.phone_rounded,
color: cs.onSurfaceVariant, size: 20),
border: InputBorder.none,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 48,
child: Obx(() => ElevatedButton(
onPressed: controller.isLoading.value
? null
: () => controller.processDriverGift(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF59E0B),
foregroundColor: Colors.white,
disabledBackgroundColor: cs.outline,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14)),
elevation: 0,
),
child: controller.isLoading.value
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2),
)
: const Text("تحقق ومنح الهدية",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.bold)),
)),
),
],
),
);
}
Widget _buildLogSection(DriverGiftCheckerController controller, ColorScheme cs) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(8),
),
child: Icon(Icons.terminal_rounded,
color: cs.primary, size: 14),
),
const SizedBox(width: 8),
Text(
'سجل العمليات',
style: TextStyle(
color: cs.onSurface,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF0A0A0B),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: cs.outline),
),
constraints: const BoxConstraints(minHeight: 200),
child: SingleChildScrollView(
child: Obx(() => Text(
controller.statusLog.value.isEmpty
? "بانتظار العملية..."
: controller.statusLog.value,
style: const TextStyle(
color: Color(0xFF4ADE80),
fontFamily: 'monospace',
fontSize: 12,
height: 1.6,
),
)),
),
),
],
),
);
}
}
@@ -0,0 +1,644 @@
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';
import 'package:siro_admin/views/widgets/snackbar.dart';
import 'package:siro_admin/constant/links.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(
'${AppLink.server}/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();
mySnackbarInfo("Paid status history has been reset");
}
// 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) {
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') {
mySnackbarError("Cannot pay driver with missing ID");
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
mySnackbarSuccess("Payment of $amount EGP sent to driver");
},
textCancel: 'Cancel',
onCancel: () => Get.back(),
);
}
}
@@ -0,0 +1,845 @@
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 SiroTrackerScreen extends StatefulWidget {
const SiroTrackerScreen({super.key});
@override
State<SiroTrackerScreen> createState() => _SiroTrackerScreenState();
}
class _SiroTrackerScreenState extends State<SiroTrackerScreen>
with TickerProviderStateMixin {
final MapController _mapController = MapController();
List<Marker> _markers = [];
bool isLiveMode = true;
bool isLoading = false;
String lastUpdated = "جاري التحميل...";
int liveCount = 0;
int dayCount = 0;
Timer? _timer;
late AnimationController _fadeController;
late AnimationController _scaleController;
String myPhone = box.read(BoxName.adminPhone).toString();
bool get isSuperAdmin =>
myPhone == '963942542053' || myPhone == '963992952235';
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) {
final cs = Theme.of(context).colorScheme;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: cs.surfaceContainerHighest,
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) {
final cs = Theme.of(context).colorScheme;
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, cs),
),
),
);
}
}
setState(() {
_markers = newMarkers;
});
}
Widget _buildMarkerWidget(double heading, ColorScheme cs) {
return Stack(
alignment: Alignment.center,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: isLiveMode
? [const Color(0xFF10B981), const Color(0xFF10B981)]
: [cs.tertiary, cs.tertiary],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: (isLiveMode
? const Color(0xFF10B981)
: cs.tertiary)
.withValues(alpha: 0.5),
blurRadius: 12,
spreadRadius: 2,
)
],
),
),
Transform.rotate(
angle: heading * (math.pi / 180),
child: Icon(
Icons.navigation,
color: cs.onPrimary,
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,
}) {
final cs = Theme.of(context).colorScheme;
showDialog(
context: context,
builder: (_) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [cs.surface, cs.surfaceContainerHighest],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 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(0xFF10B981), const Color(0xFF10B981)]
: [cs.tertiary, cs.tertiary],
),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.person_outline, color: cs.onPrimary, size: 20),
const SizedBox(width: 8),
Text(
"معلومات الكابتن",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: cs.onPrimary,
),
),
],
),
),
const SizedBox(height: 20),
_buildInfoCard(Icons.person, "الاسم", name, cs),
const SizedBox(height: 12),
_buildInfoCard(Icons.badge, "المعرف", driverId, cs),
const SizedBox(height: 12),
_buildInfoCard(Icons.speed, "السرعة", "$speed كم/س", cs),
const SizedBox(height: 20),
_buildStatsContainer(completed, cancelled, cs),
if (isSuperAdmin) ...[
const SizedBox(height: 16),
_buildPhoneButton(phone, cs),
],
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: cs.onSurface,
foregroundColor: cs.onPrimary,
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, ColorScheme cs) {
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: cs.outline,
width: 1,
),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 0.05),
blurRadius: 8,
)
],
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: cs.onSurface, size: 20),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
color: cs.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: cs.onSurface,
),
),
],
),
],
),
);
}
Widget _buildStatsContainer(
String completed, String cancelled, ColorScheme cs) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [cs.surfaceContainerHighest, cs.surfaceContainerHighest],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: cs.outline),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStatItem("✓ مكتملة", completed, const Color(0xFF10B981), cs),
Container(
width: 1,
height: 40,
color: cs.outline,
),
_buildStatItem("✕ ملغاة", cancelled, cs.error, cs),
],
),
);
}
Widget _buildStatItem(
String label, String value, Color color, ColorScheme cs) {
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: cs.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
],
);
}
Widget _buildPhoneButton(String phone, ColorScheme cs) {
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: [cs.tertiary, cs.tertiary],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: cs.tertiary.withValues(alpha: 0.4),
blurRadius: 12,
spreadRadius: 2,
)
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.call, color: cs.onPrimary, size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
phone,
style: TextStyle(
color: cs.onPrimary,
fontWeight: FontWeight.bold,
fontSize: 15,
),
textAlign: TextAlign.center,
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
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.siromove.admin',
),
MarkerLayer(markers: _markers),
],
),
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Row(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text('نظام تتبع الكابتن',
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold)),
],
),
),
),
_buildDashboard(cs),
],
),
);
}
Widget _buildDashboard(ColorScheme cs) {
return Positioned(
top: 100,
right: 16,
child: FadeTransition(
opacity: _fadeController,
child: ScaleTransition(
scale: _scaleController,
child: Container(
width: 300,
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: 0.15),
blurRadius: 30,
spreadRadius: 5,
)
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [cs.onSurface, cs.onSurfaceVariant],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.dashboard, color: cs.onPrimary, size: 22),
Text(
"لوحة التحكم",
style: TextStyle(
color: cs.onPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
children: [
Expanded(
child: _buildModeButton(
"أرشيف اليوم",
!isLiveMode,
() {
setState(() => isLiveMode = false);
fetchData();
},
cs.tertiary,
cs,
),
),
const SizedBox(width: 10),
Expanded(
child: _buildModeButton(
"مباشر",
isLiveMode,
() {
setState(() => isLiveMode = true);
fetchData();
},
const Color(0xFF10B981),
cs,
),
),
],
),
const SizedBox(height: 16),
_buildStatRow(
icon: Icons.live_tv,
label: "نشط الآن (مباشر)",
value: liveCount.toString(),
color: const Color(0xFF10B981),
cs: cs,
),
const SizedBox(height: 12),
_buildStatRow(
icon: Icons.history,
label: "إجمالي اليوم",
value: dayCount.toString(),
color: cs.tertiary,
cs: cs,
),
const SizedBox(height: 14),
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: cs.outline),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
isLoading
? "جاري التحديث..."
: "تحديث: $lastUpdated",
style: TextStyle(
fontSize: 11,
color: cs.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
Icon(
isLoading
? Icons.hourglass_bottom
: Icons.check_circle,
size: 14,
color: isLoading
? cs.tertiary
: const Color(0xFF10B981),
),
],
),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isLoading ? null : fetchData,
style: ElevatedButton.styleFrom(
backgroundColor: cs.onSurface,
foregroundColor: cs.onPrimary,
disabledBackgroundColor: cs.outline,
padding:
const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (isLoading)
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation<Color>(
cs.onPrimary,
),
),
)
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,
ColorScheme cs,
) {
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.withValues(alpha: 0.8)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: active ? null : cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: active ? color : cs.outline,
width: 1.5,
),
boxShadow: active
? [
BoxShadow(
color: color.withValues(alpha: 0.3),
blurRadius: 8,
spreadRadius: 1,
)
]
: null,
),
child: Text(
title,
style: TextStyle(
color: active ? cs.onPrimary : cs.onSurfaceVariant,
fontWeight: active ? FontWeight.bold : FontWeight.w600,
fontSize: 13,
),
),
),
);
}
Widget _buildStatRow({
required IconData icon,
required String label,
required String value,
required Color color,
required ColorScheme cs,
}) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withValues(alpha: 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: cs.onSurfaceVariant,
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,799 @@
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';
import 'package:siro_admin/controller/functions/crud.dart';
import 'package:siro_admin/views/widgets/snackbar.dart';
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'] ?? '',
);
}
}
String normalizePhone(String input) {
final clean = input.replaceAll(RegExp(r'\D+'), '');
if (clean.length == 10 && clean.startsWith('09')) {
return '963${clean.substring(1)}';
}
if (clean.length == 12 && clean.startsWith('963')) return clean;
if (clean.length == 9 && clean.startsWith('9')) return '963$clean';
if (clean.length == 10 && clean.startsWith('07')) {
return '962${clean.substring(1)}';
}
if (clean.length == 12 && clean.startsWith('962')) return clean;
if (clean.length == 9 && clean.startsWith('7')) return '962$clean';
if (clean.length == 11 && clean.startsWith('01')) {
return '20${clean.substring(1)}';
}
if (clean.length == 13 && clean.startsWith('20')) return clean;
return clean;
}
class RideMonitorController extends GetxController {
final String apiUrl = "${AppLink.server}/Admin/rides/monitorRide.php";
final TextEditingController phoneInputController = TextEditingController();
var isTracking = false.obs;
var isLoading = false.obs;
var hasError = false.obs;
var errorMessage = ''.obs;
var driverLocation = Rxn<DriverLocation>();
var driverName = "Unknown Driver".obs;
var rideStatus = "Waiting...".obs;
var startPoint = Rxn<LatLng>();
var endPoint = Rxn<LatLng>();
var routePolyline = <LatLng>[].obs;
final MapController mapController = MapController();
Timer? _timer;
bool _isFirstLoad = true;
@override
void onClose() {
_timer?.cancel();
phoneInputController.dispose();
super.onClose();
}
void startSearch() {
if (phoneInputController.text.trim().isEmpty) {
mySnackbarWarning("يرجى إدخال رقم الهاتف أولاً");
return;
}
hasError.value = false;
errorMessage.value = '';
driverLocation.value = null;
startPoint.value = null;
endPoint.value = null;
routePolyline.clear();
driverName.value = "جاري التحميل...";
rideStatus.value = "جاري التحميل...";
_isFirstLoad = true;
isTracking.value = true;
isLoading.value = true;
fetchRideData();
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 10), (timer) {
fetchRideData();
});
}
void stopTracking() {
_timer?.cancel();
isTracking.value = false;
isLoading.value = false;
}
Future<void> fetchRideData() async {
final phone = phoneInputController.text.trim();
if (phone.isEmpty) return;
try {
String normalizedPhone = normalizePhone(phone);
final response = await CRUD().post(
link: apiUrl,
payload: {"phone": normalizedPhone},
);
if (response != 'failure') {
final jsonResponse = response;
if ((jsonResponse['message'] != null &&
jsonResponse['message'] != 'failure') ||
jsonResponse['status'] == 'success') {
final data =
jsonResponse['message'] ?? jsonResponse['data'] ?? jsonResponse;
if (data['driver_details'] != null) {
driverName.value =
data['driver_details']['fullname'] ?? "سائق غير معروف";
}
if (data['ride_details'] != null) {
rideStatus.value = data['ride_details']['status'] ?? "غير معروف";
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];
}
}
final locData = data['driver_location'];
if (locData is Map<String, dynamic>) {
final newLocation = DriverLocation.fromJson(locData);
driverLocation.value = newLocation;
_updateMapBounds();
} else {
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;
}
}
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;
}
}
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) {
}
}
}
}
class RideMonitorScreen extends StatelessWidget {
const RideMonitorScreen({super.key});
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final RideMonitorController controller = Get.put(RideMonitorController());
return Scaffold(
backgroundColor: cs.surface,
body: Obx(() {
if (!controller.isTracking.value) {
return _buildSearchForm(context, controller, cs);
}
return _buildMapTrackingView(context, controller, cs);
}),
);
}
Widget _buildSearchForm(
BuildContext context, RideMonitorController controller, ColorScheme cs) {
return Column(
children: [
Container(
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
child: Row(
children: [
GestureDetector(
onTap: () => Get.back(),
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Icon(Icons.arrow_back_ios_new_rounded,
color: cs.onSurfaceVariant, size: 16),
),
),
const SizedBox(width: 12),
Text(
"مراقبة الرحلات",
style: TextStyle(
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
],
),
),
Expanded(
child: 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: cs.surface,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: cs.primary.withValues(alpha: 0.08),
blurRadius: 24,
offset: const Offset(0, 10),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: cs.primary.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.radar_rounded, size: 60, color: cs.primary),
),
const SizedBox(height: 24),
Text(
"تتبع رحلة نشطة",
style: TextStyle(
color: cs.onSurface,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
"أدخل رقم هاتف السائق أو الراكب للبدء",
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 14,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
Container(
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline, width: 2),
),
child: TextField(
controller: controller.phoneInputController,
keyboardType: TextInputType.phone,
textDirection: TextDirection.ltr,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w600,
),
decoration: InputDecoration(
hintText: "مثال: 0992952235...",
hintStyle: TextStyle(color: cs.onSurfaceVariant),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
vertical: 18, horizontal: 20),
prefixIcon:
Icon(Icons.phone_rounded, color: cs.primary),
),
),
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: controller.startSearch,
style: ElevatedButton.styleFrom(
backgroundColor: cs.primary,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Text(
"بدء المراقبة",
style: TextStyle(
color: cs.onPrimary,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
),
),
),
],
);
}
Widget _buildMapTrackingView(
BuildContext context, RideMonitorController controller, ColorScheme cs) {
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.siromove.admin',
),
if (controller.routePolyline.isNotEmpty)
PolylineLayer(
polylines: [
Polyline(
points: controller.routePolyline,
strokeWidth: 6.0,
color: cs.primary.withValues(alpha: 0.9),
borderStrokeWidth: 2.0,
borderColor: cs.primary.withValues(alpha: 0.3),
strokeCap: StrokeCap.round,
strokeJoin: StrokeJoin.round,
),
],
),
MarkerLayer(
markers: [
if (controller.startPoint.value != null)
Marker(
point: controller.startPoint.value!,
width: 30,
height: 30,
child: _buildPointMarker(const Color(0xFF10B981), cs),
),
if (controller.endPoint.value != null)
Marker(
point: controller.endPoint.value!,
width: 30,
height: 30,
child: _buildPointMarker(const Color(0xFFEF4444), cs),
),
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: cs.surface,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: cs.onSurface.withValues(alpha: 0.2),
blurRadius: 10,
spreadRadius: 2,
)
],
),
child: Icon(
Icons.directions_car_rounded,
color: cs.primary,
size: 28,
),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: cs.onSurface,
borderRadius: BorderRadius.circular(6),
),
child: Text(
"${controller.driverLocation.value!.speed.toInt()} كم",
style: TextStyle(
color: cs.onPrimary,
fontSize: 10,
fontWeight: FontWeight.bold,
),
textDirection: TextDirection.rtl,
),
)
],
),
),
),
],
),
],
),
Positioned(
top: MediaQuery.of(context).padding.top + 10,
right: 20,
child: Container(
decoration: BoxDecoration(
color: cs.surface,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: cs.onSurface.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
),
child: IconButton(
icon: Icon(Icons.close_rounded, color: cs.onSurface, size: 24),
onPressed: controller.stopTracking,
tooltip: "إيقاف المراقبة",
),
),
),
if (controller.isLoading.value &&
controller.driverLocation.value == null &&
controller.startPoint.value == null)
Container(
color: cs.surface.withValues(alpha: 0.8),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
color: cs.primary, strokeWidth: 3),
const SizedBox(height: 16),
Text(
"جاري تحديد الموقع...",
style: TextStyle(
color: cs.onSurface,
fontWeight: FontWeight.bold,
fontSize: 16,
),
)
],
),
),
),
if (controller.hasError.value)
Center(
child: Container(
margin: const EdgeInsets.all(24),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: cs.onSurface.withValues(alpha: 0.1),
blurRadius: 20,
offset: const Offset(0, 10),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: cs.error.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.error_outline_rounded,
color: cs.error, size: 40),
),
const SizedBox(height: 16),
Text(
"حدث خطأ",
style: TextStyle(
color: cs.onSurface,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
controller.errorMessage.value,
textAlign: TextAlign.center,
style: TextStyle(color: cs.onSurfaceVariant, height: 1.5),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: controller.stopTracking,
style: ElevatedButton.styleFrom(
backgroundColor: cs.surface,
foregroundColor: cs.onSurface,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: const Text("رجوع للبحث",
style: TextStyle(fontWeight: FontWeight.bold)),
),
)
],
),
),
),
if (!controller.hasError.value && !controller.isLoading.value)
Positioned(
bottom: 30,
left: 20,
right: 20,
child: Container(
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: cs.onSurface.withValues(alpha: 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: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(15),
),
child: Icon(Icons.person_rounded,
color: cs.primary, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.driverName.value,
style: TextStyle(
color: cs.onSurface,
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: cs.onSurfaceVariant,
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),
cs,
),
Container(
width: 1,
height: 30,
color: cs.outline.withValues(alpha: 0.2)),
_buildModernInfoBadge(
Icons.access_time_rounded,
controller.driverLocation.value!.updatedAt
.split(' ')
.last,
const Color(0xFF8B5CF6),
cs,
),
],
)
else
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
color: cs.primary, strokeWidth: 2),
),
const SizedBox(width: 10),
Text(
"جاري الاتصال بالسائق...",
style: TextStyle(
color: cs.primary,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
],
),
],
),
),
),
),
],
);
}
Widget _buildPointMarker(Color color, ColorScheme cs) {
return Container(
decoration: BoxDecoration(
color: color.withValues(alpha: 0.3),
shape: BoxShape.circle,
),
child: Center(
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(color: cs.surface, width: 2),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.5),
blurRadius: 6,
spreadRadius: 1,
)
],
),
),
),
);
}
Widget _buildModernInfoBadge(
IconData icon, String text, Color iconColor, ColorScheme cs) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: iconColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 16, color: iconColor),
),
const SizedBox(width: 8),
Text(
text,
style: TextStyle(
color: cs.onSurface,
fontSize: 14,
fontWeight: FontWeight.bold,
),
textDirection: TextDirection.ltr,
),
],
);
}
}

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