diff --git a/backend/Admin/Staff/add_super_admin.php b/backend/Admin/Staff/add_super_admin.php deleted file mode 100644 index f8d4ed53..00000000 --- a/backend/Admin/Staff/add_super_admin.php +++ /dev/null @@ -1,63 +0,0 @@ - 'Access denied. Admin key required.'])); -// } - -$con = Database::get('main'); - -$name = $_GET['name'] ?? filterRequest('name') ?: 'Super Admin'; -$email = $_GET['email'] ?? filterRequest('email') ?: ''; -$phone = $_GET['phone'] ?? filterRequest('phone') ?: ''; -$fingerprint = $_GET['fingerprint'] ?? filterRequest('fingerprint') ?: ''; -$password = $_GET['password'] ?? filterRequest('password') ?: bin2hex(random_bytes(8)); - -try { - $hashedPass = password_hash($password, PASSWORD_DEFAULT); - $encName = $encryptionHelper->encryptData($name); - $encPhone = $phone ? $encryptionHelper->encryptData($phone) : ''; - $encEmail = $email ? $encryptionHelper->encryptData($email) : ''; - $encFp = $fingerprint ? $encryptionHelper->encryptData($fingerprint) : ''; - $fpHash = $fingerprint ? hash('sha256', $fingerprint) : ''; - $uniqueId = bin2hex(random_bytes(16)); - - $check = $con->prepare("SELECT id FROM adminUser WHERE role = 'super_admin' LIMIT 1"); - $check->execute(); - if ($check->fetch()) { - echo "

⚠️ Super Admin already exists.

"; - exit; - } - - $sql = "INSERT INTO adminUser (id, fingerprint, fingerprint_hash, name, phone, email, password, role, created_at) - VALUES (:id, :fp, :fp_hash, :name, :phone, :email, :pass, 'super_admin', NOW())"; - $stmt = $con->prepare($sql); - $stmt->execute([ - ':id' => $uniqueId, - ':fp' => $encFp, - ':fp_hash' => $fpHash, - ':name' => $encName, - ':phone' => $encPhone, - ':email' => $encEmail, - ':pass' => $hashedPass, - ]); - - if ($stmt->rowCount() > 0) { - echo "

✅ Super Admin created successfully!

"; - echo "

ID: $uniqueId

"; - echo "

Name: $name

"; - echo "

Password: $password

"; - echo "

⚠️ Save this password. Delete this file after use.

"; - } else { - echo "

❌ Failed to create Super Admin.

"; - } -} catch (Exception $e) { - echo "

❌ Error: " . htmlspecialchars($e->getMessage()) . "

"; -} diff --git a/backend/Admin/adminUser/add_invoice.php b/backend/Admin/adminUser/add_invoice.php index 3e140a11..fd582d3c 100644 --- a/backend/Admin/adminUser/add_invoice.php +++ b/backend/Admin/adminUser/add_invoice.php @@ -7,7 +7,6 @@ error_reporting(E_ALL); require_once __DIR__ . '/../../connect.php'; -$driverID = filterRequest("driverID"); $invoiceNumber = filterRequest("invoiceNumber"); $amount = filterRequest("amount"); $date = filterRequest("date"); @@ -17,7 +16,7 @@ $linkImage = null; $uploadDate = date("Y-m-d H:i:s"); // ✅ طباعة بيانات الإدخال للتأكد -error_log("[add_invoice.php] 📥 Data received | driverID: $driverID, invoiceNumber: $invoiceNumber, amount: $amount, date: $date"); +error_log("[add_invoice.php] 📥 Data received | invoiceNumber: $invoiceNumber, amount: $amount, date: $date"); // التحقق من وجود ملف الصورة if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { @@ -43,7 +42,7 @@ if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { exit; } - $new_filename = $invoiceNumber . "_" . $driverID . '.' . $image_extension; + $new_filename = $invoiceNumber . '.' . $image_extension; $target_dir = "invoice_images/"; $target_file = $target_dir . $new_filename; @@ -66,9 +65,9 @@ if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) { } try { - $stmt = $con->prepare("INSERT INTO invoice_records (driverID, invoice_number,name, amount, date, image_link, created_at) - VALUES (?, ?, ?,?, ?, ?, ?)"); - $stmt->execute([$driverID, $invoiceNumber,$name, $amount, $date, $linkImage, $uploadDate]); + $stmt = $con->prepare("INSERT INTO invoice_records (invoice_number, name, amount, date, image_link, created_at) + VALUES (?, ?, ?, ?, ?, ?)"); + $stmt->execute([$invoiceNumber, $name, $amount, $date, $linkImage, $uploadDate]); echo json_encode([ 'status' => 'success', diff --git a/backend/Admin/auth/migrate_db.php b/backend/Admin/auth/migrate_db.php deleted file mode 100644 index f3bb1766..00000000 --- a/backend/Admin/auth/migrate_db.php +++ /dev/null @@ -1,28 +0,0 @@ -query("SHOW COLUMNS FROM adminUser LIKE 'status'"); - if ($check->rowCount() == 0) { - $sql = "ALTER TABLE adminUser - ADD COLUMN status ENUM('pending', 'approved', 'suspended', 'rejected') NOT NULL DEFAULT 'pending' AFTER role, - ADD COLUMN phone VARCHAR(50) DEFAULT NULL AFTER name, - ADD COLUMN email VARCHAR(255) DEFAULT NULL AFTER phone, - ADD COLUMN approved_by VARCHAR(64) DEFAULT NULL AFTER status, - ADD COLUMN approved_at DATETIME DEFAULT NULL AFTER approved_by"; - - $con->exec($sql); - - // Update existing admins to approved and super_admin - $con->exec("UPDATE adminUser SET status = 'approved', role = 'super_admin' WHERE id IS NOT NULL"); - - echo json_encode(["status" => "success", "message" => "Migration completed successfully."]); - } else { - echo json_encode(["status" => "success", "message" => "Columns already exist."]); - } -} catch (Exception $e) { - echo json_encode(["status" => "error", "message" => "An internal error occurred"]); -} diff --git a/backend/Admin/auth/migration_cryptography.php b/backend/Admin/auth/migration_cryptography.php deleted file mode 100644 index 2c4d5b8a..00000000 --- a/backend/Admin/auth/migration_cryptography.php +++ /dev/null @@ -1,128 +0,0 @@ - [ - 'phone', 'email', 'gender', 'birthdate', 'site', - 'first_name', 'last_name', 'accountBank', 'education', - 'employmentType', 'maritalStatus', 'national_number', - 'name_arabic', 'address' - ], - 'passengers' => [ - 'phone', 'email', 'gender', 'birthdate', - 'first_name', 'last_name', 'token' - ], - 'CarRegistration' => [ - 'vin', 'car_plate', 'owner', 'address' - ], - 'carPlateEdit' => [ - 'carPlate', 'owner' - ], - 'phone_verification' => [ - 'phone_number' - ], - 'phone_verification_passenger' => [ - 'phone_number' - ], - 'driverToken' => [ - 'token' - ], - 'passengerToken' => [ - 'token' - ], - 'mishwari' => [ - 'phone', 'gender', 'name', 'name_english', 'car_plate', 'token', 'education', 'national_number', 'age' - ], - 'rate_app' => [ - 'email', 'phone' - ], - 'admins' => [ - 'name', 'phone', 'email', 'fp' - ], - 'driver_assurance' => [ - 'assured', 'health_insurance_provider' - ], - 'blacklist_drivers' => [ - 'phone' - ], - 'blacklist_passengers' => [ - 'phone' - ], - 'feedBack' => [ - 'feedBack' - ] -]; - -$totalUpdated = 0; - -foreach ($tables as $table => $columns) { - echo "Processing table: $table ...\n"; - ob_flush(); flush(); - - try { - $sql = "SELECT `id`, `" . implode("`, `", $columns) . "` FROM `$table`"; - $stmt = $con->query($sql); - if (!$stmt) { - echo "Skipped $table (Not found or missing columns).\n"; - continue; - } - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - } catch (Exception $e) { - echo "An internal error occurred" . "\n"; - continue; - } - - $tableUpdatedCount = 0; - - foreach ($rows as $row) { - $id = $row['id']; - $needsUpdate = false; - $updateValues = []; - $params = [':id' => $id]; - - foreach ($columns as $col) { - $value = $row[$col]; - - // تحقق إذا كان الحقل يحتوي على قيمة وإذا لم يكن مشفر بالنظام الجديد - if (!empty($value) && strpos($value, 'GCM:') !== 0) { - // محاولة فك التشفير القديم (CBC) - try { - $decrypted = $encryptionHelper->decryptData($value); - if ($decrypted !== false && $decrypted !== '') { - // إعادة التشفير (سيستخدم GCM الآن) - $newEncrypted = $encryptionHelper->encryptData($decrypted); - $updateValues[] = "`$col` = :$col"; - $params[":$col"] = $newEncrypted; - $needsUpdate = true; - } - } catch (Exception $e) { - error_log("Failed to migrate $col for ID $id in $table: " . $e->getMessage()); - } - } - } - - if ($needsUpdate) { - $setClause = implode(", ", $updateValues); - $updateSql = "UPDATE `$table` SET $setClause WHERE `id` = :id"; - $updateStmt = $con->prepare($updateSql); - $updateStmt->execute($params); - $tableUpdatedCount++; - } - } - - echo "Finished $table. Updated rows: $tableUpdatedCount\n"; - $totalUpdated += $tableUpdatedCount; - ob_flush(); flush(); -} - -echo "Migration completed! Total rows updated: $totalUpdated\n"; -?> diff --git a/backend/core/Services/SiroGeminiService.php b/backend/core/Services/SiroGeminiService.php index b90245ae..0c7384c8 100644 --- a/backend/core/Services/SiroGeminiService.php +++ b/backend/core/Services/SiroGeminiService.php @@ -30,7 +30,7 @@ class SiroGeminiService { float $siroBasePrice, string $regionName, string $countryCode, - string $model = 'gemini-1.5-flash' + string $model = 'gemini-flash-lite-latest' ): ?array { if (!$this->apiKey) { error_log("[SiroGeminiService] API Key is missing."); diff --git a/backend/diagnose_fingerprint.php b/backend/diagnose_fingerprint.php deleted file mode 100644 index 5592a904..00000000 --- a/backend/diagnose_fingerprint.php +++ /dev/null @@ -1,38 +0,0 @@ -getMessage() . "\n"; - exit; -} - -$targetId = 'e494c5750f95e1c26654'; -echo "Target Passenger ID: " . $targetId . "\n\n"; - -try { - $stmt = $con->prepare("SELECT * FROM tokens WHERE passengerID = ? LIMIT 1"); - $stmt->execute([$targetId]); - $row = $stmt->fetch(PDO::FETCH_ASSOC); - if ($row) { - echo "✅ Token row found:\n"; - echo " - Passenger ID: " . $row['passengerID'] . "\n"; - echo " - Token (encrypted/raw): " . $row['token'] . "\n"; - echo " - Fingerprint (stored): " . $row['fingerprint'] . "\n"; - - $decryptedToken = $encryptionHelper->decryptData($row['token']); - echo " - Decrypted Token: " . $decryptedToken . "\n"; - - $fpPepper = getenv('FP_PEPPER') ?: ''; - echo " - FP_PEPPER: " . ($fpPepper ? "Set" : "Not Set") . "\n"; - } else { - echo "❌ No token row found for passenger!\n"; - } -} catch (Exception $e) { - echo "❌ Error: " . $e->getMessage() . "\n"; -} diff --git a/backend/diagnose_login.php b/backend/diagnose_login.php deleted file mode 100644 index e7fa425a..00000000 --- a/backend/diagnose_login.php +++ /dev/null @@ -1,94 +0,0 @@ -getMessage() . "\n"; - exit; -} - -$targetId = 'e494c5750f95e1c26654'; -echo "Target Passenger ID: " . $targetId . "\n\n"; - -// 1. Check passengers table -try { - $stmt = $con->prepare("SELECT * FROM passengers WHERE id = ?"); - $stmt->execute([$targetId]); - $p = $stmt->fetch(PDO::FETCH_ASSOC); - if ($p) { - echo "✅ Passenger found in database:\n"; - echo " - Phone (encrypted): " . $p['phone'] . "\n"; - echo " - Phone (decrypted): " . $encryptionHelper->decryptData($p['phone']) . "\n"; - echo " - First Name (decrypted): " . $encryptionHelper->decryptData($p['first_name']) . "\n"; - echo " - Last Name (decrypted): " . $encryptionHelper->decryptData($p['last_name']) . "\n"; - } else { - echo "❌ Passenger NOT found in database!\n"; - } -} catch (Exception $e) { - echo "❌ Error querying passengers table: " . $e->getMessage() . "\n"; -} - -// 2. Check phone_verification_passenger table -try { - $decryptedPhone = '962798583052'; - $encryptedPhone = $encryptionHelper->encryptData($decryptedPhone); - echo "\nSearching phone_verification_passenger for: $decryptedPhone ($encryptedPhone)\n"; - - $stmt = $con->prepare("SELECT * FROM phone_verification_passenger WHERE phone_number = ?"); - $stmt->execute([$encryptedPhone]); - $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); - if (!empty($rows)) { - echo "✅ Phone verification rows found (" . count($rows) . "):\n"; - foreach ($rows as $row) { - echo " - ID: " . $row['id'] . " | Verified: " . $row['verified'] . " | Expiration: " . $row['expiration_time'] . " | Created At: " . $row['created_at'] . "\n"; - } - } else { - echo "❌ No phone verification rows found for this phone number!\n"; - - // Let's get any recent rows - $stmt2 = $con->prepare("SELECT * FROM phone_verification_passenger ORDER BY id DESC LIMIT 5"); - $stmt2->execute(); - $all = $stmt2->fetchAll(PDO::FETCH_ASSOC); - echo " Recent rows in table:\n"; - foreach ($all as $row) { - $dec = $encryptionHelper->decryptData($row['phone_number']); - echo " - ID: " . $row['id'] . " | Phone (decrypted): $dec | Verified: " . $row['verified'] . "\n"; - } - } -} catch (Exception $e) { - echo "❌ Error querying phone_verification_passenger: " . $e->getMessage() . "\n"; -} - -// 3. Test the exact SQL query from loginFromGooglePassenger.php -echo "\n--- 🧪 Testing loginFromGooglePassenger query (WITH verified = 1 constraint) ---\n"; -try { - $sqlOld = "SELECT p.`id` FROM passengers p - LEFT JOIN phone_verification_passenger ON phone_verification_passenger.phone_number = p.phone - WHERE p.id = :id AND phone_verification_passenger.verified = '1'"; - - $stmt = $con->prepare($sqlOld); - $stmt->execute([':id' => $targetId]); - $count = $stmt->rowCount(); - echo "Old query row count: $count\n"; -} catch (Exception $e) { - echo "Old query error: " . $e->getMessage() . "\n"; -} - -echo "\n--- 🧪 Testing loginFromGooglePassenger query (WITHOUT verified = 1 constraint) ---\n"; -try { - $sqlNew = "SELECT p.`id` FROM passengers p - LEFT JOIN phone_verification_passenger ON phone_verification_passenger.phone_number = p.phone - WHERE p.id = :id"; - - $stmt = $con->prepare($sqlNew); - $stmt->execute([':id' => $targetId]); - $count = $stmt->rowCount(); - echo "New query row count: $count\n"; -} catch (Exception $e) { - echo "New query error: " . $e->getMessage() . "\n"; -} diff --git a/backend/migrate_driver_passwords.php b/backend/migrate_driver_passwords.php deleted file mode 100644 index 2c4f2528..00000000 --- a/backend/migrate_driver_passwords.php +++ /dev/null @@ -1,128 +0,0 @@ - false, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8", - ]; - $pdo = new PDO($dsn, $dbUser, $dbPass, $options); - - // نجلب الحقول التي نحتاجها لبناء السر - $sql = "SELECT id, phone, birthdate, national_number FROM driver"; - $stmt = $pdo->query($sql); - - $update = $pdo->prepare("UPDATE driver SET password = :pwd WHERE id = :id"); - - $count = 0; - $skipped = 0; - $startTime = microtime(true); - - while ($row = $stmt->fetch()) { - $id = $row['id']; - $encPhone = $row['phone']; - $encBirth = $row['birthdate'] ?? null; - $encNat = $row['national_number'] ?? null; - - // نفك التشفير – قد يرجع null لو الحقل فاضي - $phone = $encPhone ? $encryptionHelper->decryptData($encPhone) : null; - $birth = $encBirth ? $encryptionHelper->decryptData($encBirth) : null; - $nat = $encNat ? $encryptionHelper->decryptData($encNat) : null; - - if (empty($id) || empty($phone)) { - // لو ناقصين، نتجاوز السطر مع تسجيل في اللوج - error_log("[MIGRATE] Skip driver id={$id}: missing phone or id."); - $skipped++; - continue; - } - - // في الوضع المثالي عندك nat + birthdate لكل السائقين - // لو حاب تجبرهم يكونوا موجودين: - /* - if (empty($nat) || empty($birth)) { - error_log("[MIGRATE] Skip driver id={$id}: missing nat or birthdate."); - $skipped++; - continue; - } - */ - - // phone مفروض يكون أصلاً مطبّع (9639...) من سكربت التسجيل - $normalizedPhone = trim($phone); - - // نبني baseString: الأساس id + phone - $parts = [$id, $normalizedPhone]; - - // نضيف رقم وطني أو سنة الميلاد (حسب الموجود) - if (!empty($nat)) { - $parts[] = trim($nat); - } elseif (!empty($birth)) { - // birthdate متوقعة بصيغة YYYY-01-01 -> نأخذ السنة فقط - $year = substr($birth, 0, 4); - if (preg_match('/^\d{4}$/', $year)) { - $parts[] = $year; - } - } - - $baseString = implode('|', $parts); - - // اشتقاق السر النهائي (HEX string، بدون باينري) - $hmacHex = hash_hmac('sha256', $baseString, $pepper, false); - - // نخزن فقط الهاش باستخدام password_hash - $pwdHash = password_hash($hmacHex, PASSWORD_DEFAULT); - - $update->execute([ - ':pwd' => $pwdHash, - ':id' => $id, - ]); - - $count++; - - // لوج بسيط كل 100 سائق - if ($count % 100 === 0) { - $elapsed = round(microtime(true) - $startTime, 2); - error_log("[MIGRATE] Progress: updated {$count} drivers, skipped {$skipped}, elapsed {$elapsed}s"); - } - } - - $totalTime = round(microtime(true) - $startTime, 2); - error_log("[MIGRATE] Done. Updated {$count} driver passwords, skipped {$skipped}. Total time: {$totalTime}s"); - echo "Migration finished. Updated {$count} drivers, skipped {$skipped}. Time: {$totalTime}s\n"; - -} catch (PDOException $e) { - error_log("[MIGRATE][PDO] " . $e->getMessage()); - echo "Migration failed (DB error).\n"; - exit(1); -} catch (Exception $e) { - error_log("[MIGRATE][GENERAL] " . $e->getMessage()); - echo "Migration failed (general error).\n"; - exit(1); -} \ No newline at end of file diff --git a/backend/migration_create_table.php b/backend/migration_create_table.php deleted file mode 100644 index 09456f24..00000000 --- a/backend/migration_create_table.php +++ /dev/null @@ -1,21 +0,0 @@ -exec($sql); - echo "SUCCESS: passenger_opening_locations table created successfully.\n"; -} catch (Exception $e) { - echo "An internal error occurred" . "\n"; -} -?> diff --git a/backend/test_add_driver_and_car.php b/backend/test_add_driver_and_car.php deleted file mode 100644 index 0636d76c..00000000 --- a/backend/test_add_driver_and_car.php +++ /dev/null @@ -1,210 +0,0 @@ -encryptData($phone); - $encEmail = $encryptionHelper->encryptData($email); - $encFirstName = $encryptionHelper->encryptData($first_name); - $encLastName = $encryptionHelper->encryptData($last_name); - $encNameArabic = $encryptionHelper->encryptData("$first_name $last_name"); - $encGender = $encryptionHelper->encryptData($gender); - $encNationalNumber = $national_number ? $encryptionHelper->encryptData($national_number) : ''; - $encBirthdate = $encryptionHelper->encryptData($birthdate); - $encSite = $encryptionHelper->encryptData($site); - $encOwner = $encryptionHelper->encryptData($owner); - $encCarPlate = $encryptionHelper->encryptData($car_plate); - $encVin = $encryptionHelper->encryptData($vin); - - $passwordHashed = password_hash($password, PASSWORD_DEFAULT); - - $con = Database::get('main'); - - /* ================== التحقق من التكرار ================== */ - $dup = $con->prepare("SELECT id FROM driver WHERE phone = :p OR email = :e"); - $dup->execute([':p' => $encPhone, ':e' => $encEmail]); - if ($dup->rowCount() > 0) { - jsonError("Phone or email already registered."); - exit; - } - - $con->beginTransaction(); - - /* ================== 1) إدراج السائق ================== */ - $sqlDriver = " - INSERT INTO driver ( - id, phone, email, password, gender, license_type, national_number, - name_arabic, issue_date, expiry_date, license_categories, - address, licenseIssueDate, status, birthdate, site, - first_name, last_name, accountBank, bankCode, - employmentType, maritalStatus, fullNameMaritial, expirationDate, - created_at, updated_at - ) VALUES ( - :id, :phone, :email, :pwd, :gender, :license_type, :national_number, - :name_arabic, :issue_date, :expiry_date, :license_categories, - :address, :licenseIssueDate, :status, :birthdate, :site, - :first_name, :last_name, :accountBank, :bankCode, - :employmentType, :maritalStatus, :fullNameMaritial, :expirationDate, - NOW(), NOW() - ) - "; - $insD = $con->prepare($sqlDriver); - $insD->execute([ - ':id' => $driverId, - ':phone' => $encPhone, - ':email' => $encEmail, - ':pwd' => $passwordHashed, - ':gender' => $encGender, - ':license_type' => $license_type, - ':national_number' => $encNationalNumber, - ':name_arabic' => $encNameArabic, - ':issue_date' => '2020-01-01', - ':expiry_date' => '2030-01-01', - ':license_categories' => 'B', - ':address' => $encSite, - ':licenseIssueDate' => '2020-01-01', - ':status' => 'pending_review', - ':birthdate' => $encBirthdate, - ':site' => $encSite, - ':first_name' => $encFirstName, - ':last_name' => $encLastName, - ':accountBank' => 'yet', - ':bankCode' => 'CIB', - ':employmentType' => $employmentType, - ':maritalStatus' => 'Single', - ':fullNameMaritial' => '', - ':expirationDate' => date('Y-m-d', strtotime('+5 years')), - ]); - - /* ================== 2) إدراج السيارة ================== */ - $sqlCar = " - INSERT INTO CarRegistration ( - driverID, vin, car_plate, make, model, year, expiration_date, - color, owner, color_hex, fuel, - vehicle_category_id, fuel_type_id, - isDefault, created_at, status - ) VALUES ( - :driverID, :vin, :car_plate, :make, :model, :year, :expiration_date, - :color, :owner, :color_hex, :fuel, - :vehicle_category_id, :fuel_type_id, - :isDefault, NOW(), 'active' - ) - "; - $insC = $con->prepare($sqlCar); - $insC->execute([ - ':driverID' => $driverId, - ':vin' => $encVin, - ':car_plate' => $encCarPlate, - ':make' => $make, - ':model' => $model, - ':year' => $year, - ':expiration_date' => $expiration_date, - ':color' => $color, - ':owner' => $encOwner, - ':color_hex' => $color_hex, - ':fuel' => $fuel, - ':vehicle_category_id' => 1, - ':fuel_type_id' => 1, - ':isDefault' => 1, - ]); - - $carRegID = $con->lastInsertId(); - - /* ================== 3) توكن السائق ================== */ - $token = bin2hex(random_bytes(20)); - $sqlToken = " - INSERT INTO driverToken (token, captain_id, fingerPrint, created_at) - VALUES (:token, :captain_id, :fingerPrint, NOW()) - "; - $con->prepare($sqlToken)->execute([ - ':token' => $token, - ':captain_id' => $driverId, - ':fingerPrint' => 'test_fingerprint', - ]); - - /* ================== 4) توثيق رقم الهاتف ================== */ - $sqlPhoneVer = " - INSERT INTO phone_verification (phone_number, driverId, email, token_code, expiration_time, is_verified, created_at) - VALUES (:phone, :driverId, :email, :token_code, DATE_ADD(NOW(), INTERVAL 1 YEAR), 1, NOW()) - "; - $con->prepare($sqlPhoneVer)->execute([ - ':phone' => $encPhone, - ':driverId' => $driverId, - ':email' => $encEmail, - ':token_code' => $encryptionHelper->encryptData('999'), - ]); - - /* ================== Commit ================== */ - $con->commit(); - - printSuccess([ - 'driverID' => $driverId, - 'carRegID' => $carRegID, - 'status' => 'success', - 'message' => "Driver $first_name $last_name created successfully with status pending_review.", - ]); - -} catch (Exception $e) { - if (isset($con) && $con instanceof PDO && $con->inTransaction()) { - $con->rollBack(); - } - error_log("[test_add_driver] " . $e->getMessage()); - jsonError($e->getMessage()); -} diff --git a/backend/test_signed_pricing.php b/backend/test_signed_pricing.php deleted file mode 100644 index 9154c101..00000000 --- a/backend/test_signed_pricing.php +++ /dev/null @@ -1,94 +0,0 @@ - diff --git a/docs/دراسة_نظام_أتمتة_السوق_الذكي.md b/docs/دراسة_نظام_أتمتة_السوق_الذكي.md new file mode 100644 index 00000000..0875bc11 --- /dev/null +++ b/docs/دراسة_نظام_أتمتة_السوق_الذكي.md @@ -0,0 +1,704 @@ +# دراسة نظام أتمتة السوق الذكي - Siro + +**التاريخ: 26 يونيو 2026** +**إعداد: فريق التطوير** + +--- + +## فهرس المحتويات + +1. [توحيد السناك بار (Snackbar System)](#1-توحيد-السناك-بار-snackbar-system) +2. [نظرة عامة على نظام أتمتة السوق الذكي](#2-نظرة-عامة-على-نظام-أتمتة-السوق-الذكي) +3. [مكونات النظام بالتفصيل](#3-مكونات-النظام-بالتفصيل) +4. [تدفق البيانات (Data Flow)](#4-تدفق-البيانات-data-flow) +5. [قاعدة البيانات وجداولها](#5-قاعدة-البيانات-وجدولها) +6. [خريطة مفاتيح Redis](#6-خريطة-مفاتيح-redis) +7. [الخدمات الخارجية المستخدمة](#7-الخدمات-الخارجية-المستخدمة) +8. [ملفات الخلفية - مراجعة أمنية شاملة](#8-ملفات-الخلفية---مراجعة-أمنية-شاملة) +9. [قائمة الملفات المطلوب حذفها فوراً](#9-قائمة-الملفات-المطلوب-حذفها-فوراً) +10. [ثغرات SQL Injection](#10-ثغرات-sql-injection) +11. [التوصيات النهائية](#11-التوصيات-النهائية) + +--- + +## 1. توحيد السناك بار (Snackbar System) + +### الوضع الحالي - 4 تطبيقات و 4 طرق مختلفة + +يوجد حاليًا 4 تطبيقات Flutter ولكل منها نظام سناك بار مختلف تمامًا: + +--- + +### 1.1 siro_admin - الإصدار القديم (GetX) + +**الملف:** `siro_admin/lib/views/widgets/snackbar.dart` + +- يستخدم `Get.snackbar()` حصريًا +- يوجد دالتان فقط: `mySnackeBarError()` و `mySnackbarSuccess()` +- **لا يوجد** `mySnackbarWarning()` ولا `mySnackbarInfo()` +- الألوان: أحمر للخطأ (`AppColor.redColor`)، أخضر للنجاح (`AppColor.greenColor`) +- يستخدم `SnackbarConfig` للثوابت (مدة 3 ثوان، زوايا 12، ظل) +- التوقيع: `SnackbarController mySnackeBarError(String message)` +- النصوص بالإنكليزية: `'Error'.tr`, `'Success'.tr` +- 30+ استخدامًا في التطبيق + +### 1.2 siro_service - الإصدار القديم (GetX) + +**الملف:** `siro_service/lib/views/widgets/mycircular.dart` + +- يستخدم `Get.snackbar()` حصريًا +- يوجد 3 دوال: `mySnackbarError()` و `mySnackbarWarning()` و `mySnackbarSuccess()` +- الألوان: أحمر (`AppColor.redColor`)، أصفر (`AppColor.yellowColor` + نص أسود)، أخضر (`AppColor.greenColor`) +- نفس `SnackbarConfig` للثوابت +- 70+ استخدامًا في التطبيق + +### 1.3 siro_driver - الإصدار الحديث (Custom Widget) + +**الملف:** `siro_driver/lib/views/widgets/error_snakbar.dart` + +- ويج محسّن مخصص مع `_SnackContent` و `AnimationController` +- يوجد 4 دوال: `mySnackbarSuccess()` و `mySnackeBarError()` و `mySnackbarInfo()` و `mySnackbarWarning()` +- **4 متغيرات (variants):** success, error, info, warning +- ألوان: أخضر (`#1A9E5C`)، أحمر (`#D93025`)، أزرق (`#1A73E8`)، برتقالي (`#F29900`) +- ألوان السطح: `#F0FBF5`, `#FEF2F1`, `#F0F6FF`, `#FFF8E6` +- تأثيرات: `ScaleTransition` مع `Curves.elasticOut`، شريط تقدم تنازلي +- إخفاء يدوي مع زر Close + Haptic Feedback +- يتحقق أولاً من `Overlay`، فإن لم يجده يستخدم `ScaffoldMessenger` +- **مشكلة:** يستخدم `Get.snackbar()` الذي يرمي `FlutterError` إذا لم يكن `Overlay` جاهزًا +- 100+ استخدامًا في التطبيق + +### 1.4 siro_rider - الإصدار الحديث المحسّن (ScaffoldMessenger) + +**الملف:** `siro_rider/lib/views/widgets/error_snakbar.dart` + +- نفس تصميم `siro_driver` مع تحسينات جوهرية +- **يستخدم فقط `ScaffoldMessenger`** بدلاً من `Get.snackbar()` (لتجنب أخطاء `Overlay`) +- يحتوي على آلية إعادة محاولة (retry) تصل إلى 3 مرات +- `messenger.clearSnackBars()` قبل العرض (يمنع التراكم) +- يعيد `SnackbarController?` (nullable) لأن `Get.snackbar` لم يعد مستخدمًا +- التوقيع: `SnackbarController? mySnackbarSuccess(String message)` - ملاحظة: الرجوع `?` +- 100+ استخدامًا في التطبيق + +### 1.5 دوال Toast البسيطة + +- **siro_driver** و **siro_rider** لديهما `lib/controller/functions/toast.dart` +- `Toast.show(BuildContext context, String message, Color color)` - دالة بسيطة جدًا +- تستخدم `ScaffoldMessenger.of(context).showSnackBar()` مع `SnackBar` مادة +- 10+ استخدامات في كل تطبيق + +--- + +### الفروقات بين الإصدارين (القديم والحديث) + +| الخاصية | siro_admin (قديم) | siro_driver (حديث) | siro_rider (حديث) | siro_service (قديم) | +|---------|:-----------------:|:------------------:|:-----------------:|:-------------------:| +| عدد الدوال | 2 | 4 | 4 | 3 | +| `mySnackbarWarning` | ❌ | ✅ | ✅ | ✅ | +| `mySnackbarInfo` | ❌ | ✅ | ✅ | ❌ | +| `mySnackbarSuccess` | ✅ | ✅ | ✅ | ✅ | +| `mySnackeBarError` | ✅ | ✅ | ✅ | ✅ | +| آلية العرض | `Get.snackbar()` | `Get.snackbar()` + `ScaffoldMessenger` | `ScaffoldMessenger` فقط | `Get.snackbar()` | +| التصميم | نص فقط | أيقونة + نص + زر إغلاق + شريط تقدم | أيقونة + نص + زر إغلاق + شريط تقدم | نص فقط | +| Retry | ❌ | ❌ | ✅ (3 مرات) | ❌ | +| Null Safety | `SnackbarController` | `SnackbarController` | `SnackbarController?` | `SnackbarController` | +| أخطاء `Overlay` | ✅ (GetX آمن) | ⚠️ (قد يحدث) | ✅ (متفادي) | ✅ (GetX آمن) | + +--- + +### خطة التوحيد المقترحة + +#### الهدف: إنشاء package مشترك واحد لجميع التطبيقات الأربعة + +**الخطوة 1:** إنشاء مجلد مشترك (shared package) في المسار: +``` +siro_admin/lib/shared/widgets/snackbar/ +``` + +**الخطوة 2:** توحيد الواجهة (API) لتصبح: + +```dart +// الاستخدام الموحد - نفس التوقيع في كل التطبيقات +void mySnackbarSuccess(String message); +void mySnackbarError(String message); +void mySnackbarWarning(String message); +void mySnackbarInfo(String message); +``` + +**الخطوة 3:** الاعتماد على `ScaffoldMessenger` فقط (مثل siro_rider) لتجنب مشاكل `Overlay` في GetX. + +**الخطوة 4:** توحيد الألوان والثوابت: + +| المتغير | اللون الأساسي | لون السطح | الأيقونة | +|---------|:------------:|:---------:|:--------:| +| success | `#1A9E5C` (أخضر) | `#F0FBF5` | `check_circle_rounded` | +| error | `#D93025` (أحمر) | `#FEF2F1` | `error_rounded` | +| info | `#1A73E8` (أزرق) | `#F0F6FF` | `info_rounded` | +| warning | `#F29900` (برتقالي) | `#FFF8E6` | `warning_amber_rounded` | + +**الخطوة 5:** تحويل ملفات `toast.dart` في `siro_driver` و `siro_rider` لاستخدام دالة واحدة موحدة بدلاً من التكرار. + +**الخطوة 6:** إزالة دوال `Get.snackbar()` المباشرة من جميع ملفات التحكم (controllers) والاستعاضة عنها بالدوال الموحدة. + +--- + +## 2. نظرة عامة على نظام أتمتة السوق الذكي + +### ما هو النظام؟ + +**"أتمتة السوق الذكي"** هو نظام متكامل لذكاء السوق والتسعير الديناميكي في Siro. يقوم النظام بـ: + +1. **جمع بيانات المنافسين** تلقائيًا عبر Android Bot +2. **تحليل الفجوات السعرية** بين Siro والمنافسين (YallaGo, Zaken, Tufaddal) +3. **تعديل أسعار Siro تلقائيًا** بناءً على تحليل السوق +4. **كشف فرص رفع الأسعار** (Surge Opportunities) في المناطق التي يرتفع فيها الطلب +5. **توليد حملات تسويقية ذكية** باستخدام AI (Google Gemini) +6. **تقديم تقارير أسبوعية** عن صحة السوق (Market Health Reports) +7. **محاكاة "What-If"** لمعرفة أثر تغيير الأسعار + +### طبقات النظام + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ طبقة العرض (Admin Dashboard) │ +│ siro_admin → لوحة تحكم المدير → Flutter Web │ +│ endpoints: Admin/marketing/*.php │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ طبقة API (PHP Backend) │ +│ cron_jobs → bot/*.php │ pricing → ride/pricing/*.php │ +│ heatmap → ride/heatmap/*.php │ marketing → Admin/marketing/*.php │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ طبقة الذكاء والتحليل │ +│ Google Gemini AI │ SiroGeminiService │ Redis Analytics │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ طبقة جمع البيانات │ +│ Android Bot ←→ worker.php ←→ competitor_prices (MySQL) │ +│ generate_price_tasks.php (cron) → Redis Queue → Bot │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ قاعدة البيانات والذاكرة المؤقتة │ +│ MySQL (main, tracking, ride) │ Redis (مفاتيح surge, demand) │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### الدول المدعومة + +| الدولة | رمز البلد | المنطقة الزمنية | عملة | +|:------:|:--------:|:--------------:|:----:| +| سوريا | SY | Asia/Damascus | ل.س | +| الأردن | JO | Asia/Amman | د.أ | +| مصر | EG | Africa/Cairo | ج.م | +| العراق | IQ | Asia/Baghdad | د.ع | + +--- + +## 3. مكونات النظام بالتفصيل + +### 3.1 نظام البوت (Bot System) - `backend/bot/` + +#### 3.1.1 `generate_price_tasks.php` +- **الجدولة:** كل 15 دقيقة +- **الوظيفة:** يولد مهام فحص أسعار المنافسين ويدفعها إلى Redis Queue +- **آلية العمل:** + - ينشئ جدول `competitor_prices` تلقائيًا إذا لم يكن موجودًا + - يحتوي على 10 مناطق رئيسية في دمشق (ساحة الأمويين، المزة، المالكي، كفرسوسة، الميدان، باب توما، ركن الدين، دمر، برامكة، المهاجرين) + - المنافسون: `['yallago', 'zaken', 'tufaddal']` + - لكل منطقة يولد نقطة انطلاق عشوائية ضمن 2km، ثم رحلة قصيرة (2-5km) وأخرى طويلة (10-15km) + - يضغط المهام في Redis list: `queue:bot:tasks` +- **الملفات المكتوبة:** Redis key `queue:bot:tasks` +- **ملاحظة:** إنشاء الجدول داخل cron job أمر غير محبذ - يجب أن يكون في migration + +#### 3.1.2 `worker.php` +- **الوظيفة:** نقطة نهاية API لبوت Android لسحب المهام وإرسال النتائج +- **الأمان:** HMAC-SHA256 مع نافذة 5 دقائق + `BOT_SECRET_KEY` من البيئة +- **الأجهزة المسموحة:** `['SHAM_CASH_BOT_01', 'PRICE_SCRAPER_BOT_01']` +- **GET:** يسحب مهمة من قائمة Redis (`RPOP`) +- **POST:** يستقبل النتيجة: + - `price_check`: يحسب `pricePerKm` ويدرج في جدول `competitor_prices` و Redis `competitor:price_history` + - `payment`: يسجل نجاح الدفع + - `failed`: يسجل الخطأ +- **Redis:** `queue:bot:tasks` (قراءة)، `competitor:price_history:{app}` (كتابة) +- **MySQL:** إدراج في `competitor_prices` + +#### 3.1.3 `standalone_worker.php` +- **الوظيفة:** نسخة بديلة كاملة بذاتها بدون Redis أو MySQL - تستخدم ملفات JSON +- **الاستخدام:** اختبار محلي / تطوير +- **الميزات:** لوحة تحكم HTML داكنة مع Bootstrap، منشئ مهام، سجل مهام +- **الأمان:** HMAC-SHA256 مع نافذة 15 دقيقة +- **لا يشكل خطرًا أمنيًا في الإنتاج طالما لا يمكن الوصول إليه عبر الويب** + +#### 3.1.4 `cron_surge_opportunity.php` (قلب النظام) +- **الجدولة:** كل 10 دقائق +- **الوظيفة:** محرك كشف فرص رفع الأسعار (Surge Detection) +- **آلية العمل:** + 1. يستعلم من `competitor_prices` ويجمّع البيانات بخلايا جغرافية (~1.5km) + 2. يحسب خط الأساس (baseline): متوسط سعر كل منافس لآخر 7 أيام + 3. يحسب السعر الحالي: متوسط لكل منافس في آخر ساعتين + 4. يشترط وجود 2 عينة على الأقل لكل منافس في كل خلية + 5. يكتشف الفرصة عندما **جميع** المنافسين في الخلية رفعوا أسعارهم + 6. يقترح مضاعف السعر: `1.0 + (avg_competitor_surge_ratio - 1.0) * 0.6` (يقلل عن المنافسين بـ 40%) + 7. يحفظ في Redis: `surge:opportunities` مع TTL 10 دقائق +- **SQL الرئيسي:** + ```sql + SELECT ROUND(lat * 74) / 74 AS lat_group, ROUND(lng * 74) / 74 AS lng_group, + competitor_name, country_code, + AVG(CASE WHEN created_at < DATE_SUB(NOW(), INTERVAL 6 HOUR) + THEN price_per_km END) AS baseline_avg, + AVG(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR) + THEN price_per_km END) AS current_avg, + COUNT(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 2 HOUR) + THEN 1 END) AS recent_samples + FROM competitor_prices + GROUP BY lat_group, lng_group, competitor_name, country_code + HAVING recent_samples >= 2 + ``` + +#### 3.1.5 `cron_kazan_adjuster.php` +- **الجدولة:** كل 10 دقائق +- **الوظيفة:** يخفف عمولة Siro (Kazan) في المناطق التي يرتفع فيها سعر المنافسين +- **آلية العمل:** + - يقرأ `surge:opportunities:{country}` من Redis + - للخلايا ذات surge > 1.2: يطبق تخفيض 30% على العمولة (0.70x) + - للخلايا ذات surge 1.05-1.2: يطبق تخفيض 15% (0.85x) + - يحفظ في Redis: `surge:kazan_discounts:{country}` مع TTL 20 دقيقة +- **ملاحظة:** يستدعي دالة `getRedisConnection()` غير المعرّفة - سيفشل في وقت التشغيل + +#### 3.1.6 `cron_seasonal_pricing.php` +- **الجدولة:** كل 30-60 دقيقة +- **الوظيفة:** يطبق مضاعفات موسمية (رمضان، عيد، طقس سيء) +- **القواعد:** + - `ramadan_iftar` (18:00-20:00): 1.25x + - `eid`: 1.15x (غير نشط حاليًا) + - `severe_weather`: 1.30x (غير نشط حاليًا) +- **ملاحظة:** نفس مشكلة `getRedisConnection()` غير المعرّفة + +#### 3.1.7 `cron_weekly_health_report.php` +- **الجدولة:** أسبوعيًا (ليلة الأحد) +- **الوظيفة:** ينشئ تقرير صحة السوق الأسبوعي +- **المقاييس:** + - **PCI (Price Competitiveness Index):** `siroPrice / compPrice` + - **حصة السوق:** % من الرحلات التي Siro فيها أرخص + - **عدد الشذوذ (anomalies):** من جدول `price_anomalies` + - **عدد الحملات:** من جدول `marketing_campaigns_log` +- **Redis:** لا يستخدم +- **MySQL:** يدرج في `market_health_reports` + +--- + +### 3.2 نظام التسعير الديناميكي - `backend/ride/pricing/` + +#### 3.2.1 `auto_adapt.php` +- **الجدولة:** كل 30-60 دقيقة +- **الوظيفة:** يعدل جميع أسعار Siro بناءً على أدنى متوسط سعر للمنافسين +- **المعادلة:** `new_price_per_km = lowest_competitor_avg * 0.92` (أقل من المنافسين بـ 8%) +- **النطاق:** بين 85% و 115% من السعر الحالي +- **الأعمدة المحدثة:** speedPrice, comfortPrice, ladyPrice, electricPrice, vanPrice, deliveryPrice, mishwarVipPrice, fixedPrice, awfarPrice +- **الدول:** SY, JO, EG, IQ +- **SQL الرئيسي:** + ```sql + SELECT competitor_name, AVG(price_per_km) AS avg_ppm + FROM competitor_prices + WHERE country_code = :cc AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR) + AND price_per_km > 0 + GROUP BY competitor_name + ORDER BY avg_ppm ASC + ``` + +#### 3.2.2 `get.php` (نقطة تسعير الرحلة - 511 سطرًا) +- **الوظيفة:** يحسب سعر الرحلة عند الطلب (أثناء تشغيل التطبيق) +- **آلية العمل المعقدة:** + 1. **حساب الخلية الجغرافية:** من إحداثيات الراكب (~1.5km) + 2. **قراءة الطلب:** `$redis->get("demand:grid:" . $grid_id)` + 3. **قراءة surge المنافسين:** من `surge:opportunities` في Redis + 4. **حساب توفر السائقين:** `$redisLocation->georadius()` ضمن 0.75km + 5. **Surge Calculation:** إذا `طلب/سائقين > 1.2`، يطبق مضاعف يصل إلى 3.0x + 6. **التسعير الزمني:** الليل (21:00-01:00) ← `latePrice`، الفجر (01:00-05:00) ← مضاعف، الظهر (14:00-17:00) ← `heavyPrice` + 7. **تخفيضات المسافات الطويلة:** 40km+ و 100km+ + 8. **مطابقة المنافسين:** مستويين من المطابقة: + - المستوى 1: يطابق نقطة البداية والنهاية معًا + - المستوى 2: يطابق نقطة البداية فقط + - يخفض السعر بنسبة 8% إذا كان السعر المحسوب أعلى من متوسط المنافسين + 9. **العمولة:** `price * (1 + kazanPercent / 100)` + 10. **التحقق من العروض:** يتحقق من جدول `promos` + 11. **ديون الراكب:** يقرأ من Redis `passenger_debt_{id}` + 12. **توليد توكن:** يشفر حمولة السعر بانتهاء صلاحية 7 دقائق لمنع التلاعب +- **المفاتيح الأساسية:** `kazan`, `promos`, `competitor_prices` +- **Redis:** `demand:grid:{id}`, `surge:opportunities`, `passenger_debt_{id}` + +--- + +### 3.3 نظام الخريطة الحرارية (Heatmap) - `backend/ride/heatmap/` + +#### 3.3.1 `log_demand.php` +- **الوظيفة:** يسجل الطلب عند كل خلية جغرافية +- **Redis:** `INCR` على `demand:grid:{grid_id}` مع TTL 60 ثانية +- **يُستدعى من:** تطبيق الراكب عند طلب رحلة + +#### 3.3.2 `get_surge_heatmap.php` +- **الوظيفة:** يعرض بؤر surge لتطبيق السائق (Driver App) +- **المصادقة:** يتطلب دور `driver` +- **Redis:** يقرأ `surge:opportunities:{countryCode}` +- **الفلترة:** فقط الخلايا ذات مضاعف > 1.05 + +#### 3.3.3 `heatmap_live.php` +- **الوظيفة:** خريطة حية لتطبيق الكابتن - تجمع بين الطلب وتوفر السائقين +- **تحذير:** يستخدم `$redis->keys("demand:grid:*")` - أمر `KEYS` بطيء مع عدد كبير من المفاتيح +- **التصنيف:** عالي (نسبة > 2.0 أو عدد ≥ 5)، متوسط (نسبة > 1.2 أو عدد ≥ 3)، منخفض + +--- + +### 3.4 نقط نهاية التسويق - `backend/Admin/marketing/` + +#### 3.4.1 `trigger_campaign.php` (200 سطر - الأكثر تعقيدًا) +- **الوظيفة:** يطلق حملة تسويقية مدعومة بالذكاء الاصطناعي +- **الخطوات:** + 1. يجلب آخر 10 أسعار منافسين من MySQL + 2. يستدعي `SiroGeminiService->analyzeMarketAndDraftCampaign()` مع بيانات السوق + 3. إذا تم اكتشاف فرصة (`opportunity_detected`): + - يجد الركاب المستهدفين من `passenger_opening_locations` + - ينشئ كود خصم في جدول `promos` (صالحة 7 أيام) + - لكل راكب: + - إذا لديه FCM token: يرسل إشعار دفع + - إذا لا: يتحقق من anti-spam (24 ساعة)، ثم WhatsApp → SMS + 4. يسجل في `admin_audit_log` +- **الخدمات الخارجية:** Google Gemini, WhatsApp Bot, Firebase Cloud Messaging +- **الحماية:** Anti-spam (24h cooldown للـ SMS/WhatsApp) + +#### 3.4.2 `ai_price_prediction.php` +- **الوظيفة:** يتوقع ساعات الذروة بناءً على بيانات 14 يومًا +- **المنطق:** `SELECT HOUR(created_at) FROM price_anomalies WHERE anomaly_type = 'opportunity' GROUP BY HOUR ORDER BY COUNT(*) DESC LIMIT 3` +- **النتيجة:** أفضل 3 ساعات متوقعة + نسبة ثقة 85% + +#### 3.4.3 `what_if_simulator.php` +- **الوظيفة:** محاكي "ماذا لو" - يقترح السعر الأمثل +- **المنطق:** + - يأخذ `speed_price` مقترح + - يحاكي السعر لآخر 500 رحلة منافس + - يحسب PCI الجديد وحصة السوق المتوقعة + - التوصية: PCI < 0.8 تحذير (ربح قليل)، 0.9-0.95 ممتاز، > 1.0 خطر + +#### 3.4.4 `surge_opportunity_index.php` +- **الوظيفة:** نسخة Admin من `cron_surge_opportunity.php` - استعلام فوري +- **الفرق:** يُستدعى عبر HTTP، يعرض تفاصيل كل منطقة ومنافس + +#### 3.4.5 `winback_hotspot_targets.php` +- **الوظيفة:** يجد الركاب الخاملين (30 يوم بدون رحلة) في مناطق surge +- **Redis:** يقرأ `surge:opportunities:{countryCode}` +- **MySQL:** `users JOIN passenger_opening_locations` + +#### 3.4.6 `get_price_gap_heatmap.php` +- **الوظيفة:** خريطة حرارية توضح أين Siro أرخص/أغلى من المنافسين +- **المنطق:** لكل خلية جغرافية يحسب `pci = currentSpeedPrice / avg_competitor_price` و `weight = pci - 1.0` مقيد بـ [-1, 1] + +#### 3.4.7 باقي نقاط النهاية +| الملف | الوظيفة | +|-------|---------| +| `get_campaigns_log.php` | سجل الحملات التسويقية | +| `get_market_anomalies.php` | الشذوذ السعري + آخر أسعار المنافسين | +| `get_market_share_analytics.php` | بيانات حصة السوق للرسوم البيانية (آخر 12 أسبوع) | +| `get_price_comparison.php` | مقارنة الأسعار: متوسطات الساعة، PCI حسب المنطقة، أسعار Siro | +| `get_telemetry.php` | إحصائيات استخدام النظام: عدد الحملات، التكلفة التقديرية | + +--- + +### 3.5 خدمة الذكاء الاصطناعي - `backend/core/Services/SiroGeminiService.php` + +- **النموذج:** `gemini-1.5-flash` +- **الوظيفة:** تحليل السوق وكتابة الحملات التسويقية بالعربية +- **المخرجات:** JSON يحتوي على `opportunity_detected`, `campaign_text`, `discount_percent`, `message_type` +- **التكيف:** يضبط اللهجة حسب البلد (سوري، أردني، مصري، عراقي) + +--- + +## 4. تدفق البيانات (Data Flow) + +``` +generate_price_tasks.php (cron/15min) + │ + │ LPUSH → queue:bot:tasks (Redis) + ▼ +Android Bot (Scraper) ←→ worker.php (API) + │ + │ POST result → INSERT competitor_prices + ▼ +┌────────────────────────────────────────────────────────────┐ +│ competitor_prices (MySQL) │ +│ country_code | lat | lng | price_per_km | competitor_name │ +└─────────────────────────────────────────────────────────────┘ + │ + ├─── cron_surge_opportunity.php (cron/10min) + │ │ SELECT baseline vs current + │ │ WRITE surge:opportunities (Redis) + │ ▼ + ├─── cron_kazan_adjuster.php (cron/10min) + │ │ READ surge:opportunities:{CC} + │ │ WRITE surge:kazan_discounts:{CC} + │ ▼ + ├─── cron_seasonal_pricing.php (cron/30-60min) + │ │ WRITE surge:seasonal:{CC} + │ ▼ + ├─── auto_adapt.php (cron/30-60min) + │ │ SELECT AVG(price_per_km) → UPDATE kazan + │ ▼ + ├─── cron_weekly_health_report.php (cron/weekly) + │ │ SELECT → INSERT market_health_reports + │ ▼ + ├─── ride/pricing/get.php (API - عند طلب رحلة) + │ │ READ Redis (surge, demand, debt) + │ │ SELECT (competitor_prices, kazan, promos) + │ ▼ + └─── Admin/marketing/*.php (API - لوحة التحكم) + │ READ (competitor_prices, anomalies, etc.) + │ WRITE (promos, campaigns_log, audit_log) + │ CALL Gemini API +``` + +--- + +## 5. قاعدة البيانات وجداولها + +| الجدول | العمليات (SELECT) | العمليات (INSERT/UPDATE) | +|--------|:-----------------:|:------------------------:| +| `competitor_prices` | cron_surge_opportunity, cron_weekly_health, surge_opportunity_index, get_price_comparison, get_price_gap_heatmap, what_if_simulator, trigger_campaign, auto_adapt, pricing/get | worker.php (INSERT), generate_price_tasks (CREATE TABLE) | +| `kazan` | cron_weekly_health, get_price_comparison, get_price_gap_heatmap, what_if_simulator, pricing/get | auto_adapt (UPDATE) | +| `price_anomalies` | ai_price_prediction, get_market_anomalies, cron_weekly_health, get_telemetry | (خارج نطاق هذه الدراسة) | +| `market_health_reports` | get_market_share_analytics | cron_weekly_health (INSERT) | +| `marketing_campaigns_log` | get_campaigns_log, cron_weekly_health, get_telemetry | trigger_campaign (INSERT) | +| `passenger_opening_locations` | trigger_campaign, winback_hotspot_targets | (خارج النطاق) | +| `promos` | pricing/get | trigger_campaign (INSERT) | +| `passengers` | get_campaigns_log, trigger_campaign | (خارج النطاق) | +| `tokens` | trigger_campaign | (خارج النطاق) | +| `users` | winback_hotspot_targets | (خارج النطاق) | +| `admin_audit_log` | - | trigger_campaign (INSERT via logAudit) | + +--- + +## 6. خريطة مفيكات Redis + +| نمط المفتاح | يُكتب بواسطة | يُقرأ بواسطة | TTL | +|:-----------:|:------------:|:------------:|:---:| +| `surge:opportunities` | cron_surge_opportunity, surge_opportunity_index | pricing/get, get_surge_heatmap | 600s | +| `surge:opportunities:{CC}` | cron_kazan_adjuster | winback_hotspot_targets, get_surge_heatmap | 1200s | +| `surge:kazan_discounts:{CC}` | cron_kazan_adjuster | (ride logic) | 1200s | +| `surge:seasonal:{CC}` | cron_seasonal_pricing | (ride logic) | 3600s | +| `queue:bot:tasks` | generate_price_tasks | worker.php (RPOP) | list | +| `competitor:price_history:{app}` | worker.php | (تحليلات مستقبلية) | list/50 | +| `demand:grid:{grid}` | log_demand | pricing/get, heatmap_live | 60s | +| `passenger_debt_{id}` | (من نظام المحفظة) | pricing/get | متغير | + +--- + +## 7. الخدمات الخارجية المستخدمة + +| الخدمة | الاستخدام | الملف المرتبط | +|--------|:---------:|:-------------:| +| **Google Gemini AI** | إنشاء محتوى الحملات التسويقية | `SiroGeminiService.php`, `trigger_campaign.php` | +| **Firebase Cloud Messaging** | إشعارات الدفع للركاب | `trigger_campaign.php`, `FcmService.php` | +| **WhatsApp Bot Servers** | إرسال رسائل واتساب | `trigger_campaign.php` | +| **Android Bot (Scraper)** | جمع أسعار المنافسين | `worker.php`, `generate_price_tasks.php` | +| **Location Socket Server** | مواقع السائقين اللحظية | `heatmap_live.php`, `pricing/get.php` | + +--- + +## 8. ملفات الخلفية - مراجعة أمنية شاملة + +### 8.1 ملفات اختبار/تجربة عالية الخطورة (يجب حذفها فورًا) + +#### `backend/test_add_driver_and_car.php` +- **الوصف:** سكربت اختبار يُنشئ سائق وسيارة في قاعدة البيانات مباشرة +- **الخطر:** لا يتطلب أي مصادقة - أي زائر يمكنه إنشاء حسابات سائقين وهمية +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/test_signed_pricing.php` +- **الوصف:** سكربت اختبار يتحايل على المصادقة (`define('TESTING_BYPASS_AUTH', true)`) +- **الخطر:** يمكنه إنشاء رحلات حقيقية ببيانات مزيفة +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/diagnose_fingerprint.php` +- **الوصف:** أداة تشخيص تفضي ببيانات البصمات المخزنة +- **الخطر:** يعرض البيانات مفككة التشفير بدون مصادقة +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/diagnose_login.php` +- **الوصف:** أداة تشخيص تسجيل الدخول - تعرض أرقام الهواتف والأسماء +- **الخطر:** يعرض PII (معلومات شخصية) مفككة التشفير +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/auth/Tester/getTesterApp.php` و `updateTesterApp.php` +- **الوصف:** نقاط نهاية اختبار بدون مصادقة مع SQL Injection +- **الخطر:** SQL Injection صريح + لا مصادقة +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/migration_create_table.php` +- **الوصف:** سكربت إنشاء جدول (كان يجب حذفه بعد الاستخدام) +- **الخطر:** يمكن إعادة تنفيذه لتعديل schema قاعدة البيانات +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/migrate_driver_passwords.php` +- **الوصف:** سكربت ترحيل كلمات المرور +- **الخطر:** يحتوي على بيانات اعتماد قاعدة بيانات في الكود +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/Admin/auth/migration_cryptography.php` +- **الوصف:** يعيد تشفير جميع الأعمدة المشفرة (أكثر من 15 جدول) +- **الخطر:** إعادة تنفيذه قد تفسد جميع البيانات المشفرة +- **التصنيف:** **🚨 عالي - احذف فورًا** + +#### `backend/Admin/auth/migrate_db.php` +- **الوصف:** سكربت ترحيل بنية قاعدة البيانات +- **الخطر:** يمكنه تعديل schema الإنتاج +- **التصنيف:** **🚨 عالي - احذف فورًا** + +#### `backend/Admin/Staff/add_super_admin.php` +- **الوصف:** إضافة مشرف (Admin) جديد +- **الخطر:** التحقق من الصلاحية معلّق (commented out) - أي زائر يمكنه إنشاء super admin +- **التصنيف:** **🚨 عالي جدًا - احذف فورًا** + +#### `backend/Admin/Staff/setup.php` +- **الوصف:** سكربت إعداد الموظفين الأولي +- **الخطر:** يمكنه إعادة تعيين جميع صلاحيات المشرفين +- **التصنيف:** **🚨 عالي - احذف بعد التأكد من الإعداد** + +### 8.2 ملفات ترحيل البصمات - مفتاح مشفر مكشوف + +#### `siro_admin/lib/views/admin/enceypt/fingerprint_migration.dart` +#### `siro_admin/lib/views/admin/enceypt/driver_fingerprint_migration.dart` +- **الوصف:** أدوات Flutter لترحيل البصمات +- **الخطر:** يحتويان على مفتاح المشرف (admin key) بشكل نصي صريح: + ``` + 'iuyweiruinakjbfkajkjlkmalkcxnlahd' + ``` +- **التصنيف:** **🔥 خطير جدًا - احذف فورًا وغيّر المفتاح في السيرفر** + +### 8.3 ملفات "ggg" - أدوات التشفير + +| الملف | الوظيفة | المصادقة | التصنيف | +|-------|:-------:|:--------:|:-------:| +| `backend/ggg.php` | تشفير/فك تشفير للمشرف | JWT admin/super_admin | ⚠️ متوسط | +| `backend/Admin/ggg.php` | تشفير/فك تشفير | ADMIN_PHONE_NUMBERS | ⚠️ متوسط | + +**التوصية:** احذف بعد انتهاء الترحيل. هذه أدوات خطيرة لأنها تسمح بفك تشفير أي بيانات. + +### 8.4 ملفات الترحيل (Migration Files) + +| الملف | الوظيفة | التصنيف | +|-------|:-------:|:-------:| +| `backend/migration/get_all_fingerprints.php` | تصدير بصمات الركاب | ⚠️ متوسط | +| `backend/migration/get_all_driver_fingerprints.php` | تصدير بصمات السائقين | ⚠️ متوسط | +| `backend/migration/update_fingerprint_admin.php` | تحديث بصمة راكب | ⚠️ متوسط | +| `backend/migration/update_driver_fingerprint_admin.php` | تحديث بصمة سائق | ⚠️ متوسط | + +**التوصية:** احذف جميع ملفات الترحيل بعد التأكد من اكتمال الترحيل في الإنتاج. + +### 8.5 ملفات أخرى + +#### `backend/intaleq_v1_secure_latest.md` +- **الوصف:** ملف توثيق ضخم (33,608 سطر) يحتوي على كود المصدر الكامل +- **الخطر:** إذا كان الوصول إليه ممكنًا عبر الويب، فإنه يسرب كامل قاعدة الشفرة +- **التصنيف:** ⚠️ متوسط - انقل خارج جذر الويب أو احذف + +#### `siro_admin/lib/debug_jwt.dart` +- **الوصف:** سكربت Flutter لاختبار JWT +- **الخطر:** يكشف بنية JWT ومنهجية التشفير +- **التصنيف:** ⚠️ منخفض - احذف من بنية الإنتاج + +--- + +## 9. قائمة الملفات المطلوب حذفها فوراً + +### أولوية قصوى - خطر أمني مباشر 🔴 + +| الملف | السبب | +|:------|:-----:| +| `backend/test_add_driver_and_car.php` | إنشاء سائقين بدون مصادقة | +| `backend/test_signed_pricing.php` | تجاوز المصادقة | +| `backend/diagnose_fingerprint.php` | كشف بيانات حساسة بدون مصادقة | +| `backend/diagnose_login.php` | كشف PII بدون مصادقة | +| `backend/migration_create_table.php` | تعديل schema بدون مصادقة | +| `backend/migrate_driver_passwords.php` | بيانات اعتماد DB في الكود | +| `backend/Admin/auth/migration_cryptography.php` | إعادة تشفير شامل | +| `backend/Admin/auth/migrate_db.php` | تعديل schema الإنتاج | +| `backend/Admin/Staff/add_super_admin.php` | صلاحية المشرِف معلّقة | +| `backend/Admin/Staff/setup.php` | إعادة تعيين الصلاحيات | +| `backend/auth/Tester/getTesterApp.php` | SQL Injection + لا مصادقة | +| `backend/auth/Tester/updateTesterApp.php` | SQL Injection + لا مصادقة | +| `siro_admin/lib/views/admin/enceypt/fingerprint_migration.dart` | مفتاح مكشوف 🔥 | +| `siro_admin/lib/views/admin/enceypt/driver_fingerprint_migration.dart` | مفتاح مكشوف 🔥 | + +### أولوية متوسطة - أمان أو تنظيف 🟡 + +| الملف | السبب | +|:------|:-----:| +| `backend/ggg.php` | أداة تشفير للمشرفين - احذف بعد الترحيل | +| `backend/Admin/ggg.php` | أداة تشفير - احذف بعد الترحيل | +| `backend/migration/get_all_fingerprints.php` | ترحيل بصمات - احذف بعد التأكد | +| `backend/migration/get_all_driver_fingerprints.php` | ترحيل بصمات - احذف بعد التأكد | +| `backend/migration/update_fingerprint_admin.php` | ترحيل بصمات - احذف بعد التأكد | +| `backend/migration/update_driver_fingerprint_admin.php` | ترحيل بصمات - احذف بعد التأكد | +| `backend/Admin/auth/register.php` | مراجعة - هل ما زال مستخدمًا؟ | +| `siro_admin/lib/debug_jwt.dart` | تصحيح JWT - احذف من الإنتاج | +| `backend/intaleq_v1_secure_latest.md` (33k سطر) | كود مصدر كامل - انقل أو احذف | + +### أولوية منخفضة - تحسين أمني 🟢 + +| الملف | السبب | +|:------|:-----:| +| `walletintaleq.intaleq.xyz/v2/main/ride/payment/delete.php` | ملف فارغ تمامًا | +| `backend/logo.png` | (تحقق ما إذا كان ضروريًا) | + +--- + +## 10. ثغرات SQL Injection + +### 10.1 ملفات ذات SQL Injection مباشر + +هذه الملفات تستخدم `prepare()` بعد `$sql = "... WHERE id = '$var'"` مما يبطل تمامًا حماية prepared statements: + +| الملف | السطر | الكود المخترق | +|:------|:-----:|:--------------| +| `wallet/ride/passengerWallet/delete.php` | `$sql = "DELETE FROM passengerWallet WHERE id = '$id'"` | +| `wallet/ride/passengerWallet/update.php` | `$sql = "UPDATE passengerWallet SET balance = '$balance' WHERE id = '$id'"` | +| `wallet/ride/passengerWallet/getAllPassengerTransaction.php` | `WHERE passenger_id = '$passenger_id'` | +| `wallet/ride/passengerWallet/getPassengerWalletArchive.php` | `WHERE passenger_id = '$passenger_id'` | +| `wallet/ride/driverPayment/delete.php` | `DELETE FROM paymentsDriverPoints WHERE id = '$id'` | +| `wallet/ride/driverPayment/update.php` | `UPDATE ... SET amount = '$amount', ...` | +| `wallet/ride/driverPayment/add.php` | `INSERT INTO paymentsDriverPoints VALUES ('$amount', '$paymentMethod', '$driverID')` | +| `wallet/ride/driverWallet/get.php` | `WHERE driverID = '$driverID'` (في subquery) | +| `wallet/ride/payment/update.php` | بناء `SET` ديناميكي + SQL Injection | +| `backend/Admin/adminUser/get.php` | `WHERE device_number = '$device_number'` | + +### 10.2 الإجراء المطلوب + +1. **فوري:** استبدال `$sql = "... WHERE id = '$var'"` بـ `$sql = "... WHERE id = :id"` مع `bindParam(':id', $id)` +2. **مراجعة:** جميع ملفات `walletintaleq/` لديها مشكلة أمنية منهجية - تحتاج مراجعة كاملة +3. **تحذير:** ملفات `driverPayment/` لا تملك أي مصادقة JWT - يمكن لأي زوار الوصول إليها + +--- + +## 11. التوصيات النهائية + +### أولاً - فوري (خلال 24 ساعة) +1. **حذف جميع الملفات عالية الخطورة (القسم 9 - 🔴)** +2. **تغيير مفتاح `MIGRATION_ADMIN_KEY`** في جميع السيرفرات (المفتاح `iuyweiruinakjbfkajkjlkmalkcxnlahd` أصبح مكشوفًا) +3. **إصلاح ثغرات SQL Injection** في ملفات المحفظة (القسم 10) + +### ثانيًا - قصير المدى (خلال أسبوع) +4. **توحيد السناك بار** حسب الخطة في القسم 1 +5. **حذف ملفات الترحيل** بعد التأكد من اكتمال الترحيل +6. **نقل أو حذف `intaleq_v1_secure_latest.md`** من جذر الويب +7. **مراجعة جميع ملفات `walletintaleq/`** - معظمها يفتقر إلى مصادقة JWT + +### ثالثًا - طويل المدى (شهر) +8. **تبسيط الـ Redis architecture** - حاليًا يوجد اتصالان منفصلان (main + location) +9. **استبدال `KEYS` command** في `heatmap_live.php` بـ `SCAN` +10. **إصلاح الدوال غير المعرّفة** `getRedisConnection()` و `resolveAdminCountry()` +11. **فصل إنشاء الجداول** من ملفات cron إلى migration scripts +12. **توسيع التغطية الجغرافية** - حاليًا تتركز على دمشق فقط + +### رابعًا - تحسينات إضافية +13. **إضافة مزيد من التوثيق** للـ cron jobs (جدولة، مخرجات، مراقبة) +14. **إضافة نظام إنذار** عند فشل أحد cron jobs +15. **تشفير جميع مفاتيح API** في ملف `.env` فقط +16. **تطبيق pipeline CI/CD** يستبعد ملفات الاختبار والترحيل من الإنتاج + +--- + +*تم إعداد هذه الدراسة بتاريخ 26 يونيو 2026 بناءً على تحليل شامل للكود المصدري لمشروع Siro.* diff --git a/backend/intaleq_v1_secure_latest.md b/knowledge/intaleq_v1_secure_latest.md similarity index 100% rename from backend/intaleq_v1_secure_latest.md rename to knowledge/intaleq_v1_secure_latest.md diff --git a/siro_admin/lib/constant/links.dart b/siro_admin/lib/constant/links.dart index 9b6b7597..316c0e6b 100644 --- a/siro_admin/lib/constant/links.dart +++ b/siro_admin/lib/constant/links.dart @@ -1,6 +1,5 @@ import '../env/env.dart'; import '../main.dart'; -import 'box_name.dart'; class AppLink { static String seferPaymentServer = diff --git a/siro_admin/lib/controller/admin/captain_admin_controller.dart b/siro_admin/lib/controller/admin/captain_admin_controller.dart index a256772e..28062ff5 100644 --- a/siro_admin/lib/controller/admin/captain_admin_controller.dart +++ b/siro_admin/lib/controller/admin/captain_admin_controller.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; -import '../../constant/colors.dart'; import '../../constant/links.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class CaptainAdminController extends GetxController { @@ -60,8 +60,7 @@ class CaptainAdminController extends GetxController { captainData = d; } else { captainData = {}; - Get.snackbar('Error', 'No captain found with this phone number', - backgroundColor: AppColor.redColor); + mySnackbarError('No captain found with this phone number'); } isLoading = false; @@ -102,17 +101,16 @@ class CaptainAdminController extends GetxController { if (didAuthenticate) { // User authenticated successfully, proceed with payment await addCaptainPrizeToWallet(); - Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor); + mySnackbarSuccess('Prize Added'); } else { // Authentication failed, handle accordingly - Get.snackbar('Authentication failed', '', - backgroundColor: AppColor.redColor); + mySnackbarError('Authentication failed'); // 'Authentication failed'); } } else { // Local authentication not available, proceed with payment without authentication await addCaptainPrizeToWallet(); - Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor); + mySnackbarSuccess('Prize Added'); } } catch (e) { rethrow; diff --git a/siro_admin/lib/controller/admin/complaint_controller.dart b/siro_admin/lib/controller/admin/complaint_controller.dart index 26a30623..2fbbada1 100644 --- a/siro_admin/lib/controller/admin/complaint_controller.dart +++ b/siro_admin/lib/controller/admin/complaint_controller.dart @@ -1,6 +1,7 @@ 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 { @@ -41,7 +42,7 @@ class ComplaintController extends GetxController { complaintList.clear(); } } catch (e) { - Get.snackbar("خطأ", "فشل جلب الشكاوى: $e"); + mySnackbarError("فشل جلب الشكاوى: $e"); } finally { isLoading.value = false; } @@ -61,7 +62,7 @@ class ComplaintController extends GetxController { } return false; } catch (e) { - Get.snackbar("خطأ", "فشل تحديث الشكوى: $e"); + mySnackbarError("فشل تحديث الشكوى: $e"); return false; } finally { isLoading.value = false; diff --git a/siro_admin/lib/controller/admin/driver_docs_controller.dart b/siro_admin/lib/controller/admin/driver_docs_controller.dart index ef88f70e..c6c0716c 100644 --- a/siro_admin/lib/controller/admin/driver_docs_controller.dart +++ b/siro_admin/lib/controller/admin/driver_docs_controller.dart @@ -1,6 +1,7 @@ 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 { @@ -49,7 +50,7 @@ class DriverDocsController extends GetxController { } } } catch (e) { - Get.snackbar("خطأ", "فشل جلب السائقين: $e"); + mySnackbarError("فشل جلب السائقين: $e"); } finally { isLoading.value = false; isMoreLoading.value = false; @@ -70,7 +71,7 @@ class DriverDocsController extends GetxController { } } } catch (e) { - Get.snackbar("خطأ", "فشل جلب تفاصيل السائق: $e"); + mySnackbarError("فشل جلب تفاصيل السائق: $e"); } return null; } @@ -88,7 +89,7 @@ class DriverDocsController extends GetxController { } return false; } catch (e) { - Get.snackbar("خطأ", "فشل اعتماد السائق: $e"); + mySnackbarError("فشل اعتماد السائق: $e"); return false; } finally { isLoading.value = false; diff --git a/siro_admin/lib/controller/admin/kazan_controller.dart b/siro_admin/lib/controller/admin/kazan_controller.dart index 9ab1ac84..9be1fd9c 100644 --- a/siro_admin/lib/controller/admin/kazan_controller.dart +++ b/siro_admin/lib/controller/admin/kazan_controller.dart @@ -1,34 +1,51 @@ 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 = 'Syria'.obs; final CRUD _crud = CRUD(); + final List> countries = [ + {'code': 'syria', 'name': 'سوريا', 'flag': '🇸🇾'}, + {'code': 'jordan', 'name': 'الأردن', 'flag': '🇯🇴'}, + {'code': 'egypt', 'name': 'مصر', 'flag': '🇪🇬'}, + ]; + @override void onInit() { super.onInit(); getKazan(); } + void setCountry(String countryName) { + selectedCountry.value = countryName; + getKazan(); + } + Future getKazan() async { isLoading.value = true; try { - var response = await _crud.get(link: "${AppLink.getKazanPercent}?country=syria"); + final countryParam = selectedCountry.value.toLowerCase(); + 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) { - kazanData.value = message[0]; + kazanData.value = Map.from(message[0]); + kazanData['country'] = selectedCountry.value; + } else { + kazanData.value = {'country': selectedCountry.value}; } } } } catch (e) { - Get.snackbar("خطأ", "فشل جلب بيانات التسعير: $e"); + mySnackbarError('فشل جلب بيانات التسعير: $e'); } finally { isLoading.value = false; } @@ -37,8 +54,9 @@ class KazanController extends GetxController { Future updateKazan(Map data) async { isLoading.value = true; try { + data['country'] = selectedCountry.value; final String link = data.containsKey('id') ? AppLink.updateKazanPercent : AppLink.addKazanPercent; - + Map payload = {}; data.forEach((key, value) { payload[key] = value.toString(); @@ -51,7 +69,7 @@ class KazanController extends GetxController { } return false; } catch (e) { - Get.snackbar("خطأ", "فشل تحديث التسعير: $e"); + mySnackbarError('فشل تحديث التسعير: $e'); return false; } finally { isLoading.value = false; diff --git a/siro_admin/lib/controller/admin/marketing_controller.dart b/siro_admin/lib/controller/admin/marketing_controller.dart index 34ffd7e4..49db6143 100644 --- a/siro_admin/lib/controller/admin/marketing_controller.dart +++ b/siro_admin/lib/controller/admin/marketing_controller.dart @@ -1,5 +1,6 @@ import 'package:get/get.dart'; import '../../constant/links.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class MarketingController extends GetxController { @@ -38,22 +39,14 @@ class MarketingController extends GetxController { void toggleAutopilot(bool value) { isAutopilotEnabled = value; update(); - Get.snackbar( - "Autopilot Updated".tr, - value ? "Full Autopilot mode enabled".tr : "Approval Mode enabled".tr, - snackPosition: SnackPosition.BOTTOM, - ); + mySnackbarInfo(value ? "Full Autopilot mode enabled".tr : "Approval Mode enabled".tr); } // --- System Prompt Configuration Saver --- void savePrompt(String newPrompt) { systemPrompt = newPrompt; update(); - Get.snackbar( - "Configuration Saved".tr, - "Gemini system instructions updated successfully".tr, - snackPosition: SnackPosition.BOTTOM, - ); + mySnackbarSuccess("Gemini system instructions updated successfully".tr); } Future fetchAnomalies() async { @@ -72,10 +65,10 @@ class MarketingController extends GetxController { if (res is Map && res['status'] == 'success') { priceAnomalies = res['message'] ?? []; } else { - Get.snackbar("Error", "Failed to fetch price anomalies"); + mySnackbarError("Failed to fetch price anomalies"); } } catch (e) { - Get.snackbar("Error", "Network error while loading anomalies"); + mySnackbarError("Network error while loading anomalies"); } finally { isLoading = false; update(); @@ -123,10 +116,10 @@ class MarketingController extends GetxController { if (res is Map && res['status'] == 'success') { campaignsLog = res['message'] ?? []; } else { - Get.snackbar("Error", "Failed to fetch campaign logs"); + mySnackbarError("Failed to fetch campaign logs"); } } catch (e) { - Get.snackbar("Error", "Network error while loading campaign logs"); + mySnackbarError("Network error while loading campaign logs"); } finally { isLoading = false; update(); @@ -175,17 +168,17 @@ class MarketingController extends GetxController { ); if (res is Map) { if (res['status'] == 'success') { - Get.snackbar("Success", "AI campaign triggered successfully! Promos sent."); + mySnackbarSuccess("AI campaign triggered successfully! Promos sent."); fetchCampaignsLog(); fetchTelemetry(); } else { - Get.snackbar("Campaign Alert", res['message'] ?? "Campaign rate limited."); + mySnackbarWarning(res['message'] ?? "Campaign rate limited."); } } else { - Get.snackbar("Error", "Failed to trigger AI campaign"); + mySnackbarError("Failed to trigger AI campaign"); } } catch (e) { - Get.snackbar("Error", "Network error while triggering campaign"); + mySnackbarError("Network error while triggering campaign"); } finally { isLoading = false; update(); @@ -224,10 +217,10 @@ class MarketingController extends GetxController { simulatorRecommendationMessage = data['recommendation_message']; } } else { - Get.snackbar("Simulation Error", res['message'] ?? "Failed to run simulation"); + mySnackbarError(res['message'] ?? "Failed to run simulation"); } } catch (e) { - Get.snackbar("Error", "Network error during simulation"); + mySnackbarError("Network error during simulation"); } finally { isLoading = false; update(); @@ -340,9 +333,4 @@ class MarketingController extends GetxController { } } - @override - void onInit() { - super.onInit(); - // Initially fetch these too if needed, but fetch them specifically when country changes - } } diff --git a/siro_admin/lib/controller/admin/passenger_admin_controller.dart b/siro_admin/lib/controller/admin/passenger_admin_controller.dart index 37e4ddeb..89beed08 100644 --- a/siro_admin/lib/controller/admin/passenger_admin_controller.dart +++ b/siro_admin/lib/controller/admin/passenger_admin_controller.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; -import '../../constant/colors.dart'; import '../../constant/links.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class PassengerAdminController extends GetxController { @@ -90,9 +90,7 @@ class PassengerAdminController extends GetxController { final ok = (d['status'] == 'success'); if (ok) { // (اختياري) حدّث الكاش/الواجهة — مثلاً أعد الجلب - Get.snackbar('Update successful', - d['message']?.toString() ?? 'Passenger updated successfully', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess(d['message']?.toString() ?? 'Passenger updated successfully'); // await getPassengerCount(); // أو حدّث passengersData محليًا إذا متاح } else { // (اختياري) أظهر رسالة خطأ @@ -120,17 +118,16 @@ class PassengerAdminController extends GetxController { if (didAuthenticate) { // User authenticated successfully, proceed with payment await addPassengerPrizeToWallet(); - Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor); + mySnackbarSuccess('Prize Added'); } else { // Authentication failed, handle accordingly - Get.snackbar('Authentication failed', '', - backgroundColor: AppColor.redColor); + mySnackbarError('Authentication failed'); // 'Authentication failed'); } } else { // Local authentication not available, proceed with payment without authentication await addPassengerPrizeToWallet(); - Get.snackbar('Prize Added', '', backgroundColor: AppColor.greenColor); + mySnackbarSuccess('Prize Added'); } } catch (e) { rethrow; diff --git a/siro_admin/lib/controller/admin/promo_controller.dart b/siro_admin/lib/controller/admin/promo_controller.dart index c3f4d80c..3abecae8 100644 --- a/siro_admin/lib/controller/admin/promo_controller.dart +++ b/siro_admin/lib/controller/admin/promo_controller.dart @@ -1,6 +1,7 @@ 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 { @@ -27,7 +28,7 @@ class PromoController extends GetxController { promoList.clear(); } } catch (e) { - Get.snackbar("خطأ", "فشل جلب أكواد الخصم: $e"); + mySnackbarError("فشل جلب أكواد الخصم: $e"); } finally { isLoading.value = false; } @@ -43,7 +44,7 @@ class PromoController extends GetxController { } return false; } catch (e) { - Get.snackbar("خطأ", "فشل إضافة كود الخصم: $e"); + mySnackbarError("فشل إضافة كود الخصم: $e"); return false; } finally { isLoading.value = false; @@ -59,7 +60,7 @@ class PromoController extends GetxController { } return false; } catch (e) { - Get.snackbar("خطأ", "فشل حذف كود الخصم: $e"); + mySnackbarError("فشل حذف كود الخصم: $e"); return false; } } @@ -74,7 +75,7 @@ class PromoController extends GetxController { } return false; } catch (e) { - Get.snackbar("خطأ", "فشل تحديث كود الخصم: $e"); + mySnackbarError("فشل تحديث كود الخصم: $e"); return false; } finally { isLoading.value = false; diff --git a/siro_admin/lib/controller/admin/quality_controller.dart b/siro_admin/lib/controller/admin/quality_controller.dart index e447327a..159ab9d5 100644 --- a/siro_admin/lib/controller/admin/quality_controller.dart +++ b/siro_admin/lib/controller/admin/quality_controller.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constant/links.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class QualityController extends GetxController { @@ -21,10 +21,10 @@ class QualityController extends GetxController { driversBlacklist = res['message']['drivers'] ?? []; passengersBlacklist = res['message']['passengers'] ?? []; } else { - Get.snackbar("Error", "Failed to fetch blacklist"); + mySnackbarError("Failed to fetch blacklist"); } } catch (e) { - Get.snackbar("Error", "Network error"); + mySnackbarError("Network error"); } finally { isLoading = false; update(); @@ -41,13 +41,13 @@ class QualityController extends GetxController { }, ); if (res is Map && res['status'] == 'success') { - Get.snackbar("Success", "Driver unblocked successfully"); + mySnackbarSuccess("Driver unblocked successfully"); fetchBlacklist(); // Refresh } else { - Get.snackbar("Error", res['message'] ?? "Failed to unblock driver"); + mySnackbarError(res['message'] ?? "Failed to unblock driver"); } } catch (e) { - Get.snackbar("Error", "Network error"); + mySnackbarError("Network error"); } } @@ -61,13 +61,13 @@ class QualityController extends GetxController { }, ); if (res is Map && res['status'] == 'success') { - Get.snackbar("Success", "Passenger unblocked successfully"); + mySnackbarSuccess("Passenger unblocked successfully"); fetchBlacklist(); // Refresh } else { - Get.snackbar("Error", res['message'] ?? "Failed to unblock passenger"); + mySnackbarError(res['message'] ?? "Failed to unblock passenger"); } } catch (e) { - Get.snackbar("Error", "Network error"); + mySnackbarError("Network error"); } } @@ -82,11 +82,11 @@ class QualityController extends GetxController { if (res is Map && res['status'] == 'success') { scorecardData = res['message']; } else { - Get.snackbar("Error", "Failed to fetch scorecard"); + mySnackbarError("Failed to fetch scorecard"); scorecardData = {}; } } catch (e) { - Get.snackbar("Error", "Network error"); + mySnackbarError("Network error"); scorecardData = {}; } finally { isLoading = false; @@ -94,9 +94,4 @@ class QualityController extends GetxController { } } - @override - void onInit() { - super.onInit(); - // fetchBlacklist() can be called when opening the page - } } diff --git a/siro_admin/lib/controller/admin/register_captain_controller.dart b/siro_admin/lib/controller/admin/register_captain_controller.dart index 9aa95ccc..627f89ac 100644 --- a/siro_admin/lib/controller/admin/register_captain_controller.dart +++ b/siro_admin/lib/controller/admin/register_captain_controller.dart @@ -1,16 +1,15 @@ import 'dart:convert'; -import 'dart:ffi'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import '../../constant/api_key.dart'; import '../../constant/box_name.dart'; -import '../../constant/colors.dart'; import '../../constant/info.dart'; import '../../constant/links.dart'; import '../../constant/style.dart'; import '../../main.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class RegisterCaptainController extends GetxController { @@ -102,7 +101,7 @@ class RegisterCaptainController extends GetxController { driverNotCompleteRegistration = d; update(); } else { - Get.snackbar(res, ''); + mySnackbarError(res); } } @@ -279,9 +278,7 @@ class RegisterCaptainController extends GetxController { await addRegistrationCarEgypt(); if (isCarSaved && isDriverSaved) { - Get.snackbar('added', '', - backgroundColor: - AppColor.greenColor); // Get.offAll(() => HomeCaptain()); + mySnackbarSuccess('added'); // Get.offAll(() => HomeCaptain()); // Get.offAll(() => HomeCaptain()); } } @@ -348,11 +345,9 @@ class RegisterCaptainController extends GetxController { // Handle response if (status1['status'] == 'success') { isDriverSaved = true; - Get.snackbar('Success', 'Driver data saved successfully', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('Driver data saved successfully'); } else { - Get.snackbar('Error', 'Failed to save driver data', - backgroundColor: Colors.red); + mySnackbarError('Failed to save driver data'); } } @@ -363,7 +358,7 @@ class RegisterCaptainController extends GetxController { "InspectionResult": responseCriminalRecordEgypt['InspectionResult'], }); if (res != 'failure') { - Get.snackbar('uploaded sucssefuly'.tr, ''); + mySnackbarSuccess('uploaded sucssefuly'.tr); } } @@ -394,8 +389,7 @@ class RegisterCaptainController extends GetxController { var status = jsonDecode(res); if (status['status'] == 'success') { isCarSaved = true; - Get.snackbar('Success', 'message', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('message'); } } catch (e) {} } @@ -557,7 +551,6 @@ class RegisterCaptainController extends GetxController { 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(); @@ -580,8 +573,7 @@ class RegisterCaptainController extends GetxController { update(); } else { - Get.snackbar('Error', "JSON string not found", - backgroundColor: AppColor.redColor); + mySnackbarError("JSON string not found"); } // Rest of your code... diff --git a/siro_admin/lib/controller/admin/security_v2_controller.dart b/siro_admin/lib/controller/admin/security_v2_controller.dart index 7ba14932..241799dd 100644 --- a/siro_admin/lib/controller/admin/security_v2_controller.dart +++ b/siro_admin/lib/controller/admin/security_v2_controller.dart @@ -1,8 +1,8 @@ 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/views/widgets/snackbar.dart'; import '../../print.dart'; class SecurityV2Controller extends GetxController { @@ -26,9 +26,7 @@ class SecurityV2Controller extends GetxController { if (res == 'failure' || res == 'token_expired') { Log.print('CRUD returned: $res'); - Get.snackbar("خطأ بالاتصال", "السيرفر أرجع: $res", - backgroundColor: const Color(0x88FF0000), - colorText: const Color(0xFFFFFFFF)); + mySnackbarError("السيرفر أرجع: $res"); auditLogs = []; } else if (res != null) { var d = res is String ? jsonDecode(res) : res; @@ -44,16 +42,12 @@ class SecurityV2Controller extends GetxController { } } else { Log.print('Status not success: ${d['status']}'); - Get.snackbar("خطأ من السيرفر", "${d['message'] ?? d['status']}", - backgroundColor: const Color(0x88FF0000), - colorText: const Color(0xFFFFFFFF)); + mySnackbarError("${d['message'] ?? d['status']}"); } } } catch (e) { Log.print('Error fetching audit logs: $e'); - Get.snackbar("خطأ برمجي", "$e", - backgroundColor: const Color(0x88FF0000), - colorText: const Color(0xFFFFFFFF)); + mySnackbarError("$e"); } isLoading = false; diff --git a/siro_admin/lib/controller/admin/staff_controller.dart b/siro_admin/lib/controller/admin/staff_controller.dart index 2f78444e..c56bd67b 100644 --- a/siro_admin/lib/controller/admin/staff_controller.dart +++ b/siro_admin/lib/controller/admin/staff_controller.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../constant/links.dart'; import '../functions/crud.dart'; -import '../functions/device_info.dart'; import '../../views/widgets/snackbar.dart'; class StaffController extends GetxController { @@ -53,10 +52,10 @@ class StaffController extends GetxController { _clearFields(); Get.back(); } else { - mySnackeBarError('فشل في إضافة الموظف. يرجى المحاولة لاحقاً'); + mySnackbarError('فشل في إضافة الموظف. يرجى المحاولة لاحقاً'); } } catch (e) { - mySnackeBarError('حدث خطأ: $e'); + mySnackbarError('حدث خطأ: $e'); } finally { isLoading = false; update(); diff --git a/siro_admin/lib/controller/admin/static_controller.dart b/siro_admin/lib/controller/admin/static_controller.dart index 05db94a6..4b629683 100644 --- a/siro_admin/lib/controller/admin/static_controller.dart +++ b/siro_admin/lib/controller/admin/static_controller.dart @@ -303,10 +303,11 @@ class StaticController extends GetxController { totalMonthlyPassengers = data[0]['totalMonthly'].toString(); } final spots = _generateSpots(data, 'day', 'totalPassengers', start, end); - if (isCompare) + if (isCompare) { chartDataPassengersCompare = spots; - else + } else { chartDataPassengers = spots; + } } // ─── Rides ──────────────────────────────────────────────── @@ -322,10 +323,11 @@ class StaticController extends GetxController { totalMonthlyRides = data[0]['totalMonthly'].toString(); } final spots = _generateSpots(data, 'day', 'totalRides', start, end); - if (isCompare) + if (isCompare) { chartDataRidesCompare = spots; - else + } else { chartDataRides = spots; + } } // ─── Drivers ────────────────────────────────────────────── diff --git a/siro_admin/lib/controller/admin/wallet_admin_controller.dart b/siro_admin/lib/controller/admin/wallet_admin_controller.dart index f26a5c3b..64445822 100644 --- a/siro_admin/lib/controller/admin/wallet_admin_controller.dart +++ b/siro_admin/lib/controller/admin/wallet_admin_controller.dart @@ -7,7 +7,6 @@ import '../../constant/api_key.dart'; import '../../constant/box_name.dart'; import '../../constant/links.dart'; import '../../main.dart'; -import '../../print.dart'; import '../functions/crud.dart'; class WalletAdminController extends GetxController { @@ -46,7 +45,7 @@ class WalletAdminController extends GetxController { driversWalletPoints[i]['email'].toString(), ); await Future.delayed(const Duration(seconds: 3)); - } on FormatException catch (e) { + } on FormatException { // Handle the error or rethrow the exception as needed } } diff --git a/siro_admin/lib/controller/auth/login_controller.dart b/siro_admin/lib/controller/auth/login_controller.dart index e750322e..4673300b 100644 --- a/siro_admin/lib/controller/auth/login_controller.dart +++ b/siro_admin/lib/controller/auth/login_controller.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'otp_helper.dart'; class OtpVerificationAdmin extends StatefulWidget { final String phone; - const OtpVerificationAdmin({required this.phone}); + const OtpVerificationAdmin({super.key, required this.phone}); @override State createState() => _OtpVerificationAdminState(); diff --git a/siro_admin/lib/controller/auth/otp_helper.dart b/siro_admin/lib/controller/auth/otp_helper.dart index 757e551c..48da7e67 100644 --- a/siro_admin/lib/controller/auth/otp_helper.dart +++ b/siro_admin/lib/controller/auth/otp_helper.dart @@ -37,12 +37,12 @@ class OtpHelper extends GetxController { mySnackbarSuccess('تم إرسال رمز التحقق إلى رقمك عبر WhatsApp'); return true; } else { - mySnackeBarError('حدث خطأ من الخادم. حاول مجددًا.'); + mySnackbarError('حدث خطأ من الخادم. حاول مجددًا.'); return false; } } catch (e) { Log.print('OTP SEND ERROR: $e'); - mySnackeBarError('حدث خطأ أثناء الإرسال: $e'); + mySnackbarError('حدث خطأ أثناء الإرسال: $e'); return false; } } @@ -68,14 +68,14 @@ class OtpHelper extends GetxController { mySnackbarSuccess('تم التحقق من الرقم بنجاح'); await checkAdminLogin(); } else { - mySnackeBarError(response['message'] ?? 'فشل في التحقق.'); + mySnackbarError(response['message'] ?? 'فشل في التحقق.'); } } else { - mySnackeBarError('فشل من الخادم. حاول مرة أخرى.'); + mySnackbarError('فشل من الخادم. حاول مرة أخرى.'); } } catch (e) { Log.print('OTP VERIFY ERROR: $e'); - mySnackeBarError('خطأ في التحقق: $e'); + mySnackbarError('خطأ في التحقق: $e'); } } @@ -110,7 +110,7 @@ class OtpHelper extends GetxController { } } catch (e) { Log.print('LOGIN ERROR: $e'); - mySnackeBarError('حدث خطأ أثناء تسجيل الدخول: $e'); + mySnackbarError('حدث خطأ أثناء تسجيل الدخول: $e'); return false; } } @@ -141,7 +141,7 @@ class OtpHelper extends GetxController { } } catch (e) { Log.print('OTP VERIFY LOGIN ERROR: $e'); - mySnackeBarError('خطأ في التحقق من الرمز: $e'); + mySnackbarError('خطأ في التحقق من الرمز: $e'); } } @@ -161,8 +161,9 @@ class OtpHelper extends GetxController { await box.write('admin_role', role); Log.print('Admin role saved: $role'); } - if (data['phone'] != null) + if (data['phone'] != null) { await box.write(BoxName.adminPhone, data['phone']); + } } await box.write(BoxName.phoneVerified, true); @@ -197,7 +198,7 @@ class OtpHelper extends GetxController { Get.back(); verifyLoginOtp(phone, otpCode, password, fingerprint); } else { - mySnackeBarError('الرجاء إدخال رمز صحيح'); + mySnackbarError('الرجاء إدخال رمز صحيح'); } }, ); diff --git a/siro_admin/lib/controller/auth/register_controller.dart b/siro_admin/lib/controller/auth/register_controller.dart index 427e2a2f..c544de02 100644 --- a/siro_admin/lib/controller/auth/register_controller.dart +++ b/siro_admin/lib/controller/auth/register_controller.dart @@ -2,7 +2,8 @@ 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'; // للوصول لـ box +import 'package:siro_admin/main.dart'; +import 'package:siro_admin/views/widgets/snackbar.dart'; class AdminRegisterController extends GetxController { final nameCtrl = TextEditingController(); @@ -32,20 +33,14 @@ class AdminRegisterController extends GetxController { if (response != 'failure') { if (response['status'] == 'pending') { - Get.snackbar('نجاح', response['message'] ?? 'تم تقديم الطلب بنجاح', - backgroundColor: Colors.green.withOpacity(0.8), - colorText: Colors.white); + mySnackbarSuccess(response['message'] ?? 'تم تقديم الطلب بنجاح'); Future.delayed(const Duration(seconds: 2), () => Get.back()); } else { - Get.snackbar('خطأ', 'حدث خطأ غير متوقع', - backgroundColor: Colors.red.withOpacity(0.8), - colorText: Colors.white); + mySnackbarError('حدث خطأ غير متوقع'); } } } catch (e) { - Get.snackbar('خطأ', 'فشل في الاتصال بالخادم', - backgroundColor: Colors.red.withOpacity(0.8), - colorText: Colors.white); + mySnackbarError('فشل في الاتصال بالخادم'); } finally { isLoading.value = false; } diff --git a/siro_admin/lib/controller/bank_account/payout.dart b/siro_admin/lib/controller/bank_account/payout.dart index 0001a96c..e35bf5f8 100644 --- a/siro_admin/lib/controller/bank_account/payout.dart +++ b/siro_admin/lib/controller/bank_account/payout.dart @@ -6,8 +6,8 @@ import 'package:http/http.dart' as http; import '../../../constant/links.dart'; import '../../constant/api_key.dart'; import '../../constant/box_name.dart'; -import '../../constant/colors.dart'; import '../../main.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class PaymobPayout extends GetxController { @@ -65,13 +65,10 @@ class PaymobPayout extends GetxController { 'passengerID': 'admin', 'driverID': box.read(BoxName.driverID).toString(), }); - Get.snackbar('Transaction successful'.tr, - '${'Transaction successful'.tr} ${dec['amount']}', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('${'Transaction successful'.tr} ${dec['amount']}'); // Get.find().getCaptainWalletFromRide(); } else if (dec['disbursement_status'] == 'failed') { - Get.snackbar('Transaction failed'.tr, 'Transaction failed'.tr, - backgroundColor: AppColor.redColor); + mySnackbarError('Transaction failed'.tr); } } @@ -90,7 +87,7 @@ class PaymobPayout extends GetxController { "bank_code": bankCode, //"CIB", "bank_transaction_type": "cash_transfer" }; - var res = await http + await http .post( Uri.parse('https://payouts.paymobsolutions.com/api/secure/disburse/'), headers: headers, diff --git a/siro_admin/lib/controller/drivers/driver_not_active_controller.dart b/siro_admin/lib/controller/drivers/driver_not_active_controller.dart index 09a60c34..071b4391 100644 --- a/siro_admin/lib/controller/drivers/driver_not_active_controller.dart +++ b/siro_admin/lib/controller/drivers/driver_not_active_controller.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; import '../../constant/links.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class DriverController extends GetxController { @@ -17,7 +18,7 @@ class DriverController extends GetxController { drivers = (res)['message']; update(['drivers']); // تحديث الـ UI } else { - Get.snackbar('Error', 'Failed to load drivers'); + mySnackbarError('Failed to load drivers'); } } @@ -31,7 +32,7 @@ class DriverController extends GetxController { driverDetails = (res)['message']; update(['driverDetails']); // تحديث صفحة التفاصيل } else { - Get.snackbar('Error', 'Failed to load driver details'); + mySnackbarError('Failed to load driver details'); } } } diff --git a/siro_admin/lib/controller/drivers/driverthebest.dart b/siro_admin/lib/controller/drivers/driverthebest.dart index a6298c8c..8fbbbcfd 100644 --- a/siro_admin/lib/controller/drivers/driverthebest.dart +++ b/siro_admin/lib/controller/drivers/driverthebest.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'package:get/get.dart'; -import '../../constant/colors.dart'; import '../../constant/links.dart'; +import '../../views/widgets/snackbar.dart'; import '../functions/crud.dart'; class Driverthebest extends GetxController { @@ -15,7 +15,7 @@ class Driverthebest extends GetxController { driver = jsonDecode(res)['message']; update(); } else { - Get.snackbar('error', '', backgroundColor: AppColor.redColor); + mySnackbarError('error'); } } @@ -35,7 +35,7 @@ class DriverTheBestGizaController extends GetxController { driver = jsonDecode(res)['message']; update(); } else { - Get.snackbar('error', '', backgroundColor: AppColor.redColor); + mySnackbarError('error'); } } @@ -56,7 +56,7 @@ class DriverTheBestAlexandriaController extends GetxController { driver = jsonDecode(res)['message']; update(); } else { - Get.snackbar('error', '', backgroundColor: AppColor.redColor); + mySnackbarError('error'); } } diff --git a/siro_admin/lib/controller/employee_controller/employee_controller.dart b/siro_admin/lib/controller/employee_controller/employee_controller.dart index f0294ea6..cfb3df4a 100644 --- a/siro_admin/lib/controller/employee_controller/employee_controller.dart +++ b/siro_admin/lib/controller/employee_controller/employee_controller.dart @@ -3,9 +3,9 @@ import 'dart:math'; import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/colors.dart'; import 'package:siro_admin/constant/links.dart'; import 'package:siro_admin/controller/functions/crud.dart'; +import 'package:siro_admin/views/widgets/snackbar.dart'; class EmployeeController extends GetxController { List employee = []; @@ -19,7 +19,7 @@ class EmployeeController extends GetxController { fetchEmployee() async { var res = await CRUD().get(link: AppLink.getEmployee, payload: {}); if (res is String && (res == 'failure' || res == 'token_expired')) { - Get.snackbar('error', 'Failed to load employees', backgroundColor: AppColor.redColor); + mySnackbarError('Failed to load employees'); return; } @@ -28,7 +28,7 @@ class EmployeeController extends GetxController { employee = jsonData['message']; update(); } catch (e) { - Get.snackbar('error', 'Invalid server response', backgroundColor: AppColor.redColor); + mySnackbarError('Invalid server response'); } } @@ -59,8 +59,7 @@ class EmployeeController extends GetxController { // You can handle the success case here, such as showing a success message Get.back(); - Get.snackbar('Success', 'Employee added successfully', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('Employee added successfully'); name.clear(); education.clear(); site.clear(); @@ -69,8 +68,7 @@ class EmployeeController extends GetxController { fetchEmployee(); } else { // Handle the error case by showing a snackbar with an error message - Get.snackbar('Error', 'Failed to add employee', - backgroundColor: AppColor.redColor); + mySnackbarError('Failed to add employee'); } } diff --git a/siro_admin/lib/controller/firebase/firbase_messge.dart b/siro_admin/lib/controller/firebase/firbase_messge.dart index f4912797..59f43c7d 100644 --- a/siro_admin/lib/controller/firebase/firbase_messge.dart +++ b/siro_admin/lib/controller/firebase/firbase_messge.dart @@ -1,27 +1,22 @@ import 'dart:convert'; import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:secure_string_operations/secure_string_operations.dart'; import 'package:siro_admin/constant/info.dart'; -import 'package:siro_admin/env/env.dart'; import '../../constant/api_key.dart'; import '../../constant/box_name.dart'; import '../../constant/char_map.dart'; -import '../../constant/colors.dart'; import '../../constant/links.dart'; import '../../constant/style.dart'; import '../../main.dart'; import '../../print.dart'; import '../../views/widgets/elevated_btn.dart'; -import '../functions/encrypt_decrypt.dart'; +import '../../views/widgets/snackbar.dart'; import '../notification_controller.dart'; -import 'local_notification.dart'; import 'notification_service.dart'; -import 'token_access.dart'; class FirebaseMessagesController extends GetxController { final fcmToken = FirebaseMessaging.instance; @@ -53,7 +48,7 @@ class FirebaseMessagesController extends GetxController { await messaging.requestPermission(); } else if (Platform.isIOS) { // Request permission for iOS - NotificationSettings settings = await messaging.requestPermission( + await messaging.requestPermission( alert: true, announcement: true, badge: true, @@ -107,7 +102,7 @@ class FirebaseMessagesController extends GetxController { ); var jsonResponse = jsonDecode(res.body); - Log.print('jsonResponse: ${jsonResponse}'); + Log.print('jsonResponse: $jsonResponse'); if (jsonResponse['status'] == 'success') { // var newData = jsonResponse['data'] as List; // Log.print('newData: ${newData}'); @@ -185,7 +180,7 @@ class FirebaseMessagesController extends GetxController { // } isSendingNotifications = false; - Get.snackbar("Success", "All notifications sent!"); + mySnackbarSuccess("All notifications sent!"); } bool isSendingNotificationsPassenger = false; @@ -204,7 +199,7 @@ class FirebaseMessagesController extends GetxController { // } isSendingNotificationsPassenger = false; - Get.snackbar("Success", "All notifications sent!"); + mySnackbarSuccess("All notifications sent!"); } Future getAllTokenPassengers() async { @@ -241,9 +236,7 @@ class FirebaseMessagesController extends GetxController { FirebaseMessaging.onMessage.listen((RemoteMessage message) { // If the app is in the background or terminated, show a system tray message - RemoteNotification? notification = message.notification; - AndroidNotification? android = notification?.android; - // if (notification != null && android != null) { + // if (message.notification != null) { if (message.data.isNotEmpty && message.notification != null) { fireBaseTitles(message); } @@ -267,26 +260,8 @@ class FirebaseMessagesController extends GetxController { } } - SnackbarController driverAppliedTripSnakBar() { - return Get.snackbar( - 'Driver Applied the Ride for You'.tr, - '', - colorText: AppColor.greenColor, - duration: const Duration(seconds: 3), - snackPosition: SnackPosition.TOP, - titleText: Text( - 'Applied'.tr, - style: const TextStyle(color: AppColor.redColor), - ), - messageText: Text( - 'Driver Applied the Ride for You'.tr, - style: AppStyle.title, - ), - icon: const Icon(Icons.approval), - shouldIconPulse: true, - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(16), - ); + void driverAppliedTripSnakBar() { + mySnackbarSuccess('Driver Applied the Ride for You'.tr); } Future passengerDialog(String message) { diff --git a/siro_admin/lib/controller/functions/crud.dart b/siro_admin/lib/controller/functions/crud.dart index 8ea97e0b..cc6b2be1 100644 --- a/siro_admin/lib/controller/functions/crud.dart +++ b/siro_admin/lib/controller/functions/crud.dart @@ -14,9 +14,9 @@ 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 'security_checks.dart'; import 'ssl_pinning.dart'; class CRUD { @@ -36,7 +36,7 @@ class CRUD { 'password': AK.passnpassenger, 'aud': '${AK.allowed}$dev', }; - Log.print('payload: ${payload}'); + Log.print('payload: $payload'); var response1 = await _client.post( Uri.parse(AppLink.loginJwtDriver), body: payload, @@ -46,7 +46,7 @@ class CRUD { final decodedResponse1 = jsonDecode(response1.body); final jwt = decodedResponse1['jwt']; - Log.print('jwt: ${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(); @@ -334,11 +334,11 @@ class CRUD { Future postWallet( {required String link, Map? payload}) async { var s = await getJwtWallet(); - Log.print('jwt: ${s}'); + Log.print('jwt: $s'); final hmac = box.read(BoxName.hmac); - Log.print('hmac: ${hmac}'); + Log.print('hmac: $hmac'); var url = Uri.parse(link); - Log.print('url: ${url}'); + Log.print('url: $url'); // إضافة الـ HMAC للـ payload لزيادة التوافقية if (payload != null && hmac != null) { @@ -389,9 +389,9 @@ class CRUD { link: AppLink.send_whatsapp_message, payload: {'receiver': to, 'message': message}); if (res != 'failure') { - Get.snackbar('Success', 'Message sent successfully'); + mySnackbarSuccess('Message sent successfully'); } else { - Get.snackbar('Error', 'Failed to send message'); + mySnackbarError('Failed to send message'); } } @@ -399,7 +399,6 @@ class CRUD { required String channelName, required String uid, }) async { - var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver); var res = await _client.get( Uri.parse( 'https://repulsive-pig-rugby-shirt.cyclic.app/token?channelName=$channelName'), @@ -450,15 +449,6 @@ class CRUD { } Future allMethodForAI(String prompt, driverID, imagePath) async { - // await ImageController().choosImage(linkPHP, imagePath); - Future.delayed(const Duration(seconds: 2)); - var extractedString = await arabicTextExtractByVisionAndAI( - imagePath: imagePath, driverID: driverID); - var json = jsonDecode(extractedString); - var textValues = getAllTextValuesWithLineNumbers(json); - // List textValues = getAllTextValues(json); - - // await AI().geminiAiExtraction(prompt, textValues); } Map>> getAllTextValuesWithLineNumbers( @@ -616,7 +606,7 @@ class CRUD { "receiver": phone }); - var res = await _client.post( + await _client.post( Uri.parse(AppLink.sendSms), body: body, headers: headers, diff --git a/siro_admin/lib/controller/functions/device_info.dart b/siro_admin/lib/controller/functions/device_info.dart index ec7f2331..dc359796 100644 --- a/siro_admin/lib/controller/functions/device_info.dart +++ b/siro_admin/lib/controller/functions/device_info.dart @@ -12,7 +12,7 @@ import '../../print.dart'; class DeviceHelper { static Future getDeviceFingerprint() async { final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); - var deviceData; + Map deviceData; try { if (Platform.isAndroid) { @@ -58,9 +58,6 @@ class SecurityHelper { bool isNotTrust = false; bool isJailBroken = false; bool isRealDevice = true; - bool isOnExternalStorage = false; - bool checkForIssues = false; - bool isDevMode = false; bool isTampered = false; String bundleId = ""; @@ -68,15 +65,6 @@ class SecurityHelper { isNotTrust = await JailbreakRootDetection.instance.isNotTrust; isJailBroken = await JailbreakRootDetection.instance.isJailBroken; isRealDevice = await JailbreakRootDetection.instance.isRealDevice; - isOnExternalStorage = - await JailbreakRootDetection.instance.isOnExternalStorage; - - List issues = - await JailbreakRootDetection.instance.checkForIssues; - checkForIssues = issues.isNotEmpty; - - isDevMode = await JailbreakRootDetection.instance.isDevMode; - PackageInfo packageInfo = await PackageInfo.fromPlatform(); bundleId = packageInfo.packageName; if (bundleId.isNotEmpty) { diff --git a/siro_admin/lib/controller/functions/digit_obsecur_formate.dart b/siro_admin/lib/controller/functions/digit_obsecur_formate.dart index b5972e4f..11394bd4 100644 --- a/siro_admin/lib/controller/functions/digit_obsecur_formate.dart +++ b/siro_admin/lib/controller/functions/digit_obsecur_formate.dart @@ -26,8 +26,6 @@ class DigitObscuringFormatter extends TextInputFormatter { TextSelection updateCursorPosition( String maskedText, TextSelection currentSelection) { final cursorPosition = currentSelection.baseOffset; - final cursorOffset = - currentSelection.extentOffset - currentSelection.baseOffset; final totalDigits = maskedText.length; const visibleDigits = 4; final hiddenDigits = totalDigits - visibleDigits * 2; diff --git a/siro_admin/lib/controller/functions/encrypt_decrypt.dart b/siro_admin/lib/controller/functions/encrypt_decrypt.dart index 4e56d253..45ef6e1c 100644 --- a/siro_admin/lib/controller/functions/encrypt_decrypt.dart +++ b/siro_admin/lib/controller/functions/encrypt_decrypt.dart @@ -4,8 +4,6 @@ import 'package:secure_string_operations/secure_string_operations.dart'; import '../../constant/char_map.dart'; import '../../env/env.dart'; -import '../../main.dart'; -import '../../print.dart'; class EncryptionHelper { static EncryptionHelper? _instance; diff --git a/siro_admin/lib/controller/functions/log_out.dart b/siro_admin/lib/controller/functions/log_out.dart index 9afa5828..c9f07587 100644 --- a/siro_admin/lib/controller/functions/log_out.dart +++ b/siro_admin/lib/controller/functions/log_out.dart @@ -10,6 +10,7 @@ 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 { @@ -20,12 +21,11 @@ class LogOutController extends GetxController { Future deleteMyAccountDriver(String id) async { await CRUD().post(link: AppLink.removeUser, payload: {'id': id}).then( - (value) => Get.snackbar('Deleted'.tr, 'Your Account is Deleted', - backgroundColor: AppColor.redColor)); + (value) => mySnackbarWarning('Your Account is Deleted'.tr)); } checkBeforeDelete() async { - var res = await CRUD().post( + await CRUD().post( link: AppLink.deletecaptainAccounr, payload: {'id': box.read(BoxName.driverID)}).then((value) => exit(0)); } @@ -88,7 +88,7 @@ class LogOutController extends GetxController { ), ElevatedButton( style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColor.redColor), + backgroundColor: WidgetStateProperty.all(AppColor.redColor), ), onPressed: () { // box.remove(BoxName.agreeTerms); @@ -131,7 +131,7 @@ class LogOutController extends GetxController { ), ElevatedButton( style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(AppColor.redColor), + backgroundColor: WidgetStateProperty.all(AppColor.redColor), ), onPressed: () { // box.remove(BoxName.agreeTerms); @@ -172,9 +172,7 @@ class LogOutController extends GetxController { 'email': box.read(BoxName.email), }); } else { - Get.snackbar('Email Wrong'.tr, 'Email you inserted is Wrong.'.tr, - snackPosition: SnackPosition.BOTTOM, - backgroundColor: AppColor.redColor); + mySnackbarError('Email you inserted is Wrong.'.tr); } } } diff --git a/siro_admin/lib/controller/functions/security_checks.dart b/siro_admin/lib/controller/functions/security_checks.dart index 11e3ab31..e485f7c9 100755 --- a/siro_admin/lib/controller/functions/security_checks.dart +++ b/siro_admin/lib/controller/functions/security_checks.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; diff --git a/siro_admin/lib/controller/functions/upload_image copy.dart b/siro_admin/lib/controller/functions/upload_image copy.dart index 49ccc57f..62bfa8e3 100644 --- a/siro_admin/lib/controller/functions/upload_image copy.dart +++ b/siro_admin/lib/controller/functions/upload_image copy.dart @@ -5,7 +5,6 @@ import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:path/path.dart'; import 'package:image/image.dart' as img; import 'package:path_provider/path_provider.dart' as path_provider; @@ -16,6 +15,7 @@ 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 { @@ -141,7 +141,7 @@ class ImageController extends GetxController { File compressedImage = await compressImage(processedImage); print('link =$link'); - Log.print('link: ${link}'); + Log.print('link: $link'); await uploadImage( compressedImage, @@ -153,8 +153,7 @@ class ImageController extends GetxController { ); } catch (e) { print('Error in choosImage: $e'); - Get.snackbar('Image Upload Failed'.tr, e.toString(), - backgroundColor: AppColor.redColor); + mySnackbarError('Image Upload Failed'.tr); } finally { isloading = false; update(); @@ -229,8 +228,7 @@ class ImageController extends GetxController { link, ); } catch (e) { - Get.snackbar('Image Upload Failed'.tr, e.toString(), - backgroundColor: AppColor.redColor); + mySnackbarError('Image Upload Failed'.tr); } finally { isloading = false; update(); @@ -243,7 +241,7 @@ class ImageController extends GetxController { 'POST', Uri.parse(link), ); - Log.print('request: ${request}'); + Log.print('request: $request'); var length = await file.length(); var stream = http.ByteStream(file.openRead()); final headers = { @@ -251,14 +249,8 @@ class ImageController extends GetxController { 'Bearer ${r(box.read(BoxName.jwt)).split(AppInformation.addd)[0]}', // 'X-HMAC-Auth': '${box.read(BoxName.hmac)}', }; - Log.print('headers: ${headers}'); + Log.print('headers: $headers'); - var multipartFile = http.MultipartFile( - 'image', - stream, - length, - filename: basename(file.path), - ); request.headers.addAll(headers); // Set the file name to the driverID @@ -278,8 +270,7 @@ class ImageController extends GetxController { if (res.statusCode == 200) { Log.print('jsonDecode(res.body): ${jsonDecode(res.body)}'); if (jsonDecode(res.body)['status'] == 'Image uploaded successfully!') { - Get.snackbar('Success'.tr, 'Image uploaded successfully!'.tr, - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('Image uploaded successfully!'.tr); } return jsonDecode(res.body); } else { @@ -330,8 +321,7 @@ class ImageController extends GetxController { link, ); } catch (e) { - Get.snackbar('Image Upload Failed'.tr, e.toString(), - backgroundColor: AppColor.redColor); + mySnackbarError('Image Upload Failed'.tr); } finally { isloading = false; update(); @@ -346,12 +336,6 @@ class ImageController extends GetxController { 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()))}', diff --git a/siro_admin/lib/controller/functions/wallet.dart b/siro_admin/lib/controller/functions/wallet.dart index 5193920a..b54f2c6f 100644 --- a/siro_admin/lib/controller/functions/wallet.dart +++ b/siro_admin/lib/controller/functions/wallet.dart @@ -1,10 +1,9 @@ import 'dart:convert'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/colors.dart'; import '../../constant/links.dart'; -import '../firebase/firbase_messge.dart'; +import '../../views/widgets/snackbar.dart'; import 'crud.dart'; class WalletController extends GetxController { @@ -35,11 +34,9 @@ class WalletController extends GetxController { // token, // Access token correctly // 'ding.wav', // ); - Get.snackbar('success', 'addPaymentToDriver', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('addPaymentToDriver'); } else { - Get.snackbar('error', 'addPaymentToDriver', - backgroundColor: AppColor.redColor); + mySnackbarError('addPaymentToDriver'); } } @@ -68,10 +65,9 @@ class WalletController extends GetxController { 'phone': phone, }); if (res != 'failure') { - Get.snackbar('success', 'addDrivergift300', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('addDrivergift300'); } else { - Get.snackbar('error', res, backgroundColor: AppColor.redColor); + mySnackbarError(res); } } @@ -86,11 +82,9 @@ class WalletController extends GetxController { 'driverId': driverID.toString(), }); if (res != 'failure') { - Get.snackbar('success', 'addSeferWallet', - backgroundColor: AppColor.greenColor); + mySnackbarSuccess('addSeferWallet'); } else { - Get.snackbar('error', 'addSeferWallet', - backgroundColor: AppColor.redColor); + mySnackbarError('addSeferWallet'); } } } diff --git a/siro_admin/lib/controller/notification_controller.dart b/siro_admin/lib/controller/notification_controller.dart index 80f8000c..72f2448a 100644 --- a/siro_admin/lib/controller/notification_controller.dart +++ b/siro_admin/lib/controller/notification_controller.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import '../views/widgets/snackbar.dart'; import 'firebase/notification_service.dart'; class NotificationController extends GetxController { @@ -131,13 +132,7 @@ class NotificationController extends GetxController { onPressed: () async { if (titleController.text.trim().isEmpty || bodyController.text.trim().isEmpty) { - Get.snackbar( - "تنبيه", - "الرجاء تعبئة جميع الحقول", - backgroundColor: Colors.amber.withOpacity(0.8), - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - ); + mySnackbarWarning("الرجاء تعبئة جميع الحقول"); return; } @@ -171,13 +166,7 @@ class NotificationController extends GetxController { /// تنفيذ عملية الإرسال الفعلية Future _processSending(String target) async { // إظهار تنبيه بدء العملية - Get.snackbar( - "جاري الإرسال", - "يتم إرسال الإشعار لـ $target...", - backgroundColor: _primaryAccent.withOpacity(0.2), - colorText: Colors.white, - duration: const Duration(seconds: 2), - ); + mySnackbarInfo("يتم إرسال الإشعار لـ $target..."); try { // استدعاء خدمة الإرسال @@ -189,19 +178,9 @@ class NotificationController extends GetxController { category: 'fromAdmin', ); - Get.snackbar( - "نجاح", - "تم إرسال الإشعار بنجاح", - backgroundColor: Colors.green.withOpacity(0.5), - colorText: Colors.white, - ); + mySnackbarSuccess("تم إرسال الإشعار بنجاح"); } catch (e) { - Get.snackbar( - "خطأ", - "فشل إرسال الإشعار: $e", - backgroundColor: Colors.red.withOpacity(0.5), - colorText: Colors.white, - ); + mySnackbarError("فشل إرسال الإشعار: $e"); } } diff --git a/siro_admin/lib/controller/server/server_monitor_controller.dart b/siro_admin/lib/controller/server/server_monitor_controller.dart index e42f933c..7763b2bd 100644 --- a/siro_admin/lib/controller/server/server_monitor_controller.dart +++ b/siro_admin/lib/controller/server/server_monitor_controller.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:get/get.dart'; import 'package:siro_admin/constant/links.dart'; -import '../../print.dart'; // --- Models --- diff --git a/siro_admin/lib/main.dart b/siro_admin/lib/main.dart index 89ce05ba..c15286e4 100644 --- a/siro_admin/lib/main.dart +++ b/siro_admin/lib/main.dart @@ -78,7 +78,6 @@ class MainApp extends StatelessWidget { primary: AppColor.accent, secondary: AppColor.accent, surface: AppColor.surface, - background: AppColor.bg, error: AppColor.danger, ), dividerColor: AppColor.divider, diff --git a/siro_admin/lib/views/admin/admin_home_page.dart b/siro_admin/lib/views/admin/admin_home_page.dart index 2ee7bbba..d3e3e180 100644 --- a/siro_admin/lib/views/admin/admin_home_page.dart +++ b/siro_admin/lib/views/admin/admin_home_page.dart @@ -1,5 +1,4 @@ import 'dart:math'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; @@ -10,21 +9,19 @@ import 'package:siro_admin/views/admin/quality/blacklist_page.dart'; import '../../constant/box_name.dart'; import '../../constant/colors.dart'; -import '../../constant/style.dart'; import '../../controller/admin/dashboard_controller.dart'; import '../../controller/admin/static_controller.dart'; import '../../controller/functions/crud.dart'; import '../../controller/notification_controller.dart'; import '../../main.dart'; import '../../print.dart'; +import '../widgets/snackbar.dart'; import '../invoice/invoice_list_page.dart'; import 'captain/captain.dart'; import 'captain/syrian_driver_not_active.dart'; import 'drivers/monitor_ride.dart'; import 'employee/employee_page.dart'; -import 'enceypt/driver_fingerprint_migration.dart'; import 'enceypt/encrypt.dart'; -import 'enceypt/fingerprint_migration.dart'; import 'error/error/error_page.dart'; import 'packages.dart'; import 'passenger/passenger.dart'; @@ -62,7 +59,6 @@ class _AdminHomePageState extends State static const Color _surface = AppColor.surface; static const Color _surfaceElevated = AppColor.surfaceElevated; static const Color _accent = AppColor.accent; - static const Color _accentSoft = AppColor.accentSoft; static const Color _accentBorder = AppColor.accentBorder; static const Color _danger = AppColor.danger; static const Color _warning = AppColor.warning; @@ -135,8 +131,9 @@ class _AdminHomePageState extends State delegate: SliverChildBuilderDelegate( (context, index) { final category = categories[index]; - if (category.items.isEmpty) + if (category.items.isEmpty) { return const SizedBox.shrink(); + } return AnimationConfiguration.staggeredList( position: index, @@ -792,8 +789,8 @@ class _AdminHomePageState extends State ActionCategory( title: 'المالية والإدارة', items: [ - ActionItem('الإدارة المالية V2', Icons.account_balance_rounded, _accent, - () => Get.to(() => const FinancialV2Page())), + ActionItem('الإدارة المالية V2', Icons.account_balance_rounded, + _accent, () => Get.to(() => const FinancialV2Page())), ActionItem('المحفظة', Icons.account_balance_wallet_rounded, _accent, () => Get.to(() => Wallet())), ActionItem('هدية 300', Icons.card_giftcard_rounded, _warning, @@ -839,10 +836,10 @@ class _AdminHomePageState extends State () => Get.to(() => ServerMonitorPage())), ActionItem('سجل الأخطاء', Icons.error_outline_rounded, _danger, () => Get.to(() => ErrorListPage())), - ActionItem('encrypt fp', Icons.error_outline_rounded, _danger, - () => Get.to(() => FingerprintMigrationTool())), - ActionItem('encrypt fp drivers', Icons.error_outline_rounded, - _danger, () => Get.to(() => DriverFingerprintMigrationTool())), + // ActionItem('encrypt fp', Icons.error_outline_rounded, _danger, + // () => Get.to(() => FingerprintMigrationTool())), + // ActionItem('encrypt fp drivers', Icons.error_outline_rounded, + // _danger, () => Get.to(() => DriverFingerprintMigrationTool())), ActionItem( 'أداة التشفير', Icons.lock_rounded, @@ -952,7 +949,8 @@ class _AdminHomePageState extends State color: const Color(0xFF4CAF50).withAlpha(30), // ~0.12 opacity shape: BoxShape.circle, border: Border.all( - color: const Color(0xFF4CAF50).withAlpha(64)), // ~0.25 opacity + color: const Color(0xFF4CAF50) + .withAlpha(64)), // ~0.25 opacity ), child: const Icon(Icons.message_rounded, color: Color(0xFF4CAF50), size: 28), @@ -1029,20 +1027,11 @@ class _AdminHomePageState extends State Get.back(); var driverPhones = box .read(BoxName.tokensDrivers)['message'] as List?; - if (driverPhones == null || driverPhones.isEmpty) + if (driverPhones == null || driverPhones.isEmpty) { return; + } - Get.snackbar( - 'بدأ الإرسال', - 'سيتم الإرسال في الخلفية', - backgroundColor: - const Color(0xFF4CAF50).withOpacity(0.15), - colorText: _textPrimary, - borderRadius: 12, - margin: const EdgeInsets.all(16), - icon: const Icon(Icons.check_circle_rounded, - color: Color(0xFF4CAF50)), - ); + mySnackbarInfo('سيتم الإرسال في الخلفية'); for (var driverData in driverPhones) { if (driverData['phone'] != null) { diff --git a/siro_admin/lib/views/admin/captain/captain.dart b/siro_admin/lib/views/admin/captain/captain.dart index 6cf84cdc..6728c427 100644 --- a/siro_admin/lib/views/admin/captain/captain.dart +++ b/siro_admin/lib/views/admin/captain/captain.dart @@ -15,12 +15,12 @@ class CaptainsPage extends StatelessWidget { Get.put(CaptainAdminController()); final TextEditingController searchController = TextEditingController(); - String myPhone = box.read(BoxName.adminPhone).toString(); - bool isSuperAdmin = false; + final String myPhone = box.read(BoxName.adminPhone).toString(); + bool get isSuperAdmin => + myPhone == '963942542053' || myPhone == '963992952235'; @override Widget build(BuildContext context) { - isSuperAdmin = myPhone == '963942542053' || myPhone == '963992952235'; return MyScafolld( title: 'Search for Captain'.tr, diff --git a/siro_admin/lib/views/admin/captain/captain_details.dart b/siro_admin/lib/views/admin/captain/captain_details.dart index 3e49b673..71df5a28 100644 --- a/siro_admin/lib/views/admin/captain/captain_details.dart +++ b/siro_admin/lib/views/admin/captain/captain_details.dart @@ -4,11 +4,11 @@ import 'package:get/get.dart'; import '../../../constant/box_name.dart'; import '../../../constant/colors.dart'; import '../../../controller/admin/captain_admin_controller.dart'; -import '../../../controller/firebase/firbase_messge.dart'; import '../../../main.dart'; // Import main to access myPhone import '../../widgets/elevated_btn.dart'; import '../../widgets/my_scafold.dart'; import '../../widgets/my_textField.dart'; +import '../../widgets/snackbar.dart'; import '../quality/driver_scorecard_page.dart'; import 'form_captain.dart'; @@ -387,8 +387,7 @@ class CaptainDetailsPage extends StatelessWidget { // data['passengerToken'] ?? '', // Safety check // 'order.wav'); Get.back(); - Get.snackbar("Success", "Notification Sent", - backgroundColor: Colors.green.withOpacity(0.2)); + mySnackbarSuccess("Notification Sent"); } }, ), @@ -413,8 +412,7 @@ class CaptainDetailsPage extends StatelessWidget { // Call delete function here // controller.deleteCaptain(user['id']); Get.back(); - Get.snackbar("Deleted", "Captain has been removed", - backgroundColor: Colors.red.withOpacity(0.2)); + mySnackbarWarning("Captain has been removed"); }, child: Text('Delete'.tr, style: const TextStyle(color: Colors.white)), ), diff --git a/siro_admin/lib/views/admin/captain/form_captain.dart b/siro_admin/lib/views/admin/captain/form_captain.dart index dbde527f..0d914154 100644 --- a/siro_admin/lib/views/admin/captain/form_captain.dart +++ b/siro_admin/lib/views/admin/captain/form_captain.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/controller/functions/crud.dart'; import '../../../constant/links.dart'; -import '../../../constant/style.dart'; import '../../../controller/admin/captain_admin_controller.dart'; import '../../widgets/elevated_btn.dart'; import '../../widgets/my_scafold.dart'; import '../../widgets/my_textField.dart'; +import '../../widgets/snackbar.dart'; class FormCaptain extends StatefulWidget { const FormCaptain({super.key}); @@ -69,7 +69,7 @@ class _FormCaptainState extends State { print('Updating data: $updatedData'); Get.back(); // Go back after saving - Get.snackbar('Success', 'Captain data updated successfully!'); + mySnackbarSuccess('Captain data updated successfully!'); } // controller.updateCaptain(updatedData); } diff --git a/siro_admin/lib/views/admin/captain/register_captain.dart b/siro_admin/lib/views/admin/captain/register_captain.dart index 1f2e6b8e..21033527 100644 --- a/siro_admin/lib/views/admin/captain/register_captain.dart +++ b/siro_admin/lib/views/admin/captain/register_captain.dart @@ -9,7 +9,7 @@ import '../../widgets/my_scafold.dart'; import '../../widgets/mycircular.dart'; class RegisterCaptain extends StatelessWidget { - RegisterCaptain({super.key}); + const RegisterCaptain({super.key}); @override Widget build(BuildContext context) { diff --git a/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart b/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart index 42711b93..67e655c0 100644 --- a/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart +++ b/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart @@ -7,7 +7,6 @@ import '../../../constant/info.dart'; import '../../../constant/char_map.dart'; import '../../../controller/drivers/driver_not_active_controller.dart'; -import '../../../controller/functions/encrypt_decrypt.dart'; import '../../../main.dart'; import '../../../print.dart'; import 'driver_details_not_active_page.dart'; diff --git a/siro_admin/lib/views/admin/dashboard_v2_widget.dart b/siro_admin/lib/views/admin/dashboard_v2_widget.dart index 2a11cda2..02285660 100644 --- a/siro_admin/lib/views/admin/dashboard_v2_widget.dart +++ b/siro_admin/lib/views/admin/dashboard_v2_widget.dart @@ -9,10 +9,8 @@ class DashboardV2Widget extends StatelessWidget { @override Widget build(BuildContext context) { - // Initialize controller - final controller = Get.put(DashboardV2Controller()); - return GetBuilder( + init: DashboardV2Controller(), builder: (ctrl) { if (ctrl.isLoading) { return const SliverToBoxAdapter( diff --git a/siro_admin/lib/views/admin/dashboard_widget.dart b/siro_admin/lib/views/admin/dashboard_widget.dart index e0bc89a1..d65ce8e2 100644 --- a/siro_admin/lib/views/admin/dashboard_widget.dart +++ b/siro_admin/lib/views/admin/dashboard_widget.dart @@ -12,23 +12,23 @@ class DashboardStatCard extends StatelessWidget { final Color? valueColor; const DashboardStatCard({ - Key? key, + super.key, required this.title, required this.value, this.icon, this.iconColor, this.backgroundColor, this.valueColor, - }) : super(key: key); + }); @override Widget build(BuildContext context) { // Attempt to use AppStyle.boxDecoration1 properties if it's a BoxDecoration BoxDecoration? baseDecoration = AppStyle.boxDecoration1; Color? finalBackgroundColor = - backgroundColor ?? baseDecoration?.color ?? Theme.of(context).cardColor; + backgroundColor ?? baseDecoration.color ?? Theme.of(context).cardColor; BorderRadius? finalBorderRadius = - baseDecoration?.borderRadius?.resolve(Directionality.of(context)) ?? + baseDecoration.borderRadius?.resolve(Directionality.of(context)) ?? BorderRadius.circular(12.0); return Container( diff --git a/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart b/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart index d81c0a3a..29ccbf73 100644 --- a/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart +++ b/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart @@ -5,6 +5,7 @@ import '../../../constant/style.dart'; import '../../../controller/admin/driver_docs_controller.dart'; import '../../widgets/my_scafold.dart'; import '../../widgets/elevated_btn.dart'; +import '../../widgets/snackbar.dart'; import '../../../constant/links.dart'; class DriverDocsReviewPage extends StatelessWidget { @@ -100,7 +101,7 @@ class DriverDocsReviewPage extends StatelessWidget { const SizedBox(height: 24), Text('الوثائق المرفوعة', style: AppStyle.title), const SizedBox(height: 12), - ...docs.map((doc) => _buildDocCard(doc)).toList(), + ...docs.map((doc) => _buildDocCard(doc)), const SizedBox(height: 32), MyElevatedButton( title: 'اعتماد وتفعيل الحساب', @@ -110,7 +111,7 @@ class DriverDocsReviewPage extends StatelessWidget { bool success = await controller.approveDriver(id); if (success) { Get.back(); - Get.snackbar('نجاح', 'تم تفعيل حساب السائق بنجاح'); + mySnackbarSuccess('تم تفعيل حساب السائق بنجاح'); } }, ), diff --git a/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart b/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart index f2c7fd9a..b6619d4d 100644 --- a/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart +++ b/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart @@ -2,6 +2,7 @@ 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 '../../../constant/links.dart'; // تأكد من المسار @@ -17,11 +18,6 @@ class DriverGiftCheckerController extends GetxController { // قائمة السائقين (سنقوم بتحميلها للبحث عن الـ ID) List driversCache = []; - @override - void onInit() { - super.onInit(); - // fetchDriverCache(); // تحميل البيانات عند فتح الصفحة - } // 1. تحميل قائمة السائقين لاستخراج الـ ID منها Future fetchDriverCache() async { @@ -45,8 +41,7 @@ class DriverGiftCheckerController extends GetxController { String phoneInput = phoneController.text.trim(); if (phoneInput.isEmpty) { - Get.snackbar("تنبيه", "يرجى إدخال رقم الهاتف", - backgroundColor: Colors.orange); + mySnackbarWarning("يرجى إدخال رقم الهاتف"); return; } await fetchDriverCache(); diff --git a/siro_admin/lib/views/admin/drivers/driver_the_best.dart b/siro_admin/lib/views/admin/drivers/driver_the_best.dart index c4768fc9..5110d57b 100644 --- a/siro_admin/lib/views/admin/drivers/driver_the_best.dart +++ b/siro_admin/lib/views/admin/drivers/driver_the_best.dart @@ -2,6 +2,7 @@ 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'; // --- New Controller to handle the specific JSON URL --- class DriverCacheController extends GetxController { @@ -73,13 +74,7 @@ class DriverCacheController extends GetxController { paidDrivers.clear(); box.remove('paid_drivers'); update(); - Get.snackbar( - "Storage Cleared", - "Paid status history has been reset", - backgroundColor: Colors.redAccent, - colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, - ); + mySnackbarInfo("Paid status history has been reset"); } // Check if driver is already paid @@ -93,9 +88,6 @@ class DriverTheBestRedesigned extends StatelessWidget { @override Widget build(BuildContext context) { - // Put the new controller - final controller = Get.put(DriverCacheController()); - return Scaffold( backgroundColor: const Color(0xFFF8FAFC), // slate-50 background body: SafeArea( @@ -580,8 +572,7 @@ class DriverTheBestRedesigned extends StatelessWidget { String driverId = driver['driver_id']?.toString() ?? ''; String phone = driver['phone']?.toString() ?? ''; if (driverId.isEmpty || driverId == 'null') { - Get.snackbar("Error", "Cannot pay driver with missing ID", - backgroundColor: Colors.red, colorText: Colors.white); + mySnackbarError("Cannot pay driver with missing ID"); return; } @@ -642,10 +633,7 @@ class DriverTheBestRedesigned extends StatelessWidget { Get.back(); // Close Dialog - Get.snackbar("Success", "Payment of $amount EGP sent to driver", - backgroundColor: Colors.green, - colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM); + mySnackbarSuccess("Payment of $amount EGP sent to driver"); }, textCancel: 'Cancel', onCancel: () => Get.back(), diff --git a/siro_admin/lib/views/admin/drivers/monitor_ride.dart b/siro_admin/lib/views/admin/drivers/monitor_ride.dart index 9f20ba27..63926c6c 100644 --- a/siro_admin/lib/views/admin/drivers/monitor_ride.dart +++ b/siro_admin/lib/views/admin/drivers/monitor_ride.dart @@ -6,6 +6,7 @@ import 'package:latlong2/latlong.dart'; import 'package:siro_admin/constant/links.dart'; // Keep your specific imports import 'package:siro_admin/controller/functions/crud.dart'; +import 'package:siro_admin/views/widgets/snackbar.dart'; /// -------------------------------------------------------------------------- /// 1. DATA MODELS @@ -49,20 +50,23 @@ String normalizePhone(String input) { final clean = input.replaceAll(RegExp(r'\D+'), ''); // Syria: 099XXXXXXX or 9639XXXXXXX - if (clean.length == 10 && clean.startsWith('09')) + 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'; // Jordan: 079XXXXXXX or 9627XXXXXXX - if (clean.length == 10 && clean.startsWith('07')) + 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'; // Egypt: 010XXXXXXXX or 2010XXXXXXXX - if (clean.length == 11 && clean.startsWith('01')) + if (clean.length == 11 && clean.startsWith('01')) { return '20${clean.substring(1)}'; + } if (clean.length == 13 && clean.startsWith('20')) return clean; return clean; @@ -107,15 +111,7 @@ class RideMonitorController extends GetxController { void startSearch() { if (phoneInputController.text.trim().isEmpty) { - Get.snackbar( - "تنبيه", - "يرجى إدخال رقم الهاتف أولاً", - backgroundColor: Colors.redAccent.withOpacity(0.9), - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - margin: const EdgeInsets.all(15), - borderRadius: 15, - ); + mySnackbarWarning("يرجى إدخال رقم الهاتف أولاً"); return; } @@ -297,9 +293,10 @@ class RideMonitorScreen extends StatelessWidget { appBar: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), child: Obx(() { - if (controller.isTracking.value) + if (controller.isTracking.value) { return const SizedBox .shrink(); // إخفاء الـ AppBar في وضع التتبع للخريطة الكاملة + } return AppBar( backgroundColor: Colors.transparent, elevation: 0, @@ -460,7 +457,7 @@ class RideMonitorScreen extends StatelessWidget { PolylineLayer( polylines: [ Polyline( - points: controller.routePolyline.value, + points: controller.routePolyline, strokeWidth: 6.0, color: primaryColor.withOpacity(0.9), borderStrokeWidth: 2.0, diff --git a/siro_admin/lib/views/admin/enceypt/driver_fingerprint_migration.dart b/siro_admin/lib/views/admin/enceypt/driver_fingerprint_migration.dart deleted file mode 100644 index d7fa4873..00000000 --- a/siro_admin/lib/views/admin/enceypt/driver_fingerprint_migration.dart +++ /dev/null @@ -1,266 +0,0 @@ -// ═══════════════════════════════════════════════════════════════ -// driver_fingerprint_migration.dart -// ─────────────────────────────────────────────────────────────── -// المنطق ببساطة: -// 1. خذ البصمة كما هي من DB -// 2. split('_') → احذف آخر جزء (OS version) -// 3. join('_') → encrypt → رفع -// -// مثال: -// "abc123_SamsungA51_13" → "abc123_SamsungA51" → encrypt -// "TECNO_LH7n-GL_14" → "TECNO_LH7n-GL" → encrypt -// "unknown_2412DPC0AG_15" → "unknown_2412DPC0AG" → encrypt -// ═══════════════════════════════════════════════════════════════ - -import 'package:flutter/material.dart'; - -import '../../../constant/links.dart'; -import '../../../controller/functions/crud.dart'; -import '../../../controller/functions/encrypt_decrypt.dart'; -import '../../../print.dart'; - -class DriverFingerprintMigrationTool extends StatefulWidget { - const DriverFingerprintMigrationTool({super.key}); - - @override - State createState() => - _DriverFingerprintMigrationToolState(); -} - -class _DriverFingerprintMigrationToolState - extends State { - bool _isRunning = false; - bool _isDone = false; - int _total = 0; - int _processed = 0; - int _updated = 0; - int _failed = 0; - String _currentLog = ''; - - static const int _batchSize = 50; - - // ───────────────────────────────────────────────────────────── - // المنطق الأساسي — حذف آخر جزء بعد "_" - // ───────────────────────────────────────────────────────────── - String _removeLastSegment(String raw) { - final parts = raw.split('_'); - if (parts.length <= 1) return raw; // جزء واحد — ما في شيء نحذفه - parts.removeLast(); - return parts.join('_'); - } - - Future _startMigration() async { - setState(() { - _isRunning = true; - _isDone = false; - _processed = 0; - _updated = 0; - _failed = 0; - _currentLog = 'جارٍ جلب بصمات السائقين...'; - }); - - try { - final records = await _fetchAll(); - if (records == null) { - _log('❌ فشل في جلب البيانات'); - setState(() => _isRunning = false); - return; - } - - _total = records.length; - _log('✅ تم جلب $_total بصمة — بدء المعالجة...'); - - for (int i = 0; i < records.length; i += _batchSize) { - final batch = records.skip(i).take(_batchSize).toList(); - _log('⚙️ معالجة ${i + 1} → ${i + batch.length} من $_total'); - await Future.wait(batch.map(_processSingle)); - if (i + _batchSize < records.length) { - await Future.delayed(const Duration(milliseconds: 300)); - } - } - - _log('🎉 اكتمل!\nمحدَّث: $_updated | فاشل: $_failed'); - setState(() { - _isDone = true; - _isRunning = false; - }); - } catch (e) { - _log('❌ خطأ: $e'); - setState(() => _isRunning = false); - } - } - - Future>?> _fetchAll() async { - try { - final response = await CRUD().post( - link: AppLink.getAllDriverFingerprints, - payload: {'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd'}, - ); - if (response == 'failure' || response == null) return null; - - final data = response['data']; - if (data is! List) return null; - - return List>.from(data); - } catch (e) { - Log.print('fetchAll error: $e'); - return null; - } - } - - Future _processSingle(Map record) async { - final captainId = record['captain_id']?.toString() ?? ''; - final rawFp = record['fingerPrint']?.toString() ?? ''; - - if (captainId.isEmpty || rawFp.isEmpty) { - setState(() { - _failed++; - _processed++; - }); - return; - } - - try { - // ── حذف آخر جزء (OS version) ───────────────────────────── - final String newRaw = _removeLastSegment(rawFp); - final String encrypted = EncryptionHelper.instance.encryptData(newRaw); - - Log.print('🔄 [$captainId] "$rawFp" → "$newRaw" → encrypted'); - - // ── رفع للسيرفر ────────────────────────────────────────── - final res = await CRUD().post( - link: AppLink.updateDriverFingerprintAdmin, - payload: { - 'captain_id': captainId, - 'fingerprint': encrypted, - 'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd', - }, - ); - - if (res != 'failure' && res?['status'] == 'success') { - setState(() { - _updated++; - _processed++; - }); - } else { - setState(() { - _failed++; - _processed++; - }); - } - } catch (e) { - Log.print('❌ [$captainId]: $e'); - setState(() { - _failed++; - _processed++; - }); - } - } - - void _log(String msg) { - Log.print(msg); - setState(() => _currentLog = msg); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Driver FP Migration')), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.orange.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.orange.shade200), - ), - child: const Text( - '⚠️ تُستخدم مرة واحدة فقط\n\n' - '"abc123_Samsung_13" → "abc123_Samsung" → encrypt\n' - '"TECNO_LH7n_14" → "TECNO_LH7n" → encrypt', - style: - TextStyle(fontSize: 13, height: 1.7, fontFamily: 'monospace'), - ), - ), - const SizedBox(height: 24), - if (_total > 0) ...[ - Text('التقدم: $_processed / $_total', - style: const TextStyle(fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - LinearProgressIndicator( - value: _total > 0 ? _processed / _total : 0, - backgroundColor: Colors.grey.shade200, - color: _isDone ? Colors.green : Colors.blue, - minHeight: 8, - ), - const SizedBox(height: 16), - ], - if (_processed > 0) - Row(children: [ - _chip('محدَّث', _updated, Colors.green), - const SizedBox(width: 8), - _chip('فاشل', _failed, Colors.red), - ]), - const SizedBox(height: 16), - if (_currentLog.isNotEmpty) - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(8), - ), - child: Text(_currentLog, - style: - const TextStyle(fontFamily: 'monospace', fontSize: 12)), - ), - const Spacer(), - SizedBox( - width: double.infinity, - height: 52, - child: ElevatedButton( - onPressed: (_isRunning || _isDone) ? null : _startMigration, - style: ElevatedButton.styleFrom( - backgroundColor: _isDone ? Colors.green : Colors.blue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - ), - child: _isRunning - ? const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, strokeWidth: 2)), - SizedBox(width: 12), - Text('جارٍ الترحيل...', - style: - TextStyle(color: Colors.white, fontSize: 16)), - ], - ) - : Text( - _isDone ? '✅ اكتمل الترحيل' : 'بدء ترحيل بصمات السائقين', - style: const TextStyle(color: Colors.white, fontSize: 16), - ), - ), - ), - ]), - ), - ); - } - - Widget _chip(String label, int value, Color color) => Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withOpacity(0.3)), - ), - child: Text('$label: $value', - style: TextStyle(color: color, fontWeight: FontWeight.bold)), - ); -} diff --git a/siro_admin/lib/views/admin/enceypt/encrypt.dart b/siro_admin/lib/views/admin/enceypt/encrypt.dart index 3ab4aa41..3ee58b58 100644 --- a/siro_admin/lib/views/admin/enceypt/encrypt.dart +++ b/siro_admin/lib/views/admin/enceypt/encrypt.dart @@ -9,14 +9,11 @@ import '../../../constant/box_name.dart'; // ─── Custom Colors ──────────────────────────────────────────────────────────── class _AppColors { static const bg = Color(0xFF0A0D14); - static const surface = Color(0xFF111622); static const card = Color(0xFF161D2E); static const border = Color(0xFF1F2D4A); static const accent = Color(0xFF00E5FF); - static const accentDim = Color(0xFF0097A7); static const accentGlow = Color(0x2200E5FF); static const accentDecrypt = Color(0xFF7C4DFF); - static const accentDecryptDim = Color(0xFF512DA8); static const accentDecryptGlow = Color(0x227C4DFF); static const textPrimary = Color(0xFFE8F0FE); static const textSec = Color(0xFF7A8BAA); @@ -27,7 +24,7 @@ class _AppColors { class EncryptToolPage extends StatefulWidget { final String adminToken; - const EncryptToolPage({Key? key, required this.adminToken}) : super(key: key); + const EncryptToolPage({super.key, required this.adminToken}); @override State createState() => _EncryptToolPageState(); diff --git a/siro_admin/lib/views/admin/enceypt/fingerprint_migration.dart b/siro_admin/lib/views/admin/enceypt/fingerprint_migration.dart deleted file mode 100644 index 82d65237..00000000 --- a/siro_admin/lib/views/admin/enceypt/fingerprint_migration.dart +++ /dev/null @@ -1,366 +0,0 @@ -// ═══════════════════════════════════════════════════════════════ -// fingerprint_migration.dart -// ─────────────────────────────────────────────────────────────── -// أداة ترحيل البصمات القديمة للنظام الجديد -// ─────────────────────────────────────────────────────────────── -// المشكلة: -// البصمة القديمة = encrypt(androidId_model_osVersion) -// البصمة الجديدة = encrypt(androidId_model) -// -// الحل: -// 1. نجيب كل البصمات من السيرفر (batch 50 في المرة) -// 2. نفك تشفير كل بصمة بـ EncryptionHelper -// 3. نحذف آخر جزء (osVersion) مع الـ _ قبله -// 4. نعيد التشفير -// 5. نرفع البصمة المحدّثة للسيرفر -// -// يُستخدم مرة واحدة فقط ثم يُحذف من التطبيق -// ═══════════════════════════════════════════════════════════════ - -import 'dart:convert'; -import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'package:siro_admin/controller/functions/encrypt_decrypt.dart' as X; - -import '../../../constant/char_map.dart'; -import '../../../constant/links.dart'; -import '../../../controller/functions/crud.dart'; -import '../../../controller/functions/encrypt_decrypt.dart'; -import '../../../print.dart'; - -class FingerprintMigrationTool extends StatefulWidget { - const FingerprintMigrationTool({super.key}); - - @override - State createState() => - _FingerprintMigrationToolState(); -} - -class _FingerprintMigrationToolState extends State { - // ── حالة الترحيل ────────────────────────────────────────── - bool _isRunning = false; - bool _isDone = false; - int _total = 0; - int _processed = 0; - int _updated = 0; // بصمات تم تحديثها - int _skipped = 0; // بصمات كانت بالفعل بالنظام الجديد - int _failed = 0; // فشل في المعالجة - String _currentLog = ''; - - static const int _batchSize = 50; - - // ───────────────────────────────────────────────────────────── - // الدالة الرئيسية للترحيل - // ───────────────────────────────────────────────────────────── - Future _startMigration() async { - setState(() { - _isRunning = true; - _isDone = false; - _processed = 0; - _updated = 0; - _skipped = 0; - _failed = 0; - _currentLog = 'جارٍ جلب البصمات من السيرفر...'; - }); - - try { - // ── 1. جلب كل البصمات من السيرفر ────────────────────── - final allFingerprints = await _fetchAllFingerprints(); - - if (allFingerprints == null) { - _log('❌ فشل في جلب البيانات من السيرفر'); - setState(() => _isRunning = false); - return; - } - - _total = allFingerprints.length; - _log('✅ تم جلب $_total بصمة — بدء المعالجة...'); - - // ── 2. معالجة على batches ────────────────────────────── - for (int i = 0; i < allFingerprints.length; i += _batchSize) { - final batch = allFingerprints.skip(i).take(_batchSize).toList(); - - _log('⚙️ معالجة ${i + 1} → ${i + batch.length} من $_total'); - - // معالجة الـ batch بالتوازي - await Future.wait( - batch.map((record) => _processSingleRecord(record)), - ); - - // استراحة قصيرة بين الـ batches لحماية السيرفر - if (i + _batchSize < allFingerprints.length) { - await Future.delayed(const Duration(milliseconds: 300)); - } - } - - _log('🎉 اكتمل الترحيل!\n' - 'محدَّث: $_updated | متجاوز: $_skipped | فاشل: $_failed'); - - setState(() { - _isDone = true; - _isRunning = false; - }); - } catch (e) { - _log('❌ خطأ عام: $e'); - setState(() => _isRunning = false); - } - } - - // ───────────────────────────────────────────────────────────── - // جلب كل البصمات من السيرفر - // ───────────────────────────────────────────────────────────── - Future>?> _fetchAllFingerprints() async { - try { - final response = await CRUD().post( - link: AppLink.getAllFingerprints, // أضفه في AppLink - payload: { - 'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd' - }, // مفتاح أمان للـ endpoint - ); - - if (response == 'failure' || response == null) return null; - - final data = response['data']; - if (data is! List) return null; - - return List>.from(data); - } catch (e) { - Log.print('fetchAllFingerprints error: $e'); - return null; - } - } - - // ───────────────────────────────────────────────────────────── - // معالجة بصمة واحدة - // ───────────────────────────────────────────────────────────── - Future _processSingleRecord(Map record) async { - final String passengerID = record['passengerID']?.toString() ?? ''; - final String encryptedFp = record['fingerPrint']?.toString() ?? ''; - final String userType = record['userType']?.toString() ?? 'passenger'; - - if (passengerID.isEmpty || encryptedFp.isEmpty) { - setState(() { - _failed++; - _processed++; - }); - return; - } - - try { - // ── فك التشفير ──────────────────────────────────────── - final String rawFp = EncryptionHelper.instance.decryptData(encryptedFp); - - // ── تحليل البصمة ────────────────────────────────────── - // الشكل القديم: "androidId_model_osVersion" (3 أجزاء أو أكثر) - // الشكل الجديد: "androidId_model" (جزءان فقط) - final List parts = rawFp.split('_'); - - if (parts.length <= 2) { - // البصمة بالفعل بالنظام الجديد — تجاوزها - setState(() { - _skipped++; - _processed++; - }); - return; - } - - // ── حذف آخر جزء (osVersion) ────────────────────────── - // مثال: "abc123_SamsungA51_13" → "abc123_SamsungA51" - // نأخذ أول جزأين فقط بغض النظر عن عدد الأجزاء - final String newRawFp = '${parts[0]}_${parts[1]}'; - - // ── إعادة التشفير ───────────────────────────────────── - final String newEncryptedFp = - EncryptionHelper.instance.encryptData(newRawFp); - - // ── رفع البصمة الجديدة للسيرفر ─────────────────────── - final response = await CRUD().post( - link: AppLink.updateFingerprintAdmin, // أضفه في AppLink - payload: { - 'passengerID': passengerID, - 'fingerprint': newEncryptedFp, - 'userType': userType, - 'admin_key': 'iuyweiruinakjbfkajkjlkmalkcxnlahd', - }, - ); - - if (response != 'failure' && response?['status'] == 'success') { - setState(() { - _updated++; - _processed++; - }); - Log.print('✅ Updated: $passengerID | $rawFp → $newRawFp'); - } else { - setState(() { - _failed++; - _processed++; - }); - Log.print('❌ Failed update: $passengerID'); - } - } catch (e) { - // فشل فك التشفير أو إعادة التشفير - setState(() { - _failed++; - _processed++; - }); - Log.print('❌ Process error for $passengerID: $e'); - } - } - - void _log(String message) { - Log.print(message); - setState(() => _currentLog = message); - } - - // ───────────────────────────────────────────────────────────── - // UI - // ───────────────────────────────────────────────────────────── - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Fingerprint Migration Tool')), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // ── شرح الأداة ────────────────────────────────── - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.orange.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.orange.shade200), - ), - child: const Text( - '⚠️ هذه الأداة تُستخدم مرة واحدة فقط\n' - 'تقوم بتحديث بصمات الأجهزة القديمة\n' - 'لتكون متوافقة مع النظام الجديد (بدون OS version)', - style: TextStyle(fontSize: 14, height: 1.6), - ), - ), - Container( - child: TextButton( - onPressed: () { - print(EncryptionHelper.instance.decryptData('hbgbitbXrXrBr')); - }, - child: Text( - "Decrypt Test", - ), - ), - ), - Container( - child: TextButton( - onPressed: () { - print(EncryptionHelper.instance.encryptData( - '1B501143-C579-461C-B556-4E8B390EEFE1_iPhone')); - }, - child: Text( - "Encrypt Test", - ), - ), - ), - Container( - child: TextButton( - onPressed: () { - print(r('hbgbitbXrXrBr')); - }, - child: Text( - "decrypt X.r", - ), - ), - ), - - const SizedBox(height: 24), - - // ── شريط التقدم ───────────────────────────────── - if (_total > 0) ...[ - Text('التقدم: $_processed / $_total'), - const SizedBox(height: 8), - LinearProgressIndicator( - value: _total > 0 ? _processed / _total : 0, - backgroundColor: Colors.grey.shade200, - color: _isDone ? Colors.green : Colors.blue, - ), - const SizedBox(height: 16), - ], - - // ── إحصائيات ──────────────────────────────────── - if (_processed > 0) - Row(children: [ - _statChip('محدَّث', _updated, Colors.green), - const SizedBox(width: 8), - _statChip('متجاوز', _skipped, Colors.blue), - const SizedBox(width: 8), - _statChip('فاشل', _failed, Colors.red), - ]), - - const SizedBox(height: 16), - - // ── السجل الحالي ───────────────────────────────── - if (_currentLog.isNotEmpty) - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(8), - ), - child: Text(_currentLog, - style: const TextStyle(fontFamily: 'monospace')), - ), - - const Spacer(), - - // ── زر التشغيل ────────────────────────────────── - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: (_isRunning || _isDone) ? null : _startMigration, - style: ElevatedButton.styleFrom( - backgroundColor: _isDone ? Colors.green : Colors.blue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - ), - child: _isRunning - ? const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, strokeWidth: 2), - ), - SizedBox(width: 12), - Text('جارٍ الترحيل...', - style: TextStyle(color: Colors.white)), - ], - ) - : Text( - _isDone ? '✅ اكتمل الترحيل' : 'بدء الترحيل', - style: - const TextStyle(color: Colors.white, fontSize: 16), - ), - ), - ), - ], - ), - ), - ); - } - - Widget _statChip(String label, int value, Color color) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withOpacity(0.3)), - ), - child: Text('$label: $value', - style: TextStyle(color: color, fontWeight: FontWeight.bold)), - ); - } -} diff --git a/siro_admin/lib/views/admin/error/error/error_page.dart b/siro_admin/lib/views/admin/error/error/error_page.dart index 7c18dd5e..50bd7688 100644 --- a/siro_admin/lib/views/admin/error/error/error_page.dart +++ b/siro_admin/lib/views/admin/error/error/error_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:siro_admin/constant/colors.dart'; import 'package:siro_admin/constant/links.dart'; import 'package:siro_admin/controller/functions/crud.dart'; @@ -40,7 +39,7 @@ class ErrorLog { } class ErrorListPage extends StatefulWidget { - const ErrorListPage({Key? key}) : super(key: key); + const ErrorListPage({super.key}); @override State createState() => _ErrorListPageState(); diff --git a/siro_admin/lib/views/admin/financial/financial_v2_page.dart b/siro_admin/lib/views/admin/financial/financial_v2_page.dart index d8f1cbaf..a505bd21 100644 --- a/siro_admin/lib/views/admin/financial/financial_v2_page.dart +++ b/siro_admin/lib/views/admin/financial/financial_v2_page.dart @@ -204,8 +204,9 @@ class FinancialV2Page extends StatelessWidget { } Widget _buildSettlementsList(List settlements) { - if (settlements.isEmpty) + if (settlements.isEmpty) { return const Center(child: Text('لا توجد تسويات معلقة')); + } return ListView.builder( shrinkWrap: true, diff --git a/siro_admin/lib/views/admin/marketing/marketing_page.dart b/siro_admin/lib/views/admin/marketing/marketing_page.dart index 07abc8e2..92248e38 100644 --- a/siro_admin/lib/views/admin/marketing/marketing_page.dart +++ b/siro_admin/lib/views/admin/marketing/marketing_page.dart @@ -214,7 +214,7 @@ class MarketingPage extends StatelessWidget { const SizedBox(width: 24), _buildStatItem( 'الفارق', - '${((double.tryParse(anomaly['competitor_price']?.toString() ?? '0') ?? 0) - (double.tryParse(anomaly['our_price']?.toString() ?? '0') ?? 0)).toStringAsFixed(2)}', + ((double.tryParse(anomaly['competitor_price']?.toString() ?? '0') ?? 0) - (double.tryParse(anomaly['our_price']?.toString() ?? '0') ?? 0)).toStringAsFixed(2), _info, ), ], @@ -264,7 +264,7 @@ class MarketingPage extends StatelessWidget { : 0.0; List competitorSpots = []; - if (hourlyData is List && hourlyData.isNotEmpty) { + if (hourlyData.isNotEmpty) { for (int i = 0; i < hourlyData.length && i < 24; i++) { final val = hourlyData[i]; final double avg = double.tryParse((val['avg_price_per_km'] ?? 0).toString()) ?? 0.0; @@ -280,7 +280,7 @@ class MarketingPage extends StatelessWidget { List siroSpots = []; if (siroSpeedPrice > 0) { - for (int i = 0; i < (hourlyData is List ? hourlyData.length : 1); i++) { + for (int i = 0; i < hourlyData.length; i++) { siroSpots.add(FlSpot(i.toDouble(), siroSpeedPrice)); } } @@ -303,7 +303,7 @@ class MarketingPage extends StatelessWidget { ), const SizedBox(height: 4), Text( - hourlyData is List && hourlyData.isNotEmpty + hourlyData.isNotEmpty ? 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر ${hourlyData.length} ساعة' : 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر 24 ساعة', style: const TextStyle(fontSize: 10, color: _textSecondary), @@ -346,7 +346,7 @@ class MarketingPage extends StatelessWidget { ), borderData: FlBorderData(show: false), minX: 0, - maxX: ((hourlyData is List ? hourlyData.length : 1) - 1).toDouble(), + maxX: (hourlyData.length - 1).toDouble(), minY: 0, maxY: chartMaxY, lineBarsData: [ @@ -391,10 +391,8 @@ class MarketingPage extends StatelessWidget { return GetBuilder( builder: (c) { final heatmapList = c.heatmapData; - final siroSpeedPrice = c.currentSiroPriceHeatmap; - List> regions = []; - if (heatmapList is List && heatmapList.isNotEmpty) { + if (heatmapList.isNotEmpty) { for (final item in heatmapList) { final double pci = double.tryParse(item['pci'].toString()) ?? 1.0; final pct = ((1 - pci) * 100).abs().toStringAsFixed(1); @@ -433,7 +431,6 @@ class MarketingPage extends StatelessWidget { ), const SizedBox(height: 12), ...regions.map((region) { - final pciVal = (region['pci'] as num).toDouble(); return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( @@ -462,7 +459,7 @@ class MarketingPage extends StatelessWidget { ], ), ); - }).toList(), + }), ], ), ), @@ -725,7 +722,7 @@ class MarketingPage extends StatelessWidget { style: const TextStyle(fontSize: 10, color: _textSecondary), ), value: c.isAutopilotEnabled, - activeColor: _accent, + activeThumbColor: _accent, onChanged: c.toggleAutopilot, ), ), @@ -978,65 +975,4 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildWinbackCampaignsSection(BuildContext context, MarketingController c) { - return Card( - color: _surface, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: const BorderSide(color: _divider)), - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Row( - children: [ - Icon(Icons.radar, color: _accent, size: 24), - SizedBox(width: 8), - Text('حملات استعادة العملاء (Win-Back)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: _textPrimary)), - ], - ), - const SizedBox(height: 8), - const Text( - 'يتم البحث عن الركاب المنقطعين عن التطبيق والذين يتواجدون حالياً بالقرب من مناطق تشهد أسعار ذروة لدى المنافسين.', - style: TextStyle(color: _textSecondary, fontSize: 11), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: () => c.fetchWinbackTargets(), - icon: const Icon(Icons.person_search, size: 18), - label: const Text('بحث عن أهداف حالية'), - style: ElevatedButton.styleFrom( - backgroundColor: _accent.withOpacity(0.1), - foregroundColor: _accent, - elevation: 0, - ), - ), - ), - if (c.winbackTotalCount > 0) ...[ - const SizedBox(width: 12), - Expanded( - child: ElevatedButton.icon( - onPressed: () { - // TODO: Implement trigger specific winback campaign - Get.snackbar("Campaign Triggered", "SMS sent to ${c.winbackTotalCount} targets"); - }, - icon: const Icon(Icons.send), - label: Text('إرسال SMS لـ ${c.winbackTotalCount}'), - style: ElevatedButton.styleFrom( - backgroundColor: _success, - foregroundColor: Colors.white, - ), - ), - ), - ] - ], - ), - ], - ), - ), - ); - } } diff --git a/siro_admin/lib/views/admin/packages.dart b/siro_admin/lib/views/admin/packages.dart index 87c87528..c648e40c 100644 --- a/siro_admin/lib/views/admin/packages.dart +++ b/siro_admin/lib/views/admin/packages.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; import 'dart:convert'; import 'package:siro_admin/constant/links.dart'; import 'package:siro_admin/controller/functions/crud.dart'; -import 'package:siro_admin/views/widgets/my_textField.dart'; +import 'package:siro_admin/views/widgets/snackbar.dart'; import '../../print.dart'; @@ -14,7 +14,6 @@ const Color _bg = Color(0xFF0D1117); const Color _surface = Color(0xFF161B22); const Color _surfaceElevated = Color(0xFF1C2333); const Color _accent = Color(0xFF00D4AA); -const Color _danger = Color(0xFFFF5370); const Color _warning = Color(0xFFFFCB6B); const Color _info = Color(0xFF82AAFF); const Color _textPrimary = Color(0xFFE6EDF3); @@ -556,15 +555,7 @@ class PackageController extends GetxController { updatePackages(String id, String version) async { if (version.trim().isEmpty) { - Get.snackbar( - 'تنبيه', - 'يرجى إدخال رقم الإصدار', - backgroundColor: _warning.withOpacity(0.15), - colorText: _textPrimary, - borderRadius: 12, - margin: const EdgeInsets.all(16), - icon: const Icon(Icons.warning_rounded, color: _warning), - ); + mySnackbarWarning('يرجى إدخال رقم الإصدار'); return; } @@ -578,26 +569,10 @@ class PackageController extends GetxController { if (response != 'failure') { Get.back(); - Get.snackbar( - 'تم التحديث', - 'تم تحديث الإصدار بنجاح', - backgroundColor: _accent.withOpacity(0.15), - colorText: _textPrimary, - borderRadius: 12, - margin: const EdgeInsets.all(16), - icon: const Icon(Icons.check_circle_rounded, color: _accent), - ); + mySnackbarSuccess('تم تحديث الإصدار بنجاح'); fetchPackages(); } else { - Get.snackbar( - 'خطأ', - 'فشل التحديث، يرجى المحاولة مجدداً', - backgroundColor: _danger.withOpacity(0.15), - colorText: _textPrimary, - borderRadius: 12, - margin: const EdgeInsets.all(16), - icon: const Icon(Icons.error_rounded, color: _danger), - ); + mySnackbarError('فشل التحديث، يرجى المحاولة مجدداً'); } } } diff --git a/siro_admin/lib/views/admin/passenger/passenger.dart b/siro_admin/lib/views/admin/passenger/passenger.dart index fbf15b61..aa47c4be 100644 --- a/siro_admin/lib/views/admin/passenger/passenger.dart +++ b/siro_admin/lib/views/admin/passenger/passenger.dart @@ -10,6 +10,7 @@ import '../../widgets/elevated_btn.dart'; import '../../widgets/my_scafold.dart'; import '../../widgets/my_textField.dart'; import '../../widgets/mycircular.dart'; +import '../../widgets/snackbar.dart'; import 'passenger_details_page.dart'; class Passengrs extends StatelessWidget { @@ -376,15 +377,7 @@ class Passengrs extends StatelessWidget { Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))), ); } else { - Get.snackbar( - 'Not Allowed'.tr, - 'Prizes can only be added on Saturdays.'.tr, - backgroundColor: Colors.red.withOpacity(0.1), - colorText: Colors.red, - icon: const Icon(Icons.error_outline, color: Colors.red), - snackPosition: SnackPosition.TOP, - margin: const EdgeInsets.all(10), - ); + mySnackbarWarning('Prizes can only be added on Saturdays.'.tr); } } } diff --git a/siro_admin/lib/views/admin/passenger/passenger_details_page.dart b/siro_admin/lib/views/admin/passenger/passenger_details_page.dart index c0b49292..dd3e704b 100644 --- a/siro_admin/lib/views/admin/passenger/passenger_details_page.dart +++ b/siro_admin/lib/views/admin/passenger/passenger_details_page.dart @@ -3,15 +3,14 @@ import 'package:get/get.dart'; import '../../../constant/box_name.dart'; import '../../../constant/colors.dart'; +import '../../../views/widgets/snackbar.dart'; import '../../../controller/admin/passenger_admin_controller.dart'; import '../../../controller/functions/crud.dart'; -import '../../../controller/firebase/firbase_messge.dart'; import '../../../constant/links.dart'; import '../../../main.dart'; // To access 'box' for admin phone check import '../../widgets/elevated_btn.dart'; import '../../widgets/my_scafold.dart'; import '../../widgets/my_textField.dart'; -import 'form_passenger.dart'; class PassengerDetailsPage extends StatelessWidget { const PassengerDetailsPage({super.key}); @@ -399,8 +398,7 @@ class PassengerDetailsPage extends StatelessWidget { // data['passengerToken'], // 'order.wav'); Get.back(); - Get.snackbar('Success', 'Notification sent successfully!', - backgroundColor: Colors.green.withOpacity(0.2)); + mySnackbarSuccess('Notification sent successfully!'); } }, ), @@ -438,13 +436,11 @@ class PassengerDetailsPage extends StatelessWidget { // 3. Handle Result if (res['status'] == 'success') { Get.back(); // Go back to list page - Get.snackbar('Deleted', 'Passenger removed successfully', - backgroundColor: Colors.red.withOpacity(0.2)); + mySnackbarWarning('Passenger removed successfully'); // Ideally, trigger a refresh on the controller here // Get.find().getAll(); } else { - Get.snackbar('Error', res['message'] ?? 'Failed to delete', - backgroundColor: Colors.red.withOpacity(0.2)); + mySnackbarError(res['message'] ?? 'Failed to delete'); } }, child: Text('Delete'.tr, style: const TextStyle(color: Colors.white)), diff --git a/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart b/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart index 84f6a060..94b50ceb 100644 --- a/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart +++ b/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import '../../../constant/colors.dart'; import '../../../constant/style.dart'; import '../../../controller/admin/kazan_controller.dart'; +import '../../../views/widgets/snackbar.dart'; import '../../widgets/my_scafold.dart'; import '../../widgets/elevated_btn.dart'; @@ -17,39 +18,77 @@ class KazanEditorPage extends StatelessWidget { title: 'تعديل أسعار كازان'.tr, isleading: true, body: [ - Obx(() => controller.isLoading.value && controller.kazanData.isEmpty - ? const Center(child: CircularProgressIndicator()) - : SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Column( + children: [ + _buildCountryDropdown(), + Expanded( + child: Obx(() => controller.isLoading.value && controller.kazanData.isEmpty + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader('⚙️ الإعدادات العامة'), + _buildGeneralSettings(), + const SizedBox(height: 24), + _buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة'), + _buildKmPricesGrid(), + const SizedBox(height: 24), + _buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)'), + _buildMinutePrices(), + const SizedBox(height: 32), + MyElevatedButton( + title: '💾 حفظ جميع التعديلات', + icon: Icons.save_rounded, + onPressed: () => _handleSave(), + ), + const SizedBox(height: 100), + ], + ), + )), + ), + ], + ), + ], + ); + } + + Widget _buildCountryDropdown() { + return Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), + child: Obx(() => Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: AppColor.surface, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: AppColor.divider), + ), + child: DropdownButtonHideUnderline( + child: DropdownButton( + value: controller.selectedCountry.value, + isExpanded: true, + icon: const Icon(Icons.keyboard_arrow_down_rounded), + style: AppStyle.title.copyWith(fontSize: 16), + items: controller.countries.map((c) { + return DropdownMenuItem( + value: c['name'], + child: Row( children: [ - // ⚙️ الإعدادات العامة - _buildSectionHeader('⚙️ الإعدادات العامة'), - _buildGeneralSettings(), - const SizedBox(height: 24), - - // 🚗 أسعار الكيلومتر لكل نوع سيارة - _buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة'), - _buildKmPricesGrid(), - const SizedBox(height: 24), - - // ⏱️ أسعار الدقيقة - _buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)'), - _buildMinutePrices(), - const SizedBox(height: 32), - - // 💾 زر الحفظ - MyElevatedButton( - title: '💾 حفظ جميع التعديلات', - icon: Icons.save_rounded, - onPressed: () => _handleSave(), - ), - const SizedBox(height: 100), + Text(c['flag']!, style: const TextStyle(fontSize: 22)), + const SizedBox(width: 12), + Text(c['name']!), ], ), - )), - ], + ); + }).toList(), + onChanged: (val) { + if (val != null) controller.setCountry(val); + }, + ), + ), + )), ); } @@ -373,26 +412,18 @@ class KazanEditorPage extends StatelessWidget { final data = Map.from(controller.kazanData); data['adminId'] = 'admin1'; - // التأكد من وجود country (إذا لم يكن موجوداً، استخدم 'syria') + // التأكد من وجود country if (!data.containsKey('country') || data['country'] == null || data['country'].toString().isEmpty) { - data['country'] = 'Syria'; + data['country'] = controller.selectedCountry.value; } bool success = await controller.updateKazan(data); if (success) { - Get.snackbar("نجاح", "تم تحديث الأسعار بنجاح", - backgroundColor: AppColor.successSoft, - colorText: AppColor.textPrimary, - snackPosition: SnackPosition.BOTTOM, - margin: const EdgeInsets.all(16)); + mySnackbarSuccess('تم تحديث أسعار ${controller.selectedCountry.value} بنجاح'); } else { - Get.snackbar("خطأ", "فشل تحديث الأسعار", - backgroundColor: Colors.red.shade100, - colorText: AppColor.textPrimary, - snackPosition: SnackPosition.BOTTOM, - margin: const EdgeInsets.all(16)); + mySnackbarError('فشل تحديث الأسعار'); } } } diff --git a/siro_admin/lib/views/admin/rides/ride_lookup_page.dart b/siro_admin/lib/views/admin/rides/ride_lookup_page.dart index 2a5b0c8c..bc35daf1 100644 --- a/siro_admin/lib/views/admin/rides/ride_lookup_page.dart +++ b/siro_admin/lib/views/admin/rides/ride_lookup_page.dart @@ -435,7 +435,7 @@ class _RidesDashboardScreenState extends State const Color(0xFFEF4444))), Obx(() => _buildStatCard( 'الإيرادات', - '${controller.totalRevenue.value.toStringAsFixed(0)}', + controller.totalRevenue.value.toStringAsFixed(0), Icons.payments_rounded, const Color(0xFFF59E0B))), ], @@ -817,8 +817,9 @@ class _RidesDashboardScreenState extends State GestureDetector( onTap: () async { String formattedPhone = phone; - if (!formattedPhone.startsWith('+')) + if (!formattedPhone.startsWith('+')) { formattedPhone = '+$formattedPhone'; + } final Uri launchUri = Uri(scheme: 'tel', path: formattedPhone); if (await canLaunchUrl(launchUri)) await launchUrl(launchUri); }, @@ -860,11 +861,13 @@ class _RidesDashboardScreenState extends State // Helper Methods for Status Color _getStatusColor(String status) { - if (status == 'Begin' || status == 'Arrived') + if (status == 'Begin' || status == 'Arrived') { return const Color(0xFF10B981); + } if (status == 'Finished') return const Color(0xFF14B8A6); - if (status.contains('Cancel') || status == 'TimeOut') + if (status.contains('Cancel') || status == 'TimeOut') { return const Color(0xFFEF4444); + } if (status == 'New') return const Color(0xFF3B82F6); return Colors.grey; } @@ -872,8 +875,9 @@ class _RidesDashboardScreenState extends State String _getStatusText(String status) { if (status == 'Begin' || status == 'Arrived') return 'جارية'; if (status == 'Finished') return 'مكتملة'; - if (status == 'CancelFromDriver' || status == 'CancelFromDriverAfterApply') + if (status == 'CancelFromDriver' || status == 'CancelFromDriverAfterApply') { return 'ألغى السائق'; + } if (status == 'CancelFromPassenger') return 'ألغى الراكب'; if (status == 'TimeOut') return 'انتهى الوقت'; if (status == 'New') return 'جديدة'; @@ -1147,12 +1151,6 @@ class _RideMapMonitorScreenState extends State { required String phone, required Color color, }) { - String displayPhone = phone; - if (!widget.isAdmin && phone.length > 4) { - displayPhone = - phone.substring(phone.length - 4).padLeft(phone.length, '*'); - } - return Row( children: [ Container( @@ -1192,8 +1190,9 @@ class _RideMapMonitorScreenState extends State { GestureDetector( onTap: () async { String formattedPhone = phone; - if (!formattedPhone.startsWith('+')) + if (!formattedPhone.startsWith('+')) { formattedPhone = '+$formattedPhone'; + } final Uri launchUri = Uri(scheme: 'tel', path: formattedPhone); if (await canLaunchUrl(launchUri)) await launchUrl(launchUri); }, diff --git a/siro_admin/lib/views/admin/rides/rides.dart b/siro_admin/lib/views/admin/rides/rides.dart index 2ef76da9..b0947271 100644 --- a/siro_admin/lib/views/admin/rides/rides.dart +++ b/siro_admin/lib/views/admin/rides/rides.dart @@ -9,7 +9,7 @@ import '../../widgets/mycircular.dart'; class Rides extends StatelessWidget { Rides({super.key}); - RideAdminController rideAdminController = Get.put(RideAdminController()); + final RideAdminController rideAdminController = Get.put(RideAdminController()); @override Widget build(BuildContext context) { return MyScafolld(title: 'Rides'.tr, isleading: true, body: [ diff --git a/siro_admin/lib/views/admin/security/audit_logs_page.dart b/siro_admin/lib/views/admin/security/audit_logs_page.dart index 02096555..c3135c90 100644 --- a/siro_admin/lib/views/admin/security/audit_logs_page.dart +++ b/siro_admin/lib/views/admin/security/audit_logs_page.dart @@ -2,15 +2,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/constant/colors.dart'; import 'package:siro_admin/controller/admin/security_v2_controller.dart'; -import 'package:intl/intl.dart'; class AuditLogsPage extends StatelessWidget { const AuditLogsPage({super.key}); @override Widget build(BuildContext context) { - final controller = Get.put(SecurityV2Controller()); - return Scaffold( backgroundColor: AppColor.bg, appBar: AppBar( diff --git a/siro_admin/lib/views/admin/server/monitor_server_page.dart b/siro_admin/lib/views/admin/server/monitor_server_page.dart index 6b435c61..4a062c9b 100644 --- a/siro_admin/lib/views/admin/server/monitor_server_page.dart +++ b/siro_admin/lib/views/admin/server/monitor_server_page.dart @@ -3,7 +3,7 @@ import 'package:get/get.dart'; import '../../../controller/server/server_monitor_controller.dart'; class ServerMonitorPage extends StatelessWidget { - const ServerMonitorPage({Key? key}) : super(key: key); + const ServerMonitorPage({super.key}); @override Widget build(BuildContext context) { diff --git a/siro_admin/lib/views/admin/staff/add_staff_page.dart b/siro_admin/lib/views/admin/staff/add_staff_page.dart index a167ad32..5cbb81d5 100644 --- a/siro_admin/lib/views/admin/staff/add_staff_page.dart +++ b/siro_admin/lib/views/admin/staff/add_staff_page.dart @@ -187,7 +187,7 @@ class AddStaffPage extends StatelessWidget { border: Border.all(color: Colors.white.withOpacity(0.05)), ), child: DropdownButtonFormField( - value: value, + initialValue: value, dropdownColor: fillColor, style: const TextStyle(color: Colors.white), decoration: InputDecoration( diff --git a/siro_admin/lib/views/admin/staff/pending_admins_page.dart b/siro_admin/lib/views/admin/staff/pending_admins_page.dart index d8e2f571..23167df9 100644 --- a/siro_admin/lib/views/admin/staff/pending_admins_page.dart +++ b/siro_admin/lib/views/admin/staff/pending_admins_page.dart @@ -33,7 +33,7 @@ class _PendingAdminsPageState extends State { }); } } catch (e) { - mySnackeBarError('فشل في جلب البيانات: $e'); + mySnackbarError('فشل في جلب البيانات: $e'); } finally { setState(() => _isLoading = false); } @@ -53,7 +53,7 @@ class _PendingAdminsPageState extends State { _fetchPendingAdmins(); // تحديث القائمة } } catch (e) { - mySnackeBarError('حدث خطأ: $e'); + mySnackbarError('حدث خطأ: $e'); } } diff --git a/siro_admin/lib/views/admin/static/advanced_analytics_page.dart b/siro_admin/lib/views/admin/static/advanced_analytics_page.dart index 9b3cd755..8f2a5bfb 100644 --- a/siro_admin/lib/views/admin/static/advanced_analytics_page.dart +++ b/siro_admin/lib/views/admin/static/advanced_analytics_page.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/constant/colors.dart'; import 'package:siro_admin/controller/admin/analytics_v2_controller.dart'; -import 'package:intl/intl.dart'; class AdvancedAnalyticsPage extends StatelessWidget { const AdvancedAnalyticsPage({super.key}); @@ -191,8 +190,9 @@ class AdvancedAnalyticsPage extends StatelessWidget { final passengers = data['passenger_daily'] as List? ?? []; final drivers = data['driver_daily'] as List? ?? []; - if (passengers.isEmpty && drivers.isEmpty) + if (passengers.isEmpty && drivers.isEmpty) { return const Center(child: Text('لا توجد بيانات')); + } List barGroups = []; int maxLength = diff --git a/siro_admin/lib/views/admin/static/notes_driver_page.dart b/siro_admin/lib/views/admin/static/notes_driver_page.dart index 8dc55cbb..6b7d834c 100644 --- a/siro_admin/lib/views/admin/static/notes_driver_page.dart +++ b/siro_admin/lib/views/admin/static/notes_driver_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:intl/intl.dart' hide TextDirection; import 'package:siro_admin/controller/functions/launch.dart'; import '../../../controller/admin/static_controller.dart'; diff --git a/siro_admin/lib/views/admin/static/static.dart b/siro_admin/lib/views/admin/static/static.dart index 9662de3b..bb7f91e0 100644 --- a/siro_admin/lib/views/admin/static/static.dart +++ b/siro_admin/lib/views/admin/static/static.dart @@ -26,8 +26,6 @@ class StaticDash extends StatelessWidget { @override Widget build(BuildContext context) { - final controller = Get.put(StaticController()); - return Scaffold( backgroundColor: _bg, body: GetBuilder( diff --git a/siro_admin/lib/views/admin/wallet/wallet.dart b/siro_admin/lib/views/admin/wallet/wallet.dart index 5e764fd4..3b646551 100644 --- a/siro_admin/lib/views/admin/wallet/wallet.dart +++ b/siro_admin/lib/views/admin/wallet/wallet.dart @@ -9,7 +9,7 @@ import '../../widgets/my_scafold.dart'; class Wallet extends StatelessWidget { Wallet({super.key}); - WalletAdminController walletAdminController = + final WalletAdminController walletAdminController = Get.put(WalletAdminController()); @override Widget build(BuildContext context) { diff --git a/siro_admin/lib/views/auth/login_page.dart b/siro_admin/lib/views/auth/login_page.dart index 22373360..ca2ca0d3 100644 --- a/siro_admin/lib/views/auth/login_page.dart +++ b/siro_admin/lib/views/auth/login_page.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/env/env.dart'; -import '../../controller/auth/login_controller.dart'; import '../../controller/auth/otp_helper.dart'; -import '../../controller/functions/crud.dart'; -import '../../print.dart'; +import '../widgets/snackbar.dart'; import '../admin/admin_home_page.dart'; // ─── Colors (نفس نظام الألوان المستخدم في التطبيق) ────────────────────────── @@ -15,10 +12,8 @@ class _C { static const border = Color(0xFF1F2D4A); static const accent = Color(0xFF00E5FF); static const accentGlow = Color(0x2200E5FF); - static const accentDim = Color(0xFF0097A7); static const textPrimary = Color(0xFFE8F0FE); static const textSec = Color(0xFF7A8BAA); - static const error = Color(0xFFFF5252); static const inputBg = Color(0xFF0C1120); } @@ -44,7 +39,7 @@ class _AdminLoginPageState extends State final phone = _phoneController.text.trim(); if (password.isEmpty) { - Get.snackbar('خطأ', 'يرجى إدخال كلمة المرور'); + mySnackbarError('يرجى إدخال كلمة المرور'); return; } @@ -506,7 +501,7 @@ class _SubmitButtonState extends State<_SubmitButton> children: [ Icon(Icons.send_rounded, color: Colors.white, size: 18), SizedBox(width: 10), - const Text( + Text( 'تسجيل الدخول', style: TextStyle( color: Colors.white, diff --git a/siro_admin/lib/views/auth/register_page.dart b/siro_admin/lib/views/auth/register_page.dart index 7ea3f1a3..e943acb3 100644 --- a/siro_admin/lib/views/auth/register_page.dart +++ b/siro_admin/lib/views/auth/register_page.dart @@ -5,7 +5,6 @@ import 'package:siro_admin/controller/auth/register_controller.dart'; class _C { static const bg = Color(0xFF0A0D14); static const card = Color(0xFF161D2E); - static const border = Color(0xFF1F2D4A); static const accent = Color(0xFF00E5FF); static const textPrimary = Color(0xFFE8F0FE); static const textSec = Color(0xFF7A8BAA); diff --git a/siro_admin/lib/views/invoice/add_invoice_page.dart b/siro_admin/lib/views/invoice/add_invoice_page.dart index f83456fe..291391a3 100644 --- a/siro_admin/lib/views/invoice/add_invoice_page.dart +++ b/siro_admin/lib/views/invoice/add_invoice_page.dart @@ -10,6 +10,7 @@ import '../../constant/box_name.dart'; import '../../constant/info.dart'; import '../../controller/functions/encrypt_decrypt.dart'; import '../../main.dart'; +import '../../views/widgets/snackbar.dart'; class AddInvoicePage extends StatefulWidget { const AddInvoicePage({super.key}); @@ -38,7 +39,6 @@ class _AddInvoicePageState extends State { Future uploadInvoice() async { if (!_formKey.currentState!.validate()) return; - final driverID = '123'; // قيمة افتراضية أو يمكن جلبها من الكونترولر final invoiceNumber = generateInvoiceNumber(); final amount = _amountController.text.trim(); final itemName = _itemNameController.text.trim(); @@ -56,7 +56,6 @@ class _AddInvoicePageState extends State { final uri = Uri.parse(AppLink.addInvoice); final request = http.MultipartRequest('POST', uri) - ..fields['driverID'] = driverID ..fields['invoiceNumber'] = invoiceNumber ..fields['amount'] = amount ..fields['name'] = itemName @@ -84,15 +83,7 @@ class _AddInvoicePageState extends State { } if (data['status'] == 'success') { - Get.snackbar( - 'نجاح', - 'تم حفظ الفاتورة بنجاح', - backgroundColor: Colors.green.withOpacity(0.1), - colorText: Colors.green[800], - snackPosition: SnackPosition.TOP, - margin: const EdgeInsets.all(10), - borderRadius: 20, - ); + mySnackbarSuccess('تم حفظ الفاتورة بنجاح'); _itemNameController.clear(); _amountController.clear(); @@ -103,20 +94,10 @@ class _AddInvoicePageState extends State { Get.back(result: true); }); } else { - Get.snackbar( - 'تنبيه', - data['message'] ?? 'حدث خطأ غير معروف', - backgroundColor: Colors.red.withOpacity(0.1), - colorText: Colors.red[800], - ); + mySnackbarWarning(data['message'] ?? 'حدث خطأ غير معروف'); } } catch (e) { - Get.snackbar( - 'خطأ في الاتصال', - e.toString(), - backgroundColor: Colors.red.withOpacity(0.1), - colorText: Colors.red[800], - ); + mySnackbarError(e.toString()); } finally { if (mounted) setState(() => _isLoading = false); } diff --git a/siro_admin/lib/views/invoice/invoice_list_page.dart b/siro_admin/lib/views/invoice/invoice_list_page.dart index 8ad5a4d6..1405bed3 100644 --- a/siro_admin/lib/views/invoice/invoice_list_page.dart +++ b/siro_admin/lib/views/invoice/invoice_list_page.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import '../../constant/links.dart'; import '../../controller/functions/crud.dart'; import '../../print.dart'; +import '../widgets/snackbar.dart'; import 'add_invoice_page.dart'; // نفترض أن هذا الموديل موجود في مشروعك، إذا لم يكن موجوداً يرجى إضافته أو تعديل الاستيراد @@ -55,9 +56,7 @@ class _InvoiceListPageState extends State { } else { if (mounted) { setState(() => isLoading = false); - Get.snackbar("تنبيه", "لا توجد فواتير لعرضها أو حدث خطأ", - backgroundColor: Colors.orange.withOpacity(0.2), - colorText: Colors.orange[900]); + mySnackbarWarning("لا توجد فواتير لعرضها أو حدث خطأ"); } } } catch (e) { @@ -325,8 +324,7 @@ class _InvoiceListPageState extends State { if (imageUrl != null && imageUrl.isNotEmpty) { _showImageDialog(context, imageUrl); } else { - Get.snackbar("تنبيه", "لا توجد صورة مرفقة", - backgroundColor: Colors.grey[200], colorText: Colors.black); + mySnackbarWarning("لا توجد صورة مرفقة"); } }, child: Padding( @@ -407,7 +405,7 @@ class _InvoiceListPageState extends State { crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - "$amount", + amount, style: TextStyle( color: moneyColor, fontWeight: FontWeight.w900, diff --git a/siro_admin/lib/views/widgets/circle_container.dart b/siro_admin/lib/views/widgets/circle_container.dart index 9702b34e..0a8ac5a5 100644 --- a/siro_admin/lib/views/widgets/circle_container.dart +++ b/siro_admin/lib/views/widgets/circle_container.dart @@ -9,11 +9,11 @@ class MyCircleContainer extends StatelessWidget { final Color borderColor; MyCircleContainer({ - Key? key, + super.key, required this.child, this.backgroundColor = AppColor.secondaryColor, this.borderColor = AppColor.accentColor, - }) : super(key: key); + }); final controller = Get.put(CircleController()); diff --git a/siro_admin/lib/views/widgets/elevated_btn.dart b/siro_admin/lib/views/widgets/elevated_btn.dart index 518a70eb..286cbd16 100644 --- a/siro_admin/lib/views/widgets/elevated_btn.dart +++ b/siro_admin/lib/views/widgets/elevated_btn.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -13,12 +12,12 @@ class MyElevatedButton extends StatelessWidget { final IconData? icon; const MyElevatedButton({ - Key? key, + super.key, required this.title, required this.onPressed, this.kolor, this.icon, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/siro_admin/lib/views/widgets/icon_widget_menu.dart b/siro_admin/lib/views/widgets/icon_widget_menu.dart index 604aa40c..efadc37a 100644 --- a/siro_admin/lib/views/widgets/icon_widget_menu.dart +++ b/siro_admin/lib/views/widgets/icon_widget_menu.dart @@ -5,11 +5,11 @@ import '../../constant/style.dart'; class IconWidgetMenu extends StatelessWidget { const IconWidgetMenu({ - Key? key, + super.key, required this.onpressed, required this.icon, required this.title, - }) : super(key: key); + }); final VoidCallback onpressed; final IconData icon; diff --git a/siro_admin/lib/views/widgets/my_textField.dart b/siro_admin/lib/views/widgets/my_textField.dart index 5b465997..3dae10eb 100644 --- a/siro_admin/lib/views/widgets/my_textField.dart +++ b/siro_admin/lib/views/widgets/my_textField.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; import '../../constant/colors.dart'; import '../../constant/style.dart'; diff --git a/siro_admin/lib/views/widgets/mydialoug.dart b/siro_admin/lib/views/widgets/mydialoug.dart index 3089e04c..a8c2be4e 100755 --- a/siro_admin/lib/views/widgets/mydialoug.dart +++ b/siro_admin/lib/views/widgets/mydialoug.dart @@ -1,5 +1,4 @@ import 'dart:ui'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; diff --git a/siro_admin/lib/views/widgets/snackbar.dart b/siro_admin/lib/views/widgets/snackbar.dart index 0a129e4b..f2565b5b 100644 --- a/siro_admin/lib/views/widgets/snackbar.dart +++ b/siro_admin/lib/views/widgets/snackbar.dart @@ -2,122 +2,261 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import '../../constant/colors.dart'; +enum _SnackVariant { success, error, info, warning } -class SnackbarConfig { - static const duration = Duration(seconds: 3); - static const animationDuration = Duration(milliseconds: 300); - static const margin = EdgeInsets.symmetric(horizontal: 16, vertical: 10); - static const borderRadius = 12.0; - static const elevation = 6.0; +extension _VariantProps on _SnackVariant { + Color get baseColor => switch (this) { + _SnackVariant.success => const Color(0xFF1A9E5C), + _SnackVariant.error => const Color(0xFFD93025), + _SnackVariant.info => const Color(0xFF1A73E8), + _SnackVariant.warning => const Color(0xFFF29900), + }; - static final BoxShadow shadow = BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ); + Color get surfaceColor => switch (this) { + _SnackVariant.success => const Color(0xFFF0FBF5), + _SnackVariant.error => const Color(0xFFFEF2F1), + _SnackVariant.info => const Color(0xFFF0F6FF), + _SnackVariant.warning => const Color(0xFFFFF8E6), + }; + + IconData get icon => switch (this) { + _SnackVariant.success => Icons.check_circle_rounded, + _SnackVariant.error => Icons.error_rounded, + _SnackVariant.info => Icons.info_rounded, + _SnackVariant.warning => Icons.warning_amber_rounded, + }; + + String get label => switch (this) { + _SnackVariant.success => 'Success', + _SnackVariant.error => 'Error', + _SnackVariant.info => 'Info', + _SnackVariant.warning => 'Warning', + }; } -SnackbarController mySnackeBarError(String message) { - // Trigger error haptic feedback - HapticFeedback.mediumImpact(); +class _SnackContent extends StatefulWidget { + final String message; + final _SnackVariant variant; - return Get.snackbar( - 'Error'.tr, - message, - backgroundColor: AppColor.redColor.withOpacity(0.95), - colorText: AppColor.secondaryColor, - icon: const Icon( - Icons.error_outline_rounded, - color: AppColor.secondaryColor, - size: 28, - ), - shouldIconPulse: true, - snackPosition: SnackPosition.TOP, - margin: SnackbarConfig.margin, - borderRadius: SnackbarConfig.borderRadius, - duration: SnackbarConfig.duration, - animationDuration: SnackbarConfig.animationDuration, - forwardAnimationCurve: Curves.easeOutCirc, - reverseAnimationCurve: Curves.easeInCirc, - boxShadows: [SnackbarConfig.shadow], - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - titleText: Text( - 'Error'.tr, - style: const TextStyle( - fontWeight: FontWeight.w700, - color: Colors.white, - fontSize: 16, - letterSpacing: 0.2, + const _SnackContent({required this.message, required this.variant}); + + @override + State<_SnackContent> createState() => _SnackContentState(); +} + +class _SnackContentState extends State<_SnackContent> + with TickerProviderStateMixin { + late final AnimationController _ctrl; + late final AnimationController _scaleCtrl; + late final Animation _scaleAnim; + late final Animation _progressAnim; + + static const Duration _displayDuration = Duration(seconds: 4); + + @override + void initState() { + super.initState(); + _ctrl = AnimationController(vsync: this, duration: _displayDuration); + _scaleCtrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + _scaleAnim = CurvedAnimation( + parent: _scaleCtrl, + curve: Curves.elasticOut, + ); + _progressAnim = Tween(begin: 1.0, end: 0.0).animate( + CurvedAnimation(parent: _ctrl, curve: Curves.linear), + ); + _scaleCtrl.forward(); + _ctrl.forward(); + } + + @override + void dispose() { + _ctrl.dispose(); + _scaleCtrl.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final v = widget.variant; + final accent = v.baseColor; + final surface = v.surfaceColor; + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: surface, + borderRadius: BorderRadius.circular(18), + border: Border.all(color: accent.withAlpha(46), width: 1.2), + boxShadow: [ + BoxShadow( + color: accent.withAlpha(31), + blurRadius: 20, + spreadRadius: -2, + offset: const Offset(0, 6), + ), + BoxShadow( + color: Colors.black.withAlpha(15), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], ), - ), - messageText: Text( - message, - style: TextStyle( - color: Colors.white.withOpacity(0.95), - fontSize: 14, - height: 1.3, + child: ClipRRect( + borderRadius: BorderRadius.circular(18), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 10, 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ScaleTransition( + scale: _scaleAnim, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: accent.withAlpha(31), + shape: BoxShape.circle, + ), + child: Icon(v.icon, color: accent, size: 22), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + v.label.tr, + style: TextStyle( + color: accent, + fontSize: 13, + fontWeight: FontWeight.w700, + letterSpacing: 0.2, + ), + ), + const SizedBox(height: 3), + Text( + widget.message, + style: const TextStyle( + color: Color(0xFF424242), + fontSize: 13.5, + height: 1.4, + fontWeight: FontWeight.w400, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + GestureDetector( + onTap: () { + HapticFeedback.lightImpact(); + _closeSnackbar(context); + }, + child: Container( + width: 30, + height: 30, + margin: const EdgeInsets.only(left: 6), + decoration: BoxDecoration( + color: Colors.grey.withAlpha(25), + shape: BoxShape.circle, + ), + child: Icon( + Icons.close_rounded, + size: 16, + color: Colors.grey[500], + ), + ), + ), + ], + ), + ), + AnimatedBuilder( + animation: _progressAnim, + builder: (_, __) => Stack( + children: [ + Container(height: 3, color: accent.withAlpha(20)), + FractionallySizedBox( + widthFactor: _progressAnim.value, + child: Container( + height: 3, + decoration: BoxDecoration( + color: accent.withAlpha(115), + borderRadius: const BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + ), + ), + ), + ], + ), + ), + ], + ), ), - ), - onTap: (_) { + ); + } + + void _closeSnackbar(BuildContext context) { + ScaffoldMessenger.maybeOf(context)?.hideCurrentSnackBar(); + } +} + +int _retryCount = 0; + +void _show(_SnackVariant variant, String message) { + if (Get.context == null) { + if (_retryCount < 3) { + _retryCount++; + WidgetsBinding.instance.addPostFrameCallback((_) => _show(variant, message)); + } + return; + } + _retryCount = 0; + + final context = Get.context; + if (context == null) return; + + final messenger = ScaffoldMessenger.maybeOf(context); + if (messenger == null) return; + + messenger.clearSnackBars(); + + switch (variant) { + case _SnackVariant.error: + case _SnackVariant.warning: + HapticFeedback.mediumImpact(); + case _SnackVariant.success: HapticFeedback.lightImpact(); - Get.closeCurrentSnackbar(); - }, - isDismissible: true, - dismissDirection: DismissDirection.horizontal, - overlayBlur: 0.8, - overlayColor: Colors.black12, + case _SnackVariant.info: + HapticFeedback.selectionClick(); + } + + messenger.showSnackBar( + SnackBar( + content: _SnackContent(message: message, variant: variant), + backgroundColor: Colors.transparent, + elevation: 0, + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 4), + dismissDirection: DismissDirection.up, + ), ); } -SnackbarController mySnackbarSuccess(String message) { - // Trigger success haptic feedback - HapticFeedback.lightImpact(); - - return Get.snackbar( - 'Success'.tr, - message, - backgroundColor: AppColor.greenColor.withOpacity(0.95), - colorText: AppColor.secondaryColor, - icon: const Icon( - Icons.check_circle_outline_rounded, - color: AppColor.secondaryColor, - size: 28, - ), - shouldIconPulse: true, - snackPosition: SnackPosition.TOP, - margin: SnackbarConfig.margin, - borderRadius: SnackbarConfig.borderRadius, - duration: SnackbarConfig.duration, - animationDuration: SnackbarConfig.animationDuration, - forwardAnimationCurve: Curves.easeOutCirc, - reverseAnimationCurve: Curves.easeInCirc, - boxShadows: [SnackbarConfig.shadow], - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - titleText: Text( - 'Success'.tr, - style: const TextStyle( - fontWeight: FontWeight.w700, - color: Colors.white, - fontSize: 16, - letterSpacing: 0.2, - ), - ), - messageText: Text( - message, - style: TextStyle( - color: Colors.white.withOpacity(0.95), - fontSize: 14, - height: 1.3, - ), - ), - onTap: (_) { - HapticFeedback.lightImpact(); - Get.closeCurrentSnackbar(); - }, - isDismissible: true, - dismissDirection: DismissDirection.horizontal, - overlayBlur: 0.8, - overlayColor: Colors.black12, - ); -} +void mySnackbarSuccess(String message) => _show(_SnackVariant.success, message); +void mySnackbarError(String message) => _show(_SnackVariant.error, message); +void mySnackbarInfo(String message) => _show(_SnackVariant.info, message); +void mySnackbarWarning(String message) => _show(_SnackVariant.warning, message); diff --git a/siro_admin/pubspec.lock b/siro_admin/pubspec.lock index 7540cb07..225f75ed 100644 --- a/siro_admin/pubspec.lock +++ b/siro_admin/pubspec.lock @@ -617,7 +617,7 @@ packages: source: hosted version: "4.1.2" image: - dependency: transitive + dependency: "direct main" description: name: image sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce @@ -937,7 +937,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" diff --git a/siro_admin/pubspec.yaml b/siro_admin/pubspec.yaml index 2c07c2cf..a8467f36 100644 --- a/siro_admin/pubspec.yaml +++ b/siro_admin/pubspec.yaml @@ -69,6 +69,8 @@ dependencies: jailbreak_root_detection: ^1.1.5 package_info_plus: ^4.0.2 + image: any + path_provider: any dev_dependencies: flutter_test: sdk: flutter diff --git a/siro_driver/lib/controller/auth/captin/invit_controller.dart b/siro_driver/lib/controller/auth/captin/invit_controller.dart index d0cb835f..d8a0ac12 100755 --- a/siro_driver/lib/controller/auth/captin/invit_controller.dart +++ b/siro_driver/lib/controller/auth/captin/invit_controller.dart @@ -121,7 +121,7 @@ https://siromove.com/invite.php?code=$couponCode&app=rider // mySnackbarSuccess('Contacts sync completed successfully!'.tr); } } catch (e) { - // mySnackeBarError('An error occurred during contact sync: $e'.tr); + // mySnackbarError('An error occurred during contact sync: $e'.tr); } } @@ -183,7 +183,7 @@ https://siromove.com/invite.php?code=$couponCode&app=rider Get.back(); // إغلاق شاشة التحميل if (validContacts.isEmpty) { - mySnackeBarError('No contacts with phone numbers found'.tr); + mySnackbarError('No contacts with phone numbers found'.tr); return; } @@ -291,12 +291,12 @@ https://siromove.com/invite.php?code=$couponCode&app=rider ); } else { log('Permission DENIED', name: 'ContactPicker'); - mySnackeBarError('Contact permission is required to pick contacts'.tr); + mySnackbarError('Contact permission is required to pick contacts'.tr); } } catch (e) { if (Get.isDialogOpen ?? false) Get.back(); log('CRITICAL ERROR: $e', name: 'ContactPicker'); - mySnackeBarError('An error occurred while loading contacts: $e'.tr); + mySnackbarError('An error occurred while loading contacts: $e'.tr); } log('=== END: FETCHING CONTACTS ===', name: 'ContactPicker'); } @@ -383,7 +383,7 @@ https://siromove.com/invite.php?code=$couponCode&app=rider // refresh stats fetchDriverStats(); } else { - mySnackeBarError(data['message'] ?? 'Claim failed'.tr); + mySnackbarError(data['message'] ?? 'Claim failed'.tr); } } } else { @@ -475,13 +475,13 @@ https://siromove.com/invite.php?code=$couponCode&app=rider /// Sends an invitation to a potential new driver. void sendInvite() async { if (invitePhoneController.text.isEmpty) { - mySnackeBarError('Please enter a phone number'.tr); + mySnackbarError('Please enter a phone number'.tr); return; } String formattedPhoneNumber = CountryLogic.formatCurrentCountryPhone(invitePhoneController.text); if (formattedPhoneNumber.length != 12) { - mySnackeBarError('Please enter a correct phone'.tr); + mySnackbarError('Please enter a correct phone'.tr); return; } @@ -507,14 +507,14 @@ https://siromove.com/invite.php?code=$couponCode&app=rider launchCommunication('whatsapp', formattedPhoneNumber, message); invitePhoneController.clear(); } else { - mySnackeBarError("Invite code already used".tr); + mySnackbarError("Invite code already used".tr); } } /// Sends an invitation to a potential new passenger. void sendInviteToPassenger() async { if (invitePhoneController.text.isEmpty) { - mySnackeBarError('Please enter a phone number'.tr); + mySnackbarError('Please enter a phone number'.tr); return; } @@ -522,7 +522,7 @@ https://siromove.com/invite.php?code=$couponCode&app=rider if (formattedPhoneNumber.length < 12) { // +963 + 9 digits = 12+ - mySnackeBarError('Please enter a correct phone'.tr); + mySnackbarError('Please enter a correct phone'.tr); return; } @@ -552,7 +552,7 @@ https://siromove.com/invite.php?code=$couponCode&app=rider launchCommunication('whatsapp', formattedPhoneNumber, message); invitePhoneController.clear(); } else { - mySnackeBarError("Invite code already used".tr); + mySnackbarError("Invite code already used".tr); } } } diff --git a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart index 7ac23110..d53051e0 100755 --- a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart +++ b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart @@ -605,17 +605,17 @@ class LoginDriverController extends GetxController { Get.off(() => HomeCaptain()); } else { - mySnackeBarError('Login failed'.tr); + mySnackbarError('Login failed'.tr); isloading = false; update(); } } else { - mySnackeBarError('Server error'.tr); + mySnackbarError('Server error'.tr); isloading = false; update(); } } catch (e) { - mySnackeBarError('Network error'.tr); + mySnackbarError('Network error'.tr); isloading = false; update(); } diff --git a/siro_driver/lib/controller/auth/captin/opt_token_controller.dart b/siro_driver/lib/controller/auth/captin/opt_token_controller.dart index 4f7af9a6..bc73ce47 100644 --- a/siro_driver/lib/controller/auth/captin/opt_token_controller.dart +++ b/siro_driver/lib/controller/auth/captin/opt_token_controller.dart @@ -116,10 +116,10 @@ class OtpVerificationController extends GetxController { Get.offAll(() => HomeCaptain()); } else { - mySnackeBarError('OTP is incorrect or expired'.tr); + mySnackbarError('OTP is incorrect or expired'.tr); } } catch (e) { - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } finally { isVerifying.value = false; } diff --git a/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart b/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart index 06821928..d225fc06 100644 --- a/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart +++ b/siro_driver/lib/controller/auth/captin/phone_helper_controller.dart @@ -44,15 +44,15 @@ class PhoneAuthHelper { mySnackbarSuccess('An OTP has been sent to your number.'.tr); return true; } else { - mySnackeBarError(data['message'] ?? 'Failed to send OTP.'.tr); + mySnackbarError(data['message'] ?? 'Failed to send OTP.'.tr); return false; } } else { - mySnackeBarError('Server error. Please try again.'.tr); + mySnackbarError('Server error. Please try again.'.tr); return false; } } catch (e) { - // mySnackeBarError('An error occurred: $e'); + // mySnackbarError('An error occurred: $e'); return false; } } @@ -99,13 +99,13 @@ class PhoneAuthHelper { Get.to(() => RegistrationView()); } } else { - mySnackeBarError(data['message'] ?? 'Verification failed.'); + mySnackbarError(data['message'] ?? 'Verification failed.'); } } else { - mySnackeBarError('Server error. Please try again.'); + mySnackbarError('Server error. Please try again.'); } } catch (e) { - mySnackeBarError('An error occurred: $e'); + mySnackbarError('An error occurred: $e'); } } @@ -130,11 +130,11 @@ class PhoneAuthHelper { // Registration successful, log user in await _handleSuccessfulLogin(data['message']); } else { - mySnackeBarError( + mySnackbarError( "User with this phone number or email already exists.".tr); } } catch (e) { - mySnackeBarError('An error occurred: $e'); + mySnackbarError('An error occurred: $e'); } } diff --git a/siro_driver/lib/controller/auth/captin/register_captin_controller.dart b/siro_driver/lib/controller/auth/captin/register_captin_controller.dart index 64282ae7..1355bba2 100755 --- a/siro_driver/lib/controller/auth/captin/register_captin_controller.dart +++ b/siro_driver/lib/controller/auth/captin/register_captin_controller.dart @@ -143,7 +143,7 @@ class RegisterCaptainController extends GetxController { update(); } } else { - mySnackeBarError( + mySnackbarError( 'Phone Number wrong'.tr, ); } @@ -285,7 +285,7 @@ class RegisterCaptainController extends GetxController { // } } } else { - mySnackeBarError('you must insert token code '.tr); + mySnackbarError('you must insert token code '.tr); } } diff --git a/siro_driver/lib/controller/auth/syria/registration_controller.dart b/siro_driver/lib/controller/auth/syria/registration_controller.dart index fd457b74..92a9e010 100644 --- a/siro_driver/lib/controller/auth/syria/registration_controller.dart +++ b/siro_driver/lib/controller/auth/syria/registration_controller.dart @@ -220,7 +220,7 @@ class RegistrationController extends GetxController { // // الإرسال للذكاء الاصطناعي // await sendToAI(type, imageFile: outFile); } catch (e) { - mySnackeBarError('${'An unexpected error occurred:'.tr} $e'); + mySnackbarError('${'An unexpected error occurred:'.tr} $e'); } } @@ -344,7 +344,7 @@ class RegistrationController extends GetxController { Log.print('✅ Uploaded $imageType => $imageUrl'); } catch (e, st) { Log.print('❌ Error in choosImage: $e\n$st'); - mySnackeBarError('Image Upload Failed'.tr); + mySnackbarError('Image Upload Failed'.tr); } finally { isloading = false; update(); @@ -717,10 +717,10 @@ class RegistrationController extends GetxController { c.loginDriver(driverID, email); } else { final msg = (json?['message'] ?? 'Registration failed.').toString(); - mySnackeBarError(msg); + mySnackbarError(msg); } } catch (e) { - mySnackeBarError('Error: $e'); + mySnackbarError('Error: $e'); } finally { client.close(); isLoading.value = false; diff --git a/siro_driver/lib/controller/functions/crud.dart b/siro_driver/lib/controller/functions/crud.dart index 89235351..fc86373a 100755 --- a/siro_driver/lib/controller/functions/crud.dart +++ b/siro_driver/lib/controller/functions/crud.dart @@ -146,7 +146,7 @@ class CRUD { } on SocketException catch (_) { Log.print('⚠️ SocketException attempt $attempts — $link'); if (attempts >= 3) { - _netGuard.notifyOnce((title, msg) => mySnackeBarError(msg)); + _netGuard.notifyOnce((title, msg) => mySnackbarError(msg)); return 'no_internet'; } await Future.delayed(Duration(seconds: attempts)); diff --git a/siro_driver/lib/controller/functions/gemeni.dart b/siro_driver/lib/controller/functions/gemeni.dart index 4f92464b..a0c582a2 100755 --- a/siro_driver/lib/controller/functions/gemeni.dart +++ b/siro_driver/lib/controller/functions/gemeni.dart @@ -122,7 +122,7 @@ class AI extends GetxController { driverList: [], category: 'You have received a gift token!', ); } else { - // mySnackeBarError( + // mySnackbarError( // "You dont have invitation code".tr); // Localized error message } } @@ -137,7 +137,7 @@ class AI extends GetxController { update(); mySnackbarSuccess("Code approved".tr); } else { - mySnackeBarError("Code not approved".tr); + mySnackbarError("Code not approved".tr); } } } @@ -313,12 +313,12 @@ class AI extends GetxController { '${box.read(BoxName.phoneDriver)}${Env.email}'); mySnackbarSuccess('Driver data saved successfully'); } else { - mySnackeBarError('${'Failed to save driver data'.tr}: }'); + mySnackbarError('${'Failed to save driver data'.tr}: }'); } } catch (e) { isLoading = false; update(); - mySnackeBarError( + mySnackbarError( 'An error occurred while saving driver data'.tr, ); } @@ -1020,7 +1020,7 @@ class AI extends GetxController { update(); } else { - mySnackeBarError("JSON string not found"); + mySnackbarError("JSON string not found"); } // Rest of your code... diff --git a/siro_driver/lib/controller/functions/log_out.dart b/siro_driver/lib/controller/functions/log_out.dart index 6e0cc002..ed213c3a 100755 --- a/siro_driver/lib/controller/functions/log_out.dart +++ b/siro_driver/lib/controller/functions/log_out.dart @@ -77,7 +77,7 @@ class LogOutController extends GetxController { var id = await checkBeforeDelete(); deleteMyAccountDriver(id); } else { - mySnackeBarError('Your Name is Wrong'.tr); + mySnackbarError('Your Name is Wrong'.tr); } })); } @@ -148,7 +148,7 @@ class LogOutController extends GetxController { 'email': box.read(BoxName.email), }); } else { - mySnackeBarError( + mySnackbarError( 'Email you inserted is Wrong.'.tr, ); } diff --git a/siro_driver/lib/controller/functions/upload_image.dart b/siro_driver/lib/controller/functions/upload_image.dart index 2c6c5be4..5f0dc3e4 100755 --- a/siro_driver/lib/controller/functions/upload_image.dart +++ b/siro_driver/lib/controller/functions/upload_image.dart @@ -155,7 +155,7 @@ class ImageController extends GetxController { ); } catch (e) { print('Error in choosImage: $e'); - mySnackeBarError('Image Upload Failed'.tr); + mySnackbarError('Image Upload Failed'.tr); // Get.snackbar('Image Upload Failed'.tr, e.toString(), // backgroundColor: AppColor.primaryColor); } finally { @@ -218,7 +218,7 @@ class ImageController extends GetxController { print('Error in choosImage: $e'); // Get.snackbar('Image Upload Failed'.tr, e.toString(), // backgroundColor: AppColor.primaryColor); - mySnackeBarError('Image Upload Failed'.tr); + mySnackbarError('Image Upload Failed'.tr); } finally { isloading = false; update(); @@ -293,7 +293,7 @@ class ImageController extends GetxController { link, ); } catch (e) { - mySnackeBarError('Image Upload Failed'.tr); + mySnackbarError('Image Upload Failed'.tr); // Get.snackbar('Image Upload Failed'.tr, e.toString(), // backgroundColor: AppColor.redColor); } finally { @@ -439,7 +439,7 @@ class ImageController extends GetxController { } } catch (e) { Log.print('e: ${e}'); - mySnackeBarError('Image Upload Failed'.tr); + mySnackbarError('Image Upload Failed'.tr); } finally { isloading = false; update(); diff --git a/siro_driver/lib/controller/home/captin/duration_controller .dart b/siro_driver/lib/controller/home/captin/duration_controller .dart index d5968062..39b2acc0 100755 --- a/siro_driver/lib/controller/home/captin/duration_controller .dart +++ b/siro_driver/lib/controller/home/captin/duration_controller .dart @@ -136,7 +136,7 @@ class DurationController extends GetxController { update(); if (res == 'no_internet') { - mySnackeBarError('No internet connection'.tr); + mySnackbarError('No internet connection'.tr); } } } diff --git a/siro_driver/lib/controller/home/captin/help/assurance_controller.dart b/siro_driver/lib/controller/home/captin/help/assurance_controller.dart index 66e04720..74659b57 100755 --- a/siro_driver/lib/controller/home/captin/help/assurance_controller.dart +++ b/siro_driver/lib/controller/home/captin/help/assurance_controller.dart @@ -48,7 +48,7 @@ class AssuranceHealthController extends GetxController { } else { // Handle failure (e.g., show an error message) print("Failed to save health assurance data"); - mySnackeBarError("Please enter a health insurance status.".tr); + mySnackbarError("Please enter a health insurance status.".tr); } } catch (e) { // Handle any errors diff --git a/siro_driver/lib/controller/home/captin/help/video_controller.dart b/siro_driver/lib/controller/home/captin/help/video_controller.dart index 54744e8b..3859b5c9 100755 --- a/siro_driver/lib/controller/home/captin/help/video_controller.dart +++ b/siro_driver/lib/controller/home/captin/help/video_controller.dart @@ -82,10 +82,10 @@ class VideoController extends GetxController { box.write(lastFetchKey, DateTime.now().toIso8601String()); update(); } else { - mySnackeBarError(''); + mySnackbarError(''); } } catch (e) { - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } finally { isLoading(false); } diff --git a/siro_driver/lib/controller/home/captin/map_driver_controller.dart b/siro_driver/lib/controller/home/captin/map_driver_controller.dart index 18c6923d..d89c0ca4 100755 --- a/siro_driver/lib/controller/home/captin/map_driver_controller.dart +++ b/siro_driver/lib/controller/home/captin/map_driver_controller.dart @@ -544,7 +544,7 @@ class MapDriverController extends GetxController box.write(BoxName.statusDriverLocation, 'blocked'); // عرض رسالة العقوبة - mySnackeBarError( + mySnackbarError( "Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours." .tr, ); @@ -603,7 +603,7 @@ class MapDriverController extends GetxController if (Get.isDialogOpen == true) { Get.back(); } - mySnackeBarError("Failed to cancel ride".tr); + mySnackbarError("Failed to cancel ride".tr); } } catch (e) { if (Get.isDialogOpen == true) { @@ -1038,7 +1038,7 @@ class MapDriverController extends GetxController box.write(BoxName.rideStatus, 'Apply'); isPassengerInfoWindow = true; stopListeningStepNavigation(); - mySnackeBarError("Failed to start ride. Please try again."); + mySnackbarError("Failed to start ride. Please try again."); update(); } }).catchError((error) { @@ -1048,7 +1048,7 @@ class MapDriverController extends GetxController box.write(BoxName.rideStatus, 'Apply'); isPassengerInfoWindow = true; stopListeningStepNavigation(); - mySnackeBarError("Network error. Failed to start ride."); + mySnackbarError("Network error. Failed to start ride."); update(); }); } else { @@ -1075,7 +1075,7 @@ class MapDriverController extends GetxController Get.back(); } Log.print("Error starting ride: $e"); - mySnackeBarError("Could not start ride. Please check internet.".tr); + mySnackbarError("Could not start ride. Please check internet.".tr); } } @@ -1251,7 +1251,7 @@ class MapDriverController extends GetxController } catch (e) { if (Get.isDialogOpen == true) Get.back(); Log.print("Error: $e"); - mySnackeBarError("Transaction failed, please try again.".tr); + mySnackbarError("Transaction failed, please try again.".tr); } } @@ -1443,7 +1443,7 @@ class MapDriverController extends GetxController } catch (e) { if (Get.isDialogOpen == true) Get.back(); // إغلاق اللودينج Log.print("Error finishing ride: $e"); - mySnackeBarError("Failed to finish ride: $e"); + mySnackbarError("Failed to finish ride: $e"); // إعادة الحالة محلياً للسماح للمستخدم بالمحاولة مرة أخرى بأمان isRideFinished = false; @@ -1930,7 +1930,7 @@ class MapDriverController extends GetxController final String? encodedPoints = response['points']; if (encodedPoints == null) { - mySnackeBarError("No route points found".tr); + mySnackbarError("No route points found".tr); return; } @@ -1941,7 +1941,7 @@ class MapDriverController extends GetxController ); if (fullRoute.isEmpty) { - mySnackeBarError("Failed to process route points".tr); + mySnackbarError("Failed to process route points".tr); return; } @@ -2148,11 +2148,11 @@ class MapDriverController extends GetxController ); } else { if (Get.isDialogOpen == true) Get.back(); - mySnackeBarError("You must be closer than 100 meters to arrive".tr); + mySnackbarError("You must be closer than 100 meters to arrive".tr); } } catch (e) { if (Get.isDialogOpen == true) Get.back(); - mySnackeBarError("A connection error occurred".tr); + mySnackbarError("A connection error occurred".tr); } } diff --git a/siro_driver/lib/controller/home/invites_rewards_controller.dart b/siro_driver/lib/controller/home/invites_rewards_controller.dart index 8ef320b8..4124b593 100644 --- a/siro_driver/lib/controller/home/invites_rewards_controller.dart +++ b/siro_driver/lib/controller/home/invites_rewards_controller.dart @@ -66,13 +66,13 @@ class InvitesRewardsController extends GetxController { mySnackbarSuccess('Reward claimed successfully!'.tr); await getReferralStats(); // refresh list } else { - mySnackeBarError(jsonData['error'] ?? 'Failed to claim reward'.tr); + mySnackbarError(jsonData['error'] ?? 'Failed to claim reward'.tr); } } catch (e) { - mySnackeBarError('Failed to claim reward'.tr); + mySnackbarError('Failed to claim reward'.tr); } } else { - mySnackeBarError('Connection error'.tr); + mySnackbarError('Connection error'.tr); } } diff --git a/siro_driver/lib/controller/home/payment/captain_wallet_controller.dart b/siro_driver/lib/controller/home/payment/captain_wallet_controller.dart index ee9cf0e6..0bb2ce9f 100755 --- a/siro_driver/lib/controller/home/payment/captain_wallet_controller.dart +++ b/siro_driver/lib/controller/home/payment/captain_wallet_controller.dart @@ -79,10 +79,10 @@ class CaptainWalletController extends GetxController { '', ); } else { - mySnackeBarError(mapRes['message']?.toString() ?? 'Error'); + mySnackbarError(mapRes['message']?.toString() ?? 'Error'); } } else { - mySnackeBarError('Error processing request'.tr); + mySnackbarError('Error processing request'.tr); } } } @@ -113,10 +113,10 @@ class CaptainWalletController extends GetxController { amountToNewDriverMap = d['data']; // update(); } else { - mySnackeBarError("This driver is not registered".tr); + mySnackbarError("This driver is not registered".tr); } } else { - mySnackeBarError('Your Budget less than needed'.tr); + mySnackbarError('Your Budget less than needed'.tr); } } } @@ -250,7 +250,7 @@ class CaptainWalletController extends GetxController { } } else { Get.back(); - mySnackeBarError( + mySnackbarError( "A promotion record for this driver already exists for today.".tr); } } @@ -314,10 +314,10 @@ class CaptainWalletController extends GetxController { await refreshCaptainWallet(); })); } else { - mySnackeBarError(mapRes['message']?.toString() ?? 'Error'); + mySnackbarError(mapRes['message']?.toString() ?? 'Error'); } } else { - mySnackeBarError('Error processing request'.tr); + mySnackbarError('Error processing request'.tr); } } diff --git a/siro_driver/lib/controller/home/payment/paymob_payout.dart b/siro_driver/lib/controller/home/payment/paymob_payout.dart index f4d92aea..6239946e 100755 --- a/siro_driver/lib/controller/home/payment/paymob_payout.dart +++ b/siro_driver/lib/controller/home/payment/paymob_payout.dart @@ -68,7 +68,7 @@ class PaymobPayout extends GetxController { // Get.find().refreshCaptainWallet(); // } else if (dec['disbursement_status'] == 'failed') { - // mySnackeBarError('Transaction failed'.tr); + // mySnackbarError('Transaction failed'.tr); // } } else { MyDialog().getDialog('Authentication failed'.tr, ''.tr, () { @@ -166,7 +166,7 @@ class PaymobPayout extends GetxController { mySnackbarSuccess('${'Transaction successful'.tr} ${dec['amount']}'); Get.find().refreshCaptainWallet(); } else if (dec['disbursement_status'] == 'failed') { - mySnackeBarError('Transaction failed'.tr); + mySnackbarError('Transaction failed'.tr); } } else { MyDialog().getDialog('Authentication failed'.tr, ''.tr, () { diff --git a/siro_driver/lib/controller/home/profile/complaint_controller.dart b/siro_driver/lib/controller/home/profile/complaint_controller.dart index cfc9f2a5..a322d61e 100644 --- a/siro_driver/lib/controller/home/profile/complaint_controller.dart +++ b/siro_driver/lib/controller/home/profile/complaint_controller.dart @@ -43,7 +43,7 @@ class ComplaintController extends GetxController { if (title.toLowerCase() == 'success') { mySnackbarSuccess(message.tr); } else if (isError) { - mySnackeBarError(message.tr); + mySnackbarError(message.tr); } else { mySnackbarWarning(message.tr); } diff --git a/siro_driver/lib/controller/profile/captain_profile_controller.dart b/siro_driver/lib/controller/profile/captain_profile_controller.dart index d4b907b3..d5d173fa 100755 --- a/siro_driver/lib/controller/profile/captain_profile_controller.dart +++ b/siro_driver/lib/controller/profile/captain_profile_controller.dart @@ -33,7 +33,7 @@ class CaptainProfileController extends GetxController { update(); Get.back(); } else { - mySnackeBarError((res)['message']); + mySnackbarError((res)['message']); } } diff --git a/siro_driver/lib/controller/rate/rate_app_controller.dart b/siro_driver/lib/controller/rate/rate_app_controller.dart index eeac168c..f275852b 100755 --- a/siro_driver/lib/controller/rate/rate_app_controller.dart +++ b/siro_driver/lib/controller/rate/rate_app_controller.dart @@ -31,7 +31,7 @@ class RatingController extends GetxController { if (await launchUrl(Uri.parse(url))) { await launchUrl(Uri.parse(url)); } else { - mySnackeBarError("Could not open the app store."); + mySnackbarError("Could not open the app store."); } } @@ -80,7 +80,7 @@ class RatingController extends GetxController { // Send confirmation email if the rating was successfully submitted } else { - mySnackeBarError('Failed to submit rating'); + mySnackbarError('Failed to submit rating'); } } catch (e) { // If JSON decoding fails, log the response directly @@ -88,7 +88,7 @@ class RatingController extends GetxController { // backgroundColor: AppColor.greenColor); } } else { - mySnackeBarError('Failed to connect to the server'); + mySnackbarError('Failed to connect to the server'); } } } diff --git a/siro_driver/lib/views/auth/captin/driver_car_controller.dart b/siro_driver/lib/views/auth/captin/driver_car_controller.dart index 1fae00d1..a95b01a8 100755 --- a/siro_driver/lib/views/auth/captin/driver_car_controller.dart +++ b/siro_driver/lib/views/auth/captin/driver_car_controller.dart @@ -65,7 +65,7 @@ class DriverCarController extends GetxController { fetchCatrsForDrivers(); } else { - mySnackeBarError(''); + mySnackbarError(''); } } @@ -84,7 +84,7 @@ class DriverCarController extends GetxController { if (response != 'failure') { mySnackbarSuccess('Updated'.tr); } else { - mySnackeBarError('Not updated'.tr); + mySnackbarError('Not updated'.tr); } } diff --git a/siro_driver/lib/views/auth/captin/invite_driver_screen.dart b/siro_driver/lib/views/auth/captin/invite_driver_screen.dart index 028cc978..55c1f6d5 100755 --- a/siro_driver/lib/views/auth/captin/invite_driver_screen.dart +++ b/siro_driver/lib/views/auth/captin/invite_driver_screen.dart @@ -313,7 +313,7 @@ class InviteScreen extends StatelessWidget { rewardsController.linkInviteCode(manualCodeController.text.trim()); manualCodeController.clear(); } else { - mySnackeBarError('Please enter a referral code'.tr); + mySnackbarError('Please enter a referral code'.tr); } }, child: Text('Link'.tr, style: const TextStyle(color: Colors.white, fontSize: 14)), diff --git a/siro_driver/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart b/siro_driver/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart index 2b914ffc..83752278 100755 --- a/siro_driver/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart +++ b/siro_driver/lib/views/home/Captin/mapDriverWidgets/passenger_info_window.dart @@ -192,7 +192,7 @@ class PassengerInfoWindow extends StatelessWidget { context, controller); } else { // هنا ممكن تظهر رسالة: تم منع الاتصال بسبب كثرة الإلغاءات - mySnackeBarError( + mySnackbarError( "You cannot call the passenger due to policy violations" .tr); } diff --git a/siro_driver/lib/views/home/my_wallet/walet_captain.dart b/siro_driver/lib/views/home/my_wallet/walet_captain.dart index 74d8fd99..10af535e 100755 --- a/siro_driver/lib/views/home/my_wallet/walet_captain.dart +++ b/siro_driver/lib/views/home/my_wallet/walet_captain.dart @@ -348,7 +348,7 @@ class WalletCaptainRefactored extends StatelessWidget { await controller.payFromBudget(); } else { Get.back(); - mySnackeBarError('Your Budget less than needed'.tr); + mySnackbarError('Your Budget less than needed'.tr); } } else { MyDialog().getDialog( diff --git a/siro_driver/lib/views/widgets/error_snakbar.dart b/siro_driver/lib/views/widgets/error_snakbar.dart index aba57f43..21f28003 100755 --- a/siro_driver/lib/views/widgets/error_snakbar.dart +++ b/siro_driver/lib/views/widgets/error_snakbar.dart @@ -2,12 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import '../../constant/colors.dart'; -import '../../main.dart'; -// ───────────────────────────────────────────────────────────────────────────── -// Snackbar variant definition -// ───────────────────────────────────────────────────────────────────────────── enum _SnackVariant { success, error, info, warning } extension _VariantProps on _SnackVariant { @@ -38,20 +33,8 @@ extension _VariantProps on _SnackVariant { _SnackVariant.info => 'Info', _SnackVariant.warning => 'Warning', }; - - HapticFeedbackType get haptic => switch (this) { - _SnackVariant.error => HapticFeedbackType.medium, - _SnackVariant.warning => HapticFeedbackType.medium, - _SnackVariant.success => HapticFeedbackType.light, - _SnackVariant.info => HapticFeedbackType.selection, - }; } -enum HapticFeedbackType { light, medium, selection } - -// ───────────────────────────────────────────────────────────────────────────── -// Core snackbar widget -// ───────────────────────────────────────────────────────────────────────────── class _SnackContent extends StatefulWidget { final String message; final _SnackVariant variant; @@ -65,6 +48,7 @@ class _SnackContent extends StatefulWidget { class _SnackContentState extends State<_SnackContent> with TickerProviderStateMixin { late final AnimationController _ctrl; + late final AnimationController _scaleCtrl; late final Animation _scaleAnim; late final Animation _progressAnim; @@ -74,25 +58,25 @@ class _SnackContentState extends State<_SnackContent> void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: _displayDuration); - + _scaleCtrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); _scaleAnim = CurvedAnimation( - parent: AnimationController( - vsync: this, - duration: const Duration(milliseconds: 500), - )..forward(), + parent: _scaleCtrl, curve: Curves.elasticOut, ); - _progressAnim = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _ctrl, curve: Curves.linear), ); - + _scaleCtrl.forward(); _ctrl.forward(); } @override void dispose() { _ctrl.dispose(); + _scaleCtrl.dispose(); super.dispose(); } @@ -107,16 +91,16 @@ class _SnackContentState extends State<_SnackContent> decoration: BoxDecoration( color: surface, borderRadius: BorderRadius.circular(18), - border: Border.all(color: accent.withOpacity(0.18), width: 1.2), + border: Border.all(color: accent.withAlpha(46), width: 1.2), boxShadow: [ BoxShadow( - color: accent.withOpacity(0.12), + color: accent.withAlpha(31), blurRadius: 20, spreadRadius: -2, offset: const Offset(0, 6), ), BoxShadow( - color: Colors.black.withOpacity(0.06), + color: Colors.black.withAlpha(15), blurRadius: 10, offset: const Offset(0, 2), ), @@ -127,28 +111,24 @@ class _SnackContentState extends State<_SnackContent> child: Column( mainAxisSize: MainAxisSize.min, children: [ - // ── Main row ────────────────────────────────────────────────── Padding( padding: const EdgeInsets.fromLTRB(14, 14, 10, 12), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Animated icon badge ScaleTransition( scale: _scaleAnim, child: Container( width: 40, height: 40, decoration: BoxDecoration( - color: accent.withOpacity(0.12), + color: accent.withAlpha(31), shape: BoxShape.circle, ), child: Icon(v.icon, color: accent, size: 22), ), ), const SizedBox(width: 12), - - // Text content Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -166,8 +146,8 @@ class _SnackContentState extends State<_SnackContent> const SizedBox(height: 3), Text( widget.message, - style: TextStyle( - color: Colors.grey[800], + style: const TextStyle( + color: Color(0xFF424242), fontSize: 13.5, height: 1.4, fontWeight: FontWeight.w400, @@ -178,19 +158,17 @@ class _SnackContentState extends State<_SnackContent> ], ), ), - - // Close button GestureDetector( onTap: () { HapticFeedback.lightImpact(); - Get.closeCurrentSnackbar(); + _closeSnackbar(context); }, child: Container( width: 30, height: 30, margin: const EdgeInsets.only(left: 6), decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.1), + color: Colors.grey.withAlpha(25), shape: BoxShape.circle, ), child: Icon( @@ -203,24 +181,17 @@ class _SnackContentState extends State<_SnackContent> ], ), ), - - // ── Thin progress strip ─────────────────────────────────────── AnimatedBuilder( animation: _progressAnim, builder: (_, __) => Stack( children: [ - // Track - Container( - height: 3, - color: accent.withOpacity(0.08), - ), - // Fill + Container(height: 3, color: accent.withAlpha(20)), FractionallySizedBox( widthFactor: _progressAnim.value, child: Container( height: 3, decoration: BoxDecoration( - color: accent.withOpacity(0.45), + color: accent.withAlpha(115), borderRadius: const BorderRadius.only( topRight: Radius.circular(4), bottomRight: Radius.circular(4), @@ -236,74 +207,57 @@ class _SnackContentState extends State<_SnackContent> ), ); } + + void _closeSnackbar(BuildContext context) { + ScaffoldMessenger.maybeOf(context)?.hideCurrentSnackBar(); + } } -// ───────────────────────────────────────────────────────────────────────────── -// Internal dispatcher — single source of truth -// ───────────────────────────────────────────────────────────────────────────── -SnackbarController _show(_SnackVariant variant, String message) { - // Dismiss any existing snackbar first (no stacking) - if (Get.isSnackbarOpen) Get.closeCurrentSnackbar(); +int _retryCount = 0; - switch (variant.haptic) { - case HapticFeedbackType.light: - HapticFeedback.lightImpact(); - case HapticFeedbackType.medium: +void _show(_SnackVariant variant, String message) { + if (Get.context == null) { + if (_retryCount < 3) { + _retryCount++; + WidgetsBinding.instance.addPostFrameCallback((_) => _show(variant, message)); + } + return; + } + _retryCount = 0; + + final context = Get.context; + if (context == null) return; + + final messenger = ScaffoldMessenger.maybeOf(context); + if (messenger == null) return; + + messenger.clearSnackBars(); + + switch (variant) { + case _SnackVariant.error: + case _SnackVariant.warning: HapticFeedback.mediumImpact(); - case HapticFeedbackType.selection: + case _SnackVariant.success: + HapticFeedback.lightImpact(); + case _SnackVariant.info: HapticFeedback.selectionClick(); } - final BuildContext? context = Get.context ?? navigatorKey.currentContext; - if (context != null) { - final overlay = Overlay.maybeOf(context); - if (overlay == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: Colors.transparent, - elevation: 0, - padding: EdgeInsets.zero, - content: _SnackContent(message: message, variant: variant), - duration: const Duration(seconds: 4), - ), - ); - return SnackbarController(GetSnackBar(title: '', message: '')); - } - } - - return Get.snackbar( - '', - '', - snackPosition: SnackPosition.TOP, - backgroundColor: Colors.transparent, - margin: EdgeInsets.zero, - padding: EdgeInsets.zero, - duration: const Duration(seconds: 4), - barBlur: 0, - overlayBlur: 0, - overlayColor: Colors.transparent, - isDismissible: true, - dismissDirection: DismissDirection.up, - forwardAnimationCurve: Curves.easeOutCubic, - reverseAnimationCurve: Curves.easeInCubic, - snackStyle: SnackStyle.FLOATING, - userInputForm: Form( - child: _SnackContent(message: message, variant: variant), + messenger.showSnackBar( + SnackBar( + content: _SnackContent(message: message, variant: variant), + backgroundColor: Colors.transparent, + elevation: 0, + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 4), + dismissDirection: DismissDirection.up, ), ); } -// ───────────────────────────────────────────────────────────────────────────── -// Public API — drop-in replacements for the old functions -// ───────────────────────────────────────────────────────────────────────────── -SnackbarController mySnackbarSuccess(String message) => - _show(_SnackVariant.success, message); - -SnackbarController mySnackeBarError(String message) => - _show(_SnackVariant.error, message); - -SnackbarController mySnackbarInfo(String message) => - _show(_SnackVariant.info, message); - -SnackbarController mySnackbarWarning(String message) => - _show(_SnackVariant.warning, message); +void mySnackbarSuccess(String message) => _show(_SnackVariant.success, message); +void mySnackbarError(String message) => _show(_SnackVariant.error, message); +void mySnackbarInfo(String message) => _show(_SnackVariant.info, message); +void mySnackbarWarning(String message) => _show(_SnackVariant.warning, message); diff --git a/siro_rider/lib/controller/auth/login_controller.dart b/siro_rider/lib/controller/auth/login_controller.dart index 5032b833..78e98e6c 100644 --- a/siro_rider/lib/controller/auth/login_controller.dart +++ b/siro_rider/lib/controller/auth/login_controller.dart @@ -300,7 +300,7 @@ class LoginController extends GetxController { if (decoded is! Map || decoded.isEmpty) return; if (decoded['status'] == 'failure' || decoded['status'] == 'Failure') { - mySnackeBarError("User does not exist.".tr); + mySnackbarError("User does not exist.".tr); return; } @@ -444,7 +444,7 @@ class LoginController extends GetxController { Get.offAll(() => const MapPagePassenger()); } catch (e) { addError('$e', 'loginUsingCredentials'); - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } finally { isloading = false; update(); diff --git a/siro_rider/lib/controller/auth/otp_controller.dart b/siro_rider/lib/controller/auth/otp_controller.dart index e3c70201..ed3b71e0 100644 --- a/siro_rider/lib/controller/auth/otp_controller.dart +++ b/siro_rider/lib/controller/auth/otp_controller.dart @@ -42,11 +42,11 @@ class PhoneAuthHelper { mySnackbarSuccess('An OTP has been sent to your number.'.tr); return true; } else { - mySnackeBarError(data['message'] ?? 'Failed to send OTP.'); + mySnackbarError(data['message'] ?? 'Failed to send OTP.'); return false; } } else { - mySnackeBarError('Server error. Please try again.'.tr); + mySnackbarError('Server error. Please try again.'.tr); return false; } } catch (e) { @@ -87,13 +87,13 @@ class PhoneAuthHelper { Get.to(() => RegistrationScreen(phoneNumber: phoneNumber)); } } else { - mySnackeBarError(data['message']); + mySnackbarError(data['message']); } } else { - mySnackeBarError('Server error. Please try again.'.tr); + mySnackbarError('Server error. Please try again.'.tr); } } catch (e) { - mySnackeBarError('An error occurred: $e'); + mySnackbarError('An error occurred: $e'); } } @@ -133,12 +133,12 @@ class PhoneAuthHelper { await _handleSuccessfulLogin(data['message']['data']); } else { - mySnackeBarError( + mySnackbarError( "User with this phone number or email already exists.".tr); } } catch (e) { Log.print('e: ${e}'); - mySnackeBarError('An error occurred: $e'); + mySnackbarError('An error occurred: $e'); } } diff --git a/siro_rider/lib/controller/auth/register_controller.dart b/siro_rider/lib/controller/auth/register_controller.dart index db4ccf38..3086ddf7 100644 --- a/siro_rider/lib/controller/auth/register_controller.dart +++ b/siro_rider/lib/controller/auth/register_controller.dart @@ -308,17 +308,17 @@ class RegisterController extends GetxController { box.read(BoxName.email).toString(), ); } else { - mySnackeBarError("The email or phone number is already registered.".tr); + mySnackbarError("The email or phone number is already registered.".tr); } } else { - mySnackeBarError("phone not verified".tr); + mySnackbarError("phone not verified".tr); } } else { - mySnackeBarError("you must insert token code".tr); + mySnackbarError("you must insert token code".tr); } } catch (e) { addError(e.toString(), 'passenger sign up '); - mySnackeBarError("Something went wrong. Please try again.".tr); + mySnackbarError("Something went wrong. Please try again.".tr); } } diff --git a/siro_rider/lib/controller/auth/token_otp_change_controller.dart b/siro_rider/lib/controller/auth/token_otp_change_controller.dart index e69cb8e2..1239f987 100644 --- a/siro_rider/lib/controller/auth/token_otp_change_controller.dart +++ b/siro_rider/lib/controller/auth/token_otp_change_controller.dart @@ -69,10 +69,10 @@ class OtpVerificationController extends GetxController { isLoading.value = true; // بإمكانك عرض رسالة نجاح هنا } else { - mySnackeBarError('Failed to send OTP'.tr); + mySnackbarError('Failed to send OTP'.tr); } } catch (e) { - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } finally { // isLoading.value = false; } @@ -108,10 +108,10 @@ class OtpVerificationController extends GetxController { Get.offAll(() => const MapPagePassenger()); } else { - mySnackeBarError('OTP is incorrect or expired'); + mySnackbarError('OTP is incorrect or expired'); } } catch (e) { - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } finally { isVerifying.value = false; } diff --git a/siro_rider/lib/controller/functions/crud.dart b/siro_rider/lib/controller/functions/crud.dart index 95e1c638..c9f5fc06 100644 --- a/siro_rider/lib/controller/functions/crud.dart +++ b/siro_rider/lib/controller/functions/crud.dart @@ -144,7 +144,7 @@ class CRUD { } on SocketException catch (_) { Log.print('⚠️ SocketException attempt $attempts — $link'); if (attempts >= 3) { - _netGuard.notifyOnce((title, msg) => mySnackeBarError(msg)); + _netGuard.notifyOnce((title, msg) => mySnackbarError(msg)); return 'no_internet'; } await Future.delayed(Duration(seconds: attempts)); diff --git a/siro_rider/lib/controller/functions/log_out.dart b/siro_rider/lib/controller/functions/log_out.dart index a843e0ac..f1bee96c 100644 --- a/siro_rider/lib/controller/functions/log_out.dart +++ b/siro_rider/lib/controller/functions/log_out.dart @@ -198,7 +198,7 @@ class LogOutController extends GetxController { 'email': box.read(BoxName.email), }); } else { - mySnackeBarError('Email you inserted is Wrong.'.tr); + mySnackbarError('Email you inserted is Wrong.'.tr); } } } diff --git a/siro_rider/lib/controller/functions/upload_image.dart b/siro_rider/lib/controller/functions/upload_image.dart index e251b74a..df680048 100644 --- a/siro_rider/lib/controller/functions/upload_image.dart +++ b/siro_rider/lib/controller/functions/upload_image.dart @@ -55,7 +55,7 @@ class ImageController extends GetxController { link, ); } catch (e) { - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } finally { isloading = false; update(); diff --git a/siro_rider/lib/controller/home/map/map_engine_controller.dart b/siro_rider/lib/controller/home/map/map_engine_controller.dart index 53dcf2c0..eba4181e 100644 --- a/siro_rider/lib/controller/home/map/map_engine_controller.dart +++ b/siro_rider/lib/controller/home/map/map_engine_controller.dart @@ -28,6 +28,7 @@ class MapEngineController extends GetxController { Set markers = {}; Set polyLines = {}; + int markerRevision = 0; List polylineCoordinates = []; Set polygons = {}; Set circles = {}; diff --git a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart index e5903a82..a4661aeb 100644 --- a/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart +++ b/siro_rider/lib/controller/home/map/ride_lifecycle_controller.dart @@ -1935,7 +1935,7 @@ class RideLifecycleController extends GetxController { Get.back(); await Future.delayed(const Duration(milliseconds: 120)); } catch (e) { - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } } @@ -2734,7 +2734,7 @@ class RideLifecycleController extends GetxController { throw Exception('Failed to save trip'); } } catch (e) { - mySnackeBarError('Failed to book trip: $e'.tr); + mySnackbarError('Failed to book trip: $e'.tr); } } @@ -3494,20 +3494,13 @@ class RideLifecycleController extends GetxController { minutes = (durationToAdd.inMinutes % 60).round(); if (polyLines.isNotEmpty) { - mapEngine.clearPolyline(); - await Future.delayed(Duration.zero); + mapEngine.polyLines.clear(); } rideConfirm = false; isMarkersShown = true; - update(); - await bottomSheet(); - - await mapEngine.playRouteAnimation( - mapEngine.polylineCoordinates, mapEngine.lastComputedBounds); - - // بناء Set كاملة دفعة واحدة ثم تعيينها + // بناء الـ markers أولاً ثم وضعها معاً مع polylines في تحديث واحد final Set newMarkers = { Marker( markerId: const MarkerId('start'), @@ -3542,11 +3535,33 @@ class RideLifecycleController extends GetxController { } } - // تعيين الـ markers مباشرة في mapEngine (بدون setter لتجنب update() مضاعف) mapEngine.markers = newMarkers; + mapEngine.markerRevision++; + Log.print( '✅ FIX P1 v2: ${newMarkers.length} markers placed — start: $startLoc, end: $endLoc'); - mapEngine.update(); + + // إضافة الـ markers مباشرة عبر الـ controller لضمان ظهورها + try { + final ctrl = mapEngine.mapController; + if (ctrl != null) { + for (final m in newMarkers) { + await ctrl.addMarker(m); + } + Log.print('✅ Added ${newMarkers.length} markers via controller.addMarker()'); + } else { + Log.print('⚠️ mapController is null, relying on declarative markers'); + } + } catch (e) { + Log.print('⚠️ addMarker via controller failed: $e'); + } + + await bottomSheet(); + + await mapEngine.playRouteAnimation( + mapEngine.polylineCoordinates, mapEngine.lastComputedBounds); + + update(); } catch (e, stackTrace) { if (isDrawingRoute) { isDrawingRoute = false; diff --git a/siro_rider/lib/controller/home/profile/complaint_controller.dart b/siro_rider/lib/controller/home/profile/complaint_controller.dart index 7da1b454..f00f52db 100644 --- a/siro_rider/lib/controller/home/profile/complaint_controller.dart +++ b/siro_rider/lib/controller/home/profile/complaint_controller.dart @@ -41,7 +41,7 @@ class ComplaintController extends GetxController { void _showCustomSnackbar(String title, String message, {bool isError = false}) { if (isError) { - mySnackeBarError(message.tr); + mySnackbarError(message.tr); } else { mySnackbarSuccess(message.tr); } diff --git a/siro_rider/lib/controller/home/profile/invit_controller.dart b/siro_rider/lib/controller/home/profile/invit_controller.dart index b8b267e7..fa817372 100644 --- a/siro_rider/lib/controller/home/profile/invit_controller.dart +++ b/siro_rider/lib/controller/home/profile/invit_controller.dart @@ -171,7 +171,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider } } catch (e) { Log.print('Error picking contacts: $e'); - mySnackeBarError('An error occurred while picking contacts: $e'.tr); + mySnackbarError('An error occurred while picking contacts: $e'.tr); } } @@ -189,7 +189,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider void sendInviteToPassenger() async { if (invitePhoneController.text.isEmpty || invitePhoneController.text.length < 9) { - mySnackeBarError('Please enter a correct phone'.tr); + mySnackbarError('Please enter a correct phone'.tr); return; } @@ -234,7 +234,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider } } catch (e) { Log.print("Error sending invite: $e"); - mySnackeBarError('An unexpected error occurred. Please try again.'.tr); + mySnackbarError('An unexpected error occurred. Please try again.'.tr); } } @@ -290,7 +290,7 @@ ${AppLink.inviteRedirectUrl}?code=$couponCode&app=rider ); fetchDriverStatsPassengers(); // Refresh list } else { - mySnackeBarError(response['message'] ?? 'Claim failed'.tr); + mySnackbarError(response['message'] ?? 'Claim failed'.tr); } } }, diff --git a/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart b/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart index 9e80cd1d..8d1a8146 100644 --- a/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart +++ b/siro_rider/lib/controller/home/profile/invites_rewards_controller.dart @@ -80,11 +80,11 @@ class InvitesRewardsController extends GetxController { mySnackbarWarning(response['message'] ?? "Could not add invite".tr); } } else { - mySnackeBarError("Network error occurred".tr); + mySnackbarError("Network error occurred".tr); } } catch (e) { Navigator.maybeOf(Get.context!)?.pop(); // close loading dialog only - mySnackeBarError("Network error occurred".tr); + mySnackbarError("Network error occurred".tr); } } } diff --git a/siro_rider/lib/controller/home/vip_waitting_page.dart b/siro_rider/lib/controller/home/vip_waitting_page.dart index 22d34e5d..0e593739 100644 --- a/siro_rider/lib/controller/home/vip_waitting_page.dart +++ b/siro_rider/lib/controller/home/vip_waitting_page.dart @@ -57,15 +57,15 @@ class VipOrderController extends GetxController { tripData.value = res['message']; } else { tripData.clear(); // Ensure empty list if no data - // mySnackeBarError('No trip data found'); + // mySnackbarError('No trip data found'); } } else { tripData.clear(); - // mySnackeBarError('Failed to fetch trip data'); + // mySnackbarError('Failed to fetch trip data'); } } catch (e) { tripData.clear(); - // mySnackeBarError('An error occurred: $e'); + // mySnackbarError('An error occurred: $e'); } finally { isLoading.value = false; } diff --git a/siro_rider/lib/controller/payment/payment_controller.dart b/siro_rider/lib/controller/payment/payment_controller.dart index c3c0d9b4..0ad5a264 100644 --- a/siro_rider/lib/controller/payment/payment_controller.dart +++ b/siro_rider/lib/controller/payment/payment_controller.dart @@ -431,7 +431,7 @@ class PaymentController extends GetxController { try { final phone = box.read(BoxName.phoneWallet) ?? walletphoneController.text.trim(); if (phone.isEmpty) { - mySnackeBarError('Please enter a phone number'.tr); + mySnackbarError('Please enter a phone number'.tr); return; } @@ -465,11 +465,11 @@ class PaymentController extends GetxController { amount: double.parse(amount), )); } else { - mySnackeBarError(resMap['message']?.toString() ?? 'Failed to create invoice'.tr); + mySnackbarError(resMap['message']?.toString() ?? 'Failed to create invoice'.tr); } } catch (e) { if (Get.isDialogOpen ?? false) Get.back(); - mySnackeBarError(e.toString()); + mySnackbarError(e.toString()); } } diff --git a/siro_rider/lib/controller/voice_call_controller.dart b/siro_rider/lib/controller/voice_call_controller.dart index a1cedddd..149dd3e0 100644 --- a/siro_rider/lib/controller/voice_call_controller.dart +++ b/siro_rider/lib/controller/voice_call_controller.dart @@ -259,7 +259,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver { final permissionStatus = await Permission.microphone.request(); if (!permissionStatus.isGranted) { _endCallInternal("permission_denied"); - mySnackeBarError("Microphone permission is required for voice calls".tr); + mySnackbarError("Microphone permission is required for voice calls".tr); return; } @@ -274,7 +274,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver { response == 'failure' || response['status'] != 'success') { _endCallInternal("session_creation_failed"); - mySnackeBarError("Microphone permission is required for voice calls".tr); + mySnackbarError("Microphone permission is required for voice calls".tr); return; } @@ -353,7 +353,7 @@ class VoiceCallController extends GetxController with WidgetsBindingObserver { final permissionStatus = await Permission.microphone.request(); if (!permissionStatus.isGranted) { declineCall(); - mySnackeBarError("Microphone permission is required for voice calls".tr); + mySnackbarError("Microphone permission is required for voice calls".tr); return; } diff --git a/siro_rider/lib/views/home/HomePage/share_app_page.dart b/siro_rider/lib/views/home/HomePage/share_app_page.dart index dd81f210..f557ca33 100644 --- a/siro_rider/lib/views/home/HomePage/share_app_page.dart +++ b/siro_rider/lib/views/home/HomePage/share_app_page.dart @@ -830,7 +830,7 @@ class ShareAppPage extends StatelessWidget { rewardsController.linkInviteCode(manualCodeController.text.trim()); manualCodeController.clear(); } else { - mySnackeBarError('Please enter a referral code'.tr); + mySnackbarError('Please enter a referral code'.tr); } }, child: Text('Link'.tr, style: const TextStyle(color: Colors.white, fontSize: 14)), diff --git a/siro_rider/lib/views/home/map_widget.dart/google_map_passenger_widget.dart b/siro_rider/lib/views/home/map_widget.dart/google_map_passenger_widget.dart index bead74f8..a5a49a14 100644 --- a/siro_rider/lib/views/home/map_widget.dart/google_map_passenger_widget.dart +++ b/siro_rider/lib/views/home/map_widget.dart/google_map_passenger_widget.dart @@ -56,8 +56,8 @@ class GoogleMapPassengerWidget extends StatelessWidget { Log.print('⚠️ onCameraIdle: mapController is NULL'); } }, - markers: controller.markers, - polylines: controller.polyLines, + markers: Set.of(controller.markers), + polylines: Set.of(controller.polyLines), polygons: controller.polygons, circles: controller.circles, initialCameraPosition: CameraPosition( diff --git a/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart b/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart index 43fef1ec..663d94e2 100644 --- a/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart +++ b/siro_rider/lib/views/home/map_widget.dart/map_menu_widget.dart @@ -387,10 +387,10 @@ class MapMenuWidget extends StatelessWidget { if (await canLaunchUrl(url)) { await launchUrl(url); } else { - mySnackeBarError('Could not launch driver app store.'); + mySnackbarError('Could not launch driver app store.'); } } catch (e) { - mySnackeBarError('Could not open the link.'); + mySnackbarError('Could not open the link.'); } } } diff --git a/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart b/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart index a3a39220..eea0741f 100644 --- a/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart +++ b/siro_rider/lib/views/home/my_wallet/payment_screen_cliq.dart @@ -64,7 +64,7 @@ class _PaymentScreenCliqState extends State with SingleTicker Future _submitProof() async { if (_proofController.text.trim().isEmpty) { - mySnackeBarError('Please paste the transfer message'.tr); + mySnackbarError('Please paste the transfer message'.tr); return; } setState(() => _status = 'verifying'); diff --git a/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart b/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart index 911c03bf..813fa81d 100644 --- a/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart +++ b/siro_rider/lib/views/home/my_wallet/payment_screen_mtn.dart @@ -65,7 +65,7 @@ class _PaymentScreenMtnState extends State with SingleTickerPr Future _submitProof() async { if (_proofController.text.trim().isEmpty) { - mySnackeBarError('Please paste the transfer message'.tr); + mySnackbarError('Please paste the transfer message'.tr); return; } setState(() => _status = 'verifying'); diff --git a/siro_rider/lib/views/home/profile/passenger_profile_page.dart b/siro_rider/lib/views/home/profile/passenger_profile_page.dart index 0c77a0da..02de2ff0 100644 --- a/siro_rider/lib/views/home/profile/passenger_profile_page.dart +++ b/siro_rider/lib/views/home/profile/passenger_profile_page.dart @@ -100,7 +100,7 @@ class _PassengerProfilePageState extends State { } void _showUploadError() { - mySnackeBarError('Failed to upload photo'.tr); + mySnackbarError('Failed to upload photo'.tr); } Future _showImageSourceSheet() async { diff --git a/siro_rider/lib/views/widgets/error_snakbar.dart b/siro_rider/lib/views/widgets/error_snakbar.dart index 8e8efc8b..21f28003 100644 --- a/siro_rider/lib/views/widgets/error_snakbar.dart +++ b/siro_rider/lib/views/widgets/error_snakbar.dart @@ -2,11 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; -import '../../constant/colors.dart'; -// ───────────────────────────────────────────────────────────────────────────── -// Snackbar variant definition -// ───────────────────────────────────────────────────────────────────────────── enum _SnackVariant { success, error, info, warning } extension _VariantProps on _SnackVariant { @@ -37,20 +33,8 @@ extension _VariantProps on _SnackVariant { _SnackVariant.info => 'Info', _SnackVariant.warning => 'Warning', }; - - HapticFeedbackType get haptic => switch (this) { - _SnackVariant.error => HapticFeedbackType.medium, - _SnackVariant.warning => HapticFeedbackType.medium, - _SnackVariant.success => HapticFeedbackType.light, - _SnackVariant.info => HapticFeedbackType.selection, - }; } -enum HapticFeedbackType { light, medium, selection } - -// ───────────────────────────────────────────────────────────────────────────── -// Core snackbar widget -// ───────────────────────────────────────────────────────────────────────────── class _SnackContent extends StatefulWidget { final String message; final _SnackVariant variant; @@ -74,21 +58,17 @@ class _SnackContentState extends State<_SnackContent> void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: _displayDuration); - _scaleCtrl = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); - _scaleAnim = CurvedAnimation( parent: _scaleCtrl, curve: Curves.elasticOut, ); - _progressAnim = Tween(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _ctrl, curve: Curves.linear), ); - _scaleCtrl.forward(); _ctrl.forward(); } @@ -111,16 +91,16 @@ class _SnackContentState extends State<_SnackContent> decoration: BoxDecoration( color: surface, borderRadius: BorderRadius.circular(18), - border: Border.all(color: accent.withOpacity(0.18), width: 1.2), + border: Border.all(color: accent.withAlpha(46), width: 1.2), boxShadow: [ BoxShadow( - color: accent.withOpacity(0.12), + color: accent.withAlpha(31), blurRadius: 20, spreadRadius: -2, offset: const Offset(0, 6), ), BoxShadow( - color: Colors.black.withOpacity(0.06), + color: Colors.black.withAlpha(15), blurRadius: 10, offset: const Offset(0, 2), ), @@ -131,28 +111,24 @@ class _SnackContentState extends State<_SnackContent> child: Column( mainAxisSize: MainAxisSize.min, children: [ - // ── Main row ────────────────────────────────────────────────── Padding( padding: const EdgeInsets.fromLTRB(14, 14, 10, 12), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Animated icon badge ScaleTransition( scale: _scaleAnim, child: Container( width: 40, height: 40, decoration: BoxDecoration( - color: accent.withOpacity(0.12), + color: accent.withAlpha(31), shape: BoxShape.circle, ), child: Icon(v.icon, color: accent, size: 22), ), ), const SizedBox(width: 12), - - // Text content Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -170,8 +146,8 @@ class _SnackContentState extends State<_SnackContent> const SizedBox(height: 3), Text( widget.message, - style: TextStyle( - color: Colors.grey[800], + style: const TextStyle( + color: Color(0xFF424242), fontSize: 13.5, height: 1.4, fontWeight: FontWeight.w400, @@ -182,19 +158,17 @@ class _SnackContentState extends State<_SnackContent> ], ), ), - - // Close button GestureDetector( onTap: () { HapticFeedback.lightImpact(); - Get.closeCurrentSnackbar(); + _closeSnackbar(context); }, child: Container( width: 30, height: 30, margin: const EdgeInsets.only(left: 6), decoration: BoxDecoration( - color: Colors.grey.withOpacity(0.1), + color: Colors.grey.withAlpha(25), shape: BoxShape.circle, ), child: Icon( @@ -207,24 +181,17 @@ class _SnackContentState extends State<_SnackContent> ], ), ), - - // ── Thin progress strip ─────────────────────────────────────── AnimatedBuilder( animation: _progressAnim, builder: (_, __) => Stack( children: [ - // Track - Container( - height: 3, - color: accent.withOpacity(0.08), - ), - // Fill + Container(height: 3, color: accent.withAlpha(20)), FractionallySizedBox( widthFactor: _progressAnim.value, child: Container( height: 3, decoration: BoxDecoration( - color: accent.withOpacity(0.45), + color: accent.withAlpha(115), borderRadius: const BorderRadius.only( topRight: Radius.circular(4), bottomRight: Radius.circular(4), @@ -240,84 +207,57 @@ class _SnackContentState extends State<_SnackContent> ), ); } + + void _closeSnackbar(BuildContext context) { + ScaffoldMessenger.maybeOf(context)?.hideCurrentSnackBar(); + } } -// ───────────────────────────────────────────────────────────────────────────── -// Internal dispatcher — single source of truth -// ───────────────────────────────────────────────────────────────────────────── int _retryCount = 0; -SnackbarController? _show(_SnackVariant variant, String message) { - // Prevent crash if Navigator or Overlay context is not yet initialized at early startup - if (Get.context == null || Get.overlayContext == null || Get.key.currentState?.overlay == null) { - debugPrint("⚠️ Cannot show snackbar: Overlay/Navigator is not ready yet. Message: $message"); - // Retry up to 3 times after the next frame (handles race condition during route transitions) +void _show(_SnackVariant variant, String message) { + if (Get.context == null) { if (_retryCount < 3) { _retryCount++; WidgetsBinding.instance.addPostFrameCallback((_) => _show(variant, message)); } - return null; + return; } _retryCount = 0; - try { - // Removed Get.closeCurrentSnackbar() because it causes async LateInitializationError in GetX at early startup + final context = Get.context; + if (context == null) return; - // We use ScaffoldMessenger instead of Get.snackbar because GetX's snackbar - // throws synchronous "No Overlay widget found" FlutterErrors when the - // Overlay isn't perfectly mounted, crashing the app globally. - final context = Get.context; - if (context == null) return null; + final messenger = ScaffoldMessenger.maybeOf(context); + if (messenger == null) return; - final messenger = ScaffoldMessenger.maybeOf(context); - if (messenger == null) { - debugPrint("⚠️ Cannot show snackbar: ScaffoldMessenger not found. Message: $message"); - return null; - } + messenger.clearSnackBars(); - messenger.clearSnackBars(); // Prevent stacking - - switch (variant.haptic) { - case HapticFeedbackType.light: - HapticFeedback.lightImpact(); - case HapticFeedbackType.medium: - HapticFeedback.mediumImpact(); - case HapticFeedbackType.selection: - HapticFeedback.selectionClick(); - } - - messenger.showSnackBar( - SnackBar( - content: _SnackContent(message: message, variant: variant), - backgroundColor: Colors.transparent, - elevation: 0, - margin: EdgeInsets.zero, - padding: EdgeInsets.zero, - behavior: SnackBarBehavior.floating, - duration: const Duration(seconds: 4), - dismissDirection: DismissDirection.up, - ), - ); - - return null; // We return null since we no longer use Get.snackbar's controller - } catch (e) { - debugPrint("⚠️ Exception caught showing snackbar: $e"); - return null; + switch (variant) { + case _SnackVariant.error: + case _SnackVariant.warning: + HapticFeedback.mediumImpact(); + case _SnackVariant.success: + HapticFeedback.lightImpact(); + case _SnackVariant.info: + HapticFeedback.selectionClick(); } + + messenger.showSnackBar( + SnackBar( + content: _SnackContent(message: message, variant: variant), + backgroundColor: Colors.transparent, + elevation: 0, + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 4), + dismissDirection: DismissDirection.up, + ), + ); } -// ───────────────────────────────────────────────────────────────────────────── -// Public API — drop-in replacements for the old functions -// ───────────────────────────────────────────────────────────────────────────── -SnackbarController? mySnackbarSuccess(String message) => - _show(_SnackVariant.success, message); - -SnackbarController? mySnackeBarError(String message) => - _show(_SnackVariant.error, message); - -SnackbarController? mySnackbarInfo(String message) => - _show(_SnackVariant.info, message); - -SnackbarController? mySnackbarWarning(String message) => - _show(_SnackVariant.warning, message); - +void mySnackbarSuccess(String message) => _show(_SnackVariant.success, message); +void mySnackbarError(String message) => _show(_SnackVariant.error, message); +void mySnackbarInfo(String message) => _show(_SnackVariant.info, message); +void mySnackbarWarning(String message) => _show(_SnackVariant.warning, message); diff --git a/siro_service/lib/views/widgets/mycircular.dart b/siro_service/lib/views/widgets/mycircular.dart index 8e821a7e..810a043e 100644 --- a/siro_service/lib/views/widgets/mycircular.dart +++ b/siro_service/lib/views/widgets/mycircular.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; @@ -41,171 +41,261 @@ class MyCircularProgressIndicator extends StatelessWidget { } } -class SnackbarConfig { - static const duration = Duration(seconds: 3); - static const animationDuration = Duration(milliseconds: 300); - static const margin = EdgeInsets.symmetric(horizontal: 16, vertical: 10); - static const borderRadius = 12.0; - static const elevation = 6.0; +enum _SnackVariant { success, error, info, warning } - static final BoxShadow shadow = BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ); +extension _VariantProps on _SnackVariant { + Color get baseColor => switch (this) { + _SnackVariant.success => const Color(0xFF1A9E5C), + _SnackVariant.error => const Color(0xFFD93025), + _SnackVariant.info => const Color(0xFF1A73E8), + _SnackVariant.warning => const Color(0xFFF29900), + }; + + Color get surfaceColor => switch (this) { + _SnackVariant.success => const Color(0xFFF0FBF5), + _SnackVariant.error => const Color(0xFFFEF2F1), + _SnackVariant.info => const Color(0xFFF0F6FF), + _SnackVariant.warning => const Color(0xFFFFF8E6), + }; + + IconData get icon => switch (this) { + _SnackVariant.success => Icons.check_circle_rounded, + _SnackVariant.error => Icons.error_rounded, + _SnackVariant.info => Icons.info_rounded, + _SnackVariant.warning => Icons.warning_amber_rounded, + }; + + String get label => switch (this) { + _SnackVariant.success => 'Success', + _SnackVariant.error => 'Error', + _SnackVariant.info => 'Info', + _SnackVariant.warning => 'Warning', + }; } -SnackbarController mySnackbarError(String message) { - // Trigger error haptic feedback - HapticFeedback.mediumImpact(); +class _SnackContent extends StatefulWidget { + final String message; + final _SnackVariant variant; - return Get.snackbar( - 'Error'.tr, - message, - backgroundColor: AppColor.redColor.withOpacity(0.95), - colorText: AppColor.secondaryColor, - icon: const Icon( - Icons.error_outline_rounded, - color: AppColor.secondaryColor, - size: 28, - ), - shouldIconPulse: true, - snackPosition: SnackPosition.TOP, - margin: SnackbarConfig.margin, - borderRadius: SnackbarConfig.borderRadius, - duration: SnackbarConfig.duration, - animationDuration: SnackbarConfig.animationDuration, - forwardAnimationCurve: Curves.easeOutCirc, - reverseAnimationCurve: Curves.easeInCirc, - boxShadows: [SnackbarConfig.shadow], - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - titleText: Text( - 'Error'.tr, - style: const TextStyle( - fontWeight: FontWeight.w700, - color: Colors.white, - fontSize: 16, - letterSpacing: 0.2, + const _SnackContent({required this.message, required this.variant}); + + @override + State<_SnackContent> createState() => _SnackContentState(); +} + +class _SnackContentState extends State<_SnackContent> + with TickerProviderStateMixin { + late final AnimationController _ctrl; + late final AnimationController _scaleCtrl; + late final Animation _scaleAnim; + late final Animation _progressAnim; + + static const Duration _displayDuration = Duration(seconds: 4); + + @override + void initState() { + super.initState(); + _ctrl = AnimationController(vsync: this, duration: _displayDuration); + _scaleCtrl = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + _scaleAnim = CurvedAnimation( + parent: _scaleCtrl, + curve: Curves.elasticOut, + ); + _progressAnim = Tween(begin: 1.0, end: 0.0).animate( + CurvedAnimation(parent: _ctrl, curve: Curves.linear), + ); + _scaleCtrl.forward(); + _ctrl.forward(); + } + + @override + void dispose() { + _ctrl.dispose(); + _scaleCtrl.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final v = widget.variant; + final accent = v.baseColor; + final surface = v.surfaceColor; + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: surface, + borderRadius: BorderRadius.circular(18), + border: Border.all(color: accent.withAlpha(46), width: 1.2), + boxShadow: [ + BoxShadow( + color: accent.withAlpha(31), + blurRadius: 20, + spreadRadius: -2, + offset: const Offset(0, 6), + ), + BoxShadow( + color: Colors.black.withAlpha(15), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], ), - ), - messageText: Text( - message, - style: TextStyle( - color: Colors.white.withOpacity(0.95), - fontSize: 14, - height: 1.3, + child: ClipRRect( + borderRadius: BorderRadius.circular(18), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 10, 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ScaleTransition( + scale: _scaleAnim, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: accent.withAlpha(31), + shape: BoxShape.circle, + ), + child: Icon(v.icon, color: accent, size: 22), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + v.label.tr, + style: TextStyle( + color: accent, + fontSize: 13, + fontWeight: FontWeight.w700, + letterSpacing: 0.2, + ), + ), + const SizedBox(height: 3), + Text( + widget.message, + style: const TextStyle( + color: Color(0xFF424242), + fontSize: 13.5, + height: 1.4, + fontWeight: FontWeight.w400, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + GestureDetector( + onTap: () { + HapticFeedback.lightImpact(); + _closeSnackbar(context); + }, + child: Container( + width: 30, + height: 30, + margin: const EdgeInsets.only(left: 6), + decoration: BoxDecoration( + color: Colors.grey.withAlpha(25), + shape: BoxShape.circle, + ), + child: Icon( + Icons.close_rounded, + size: 16, + color: Colors.grey[500], + ), + ), + ), + ], + ), + ), + AnimatedBuilder( + animation: _progressAnim, + builder: (_, __) => Stack( + children: [ + Container(height: 3, color: accent.withAlpha(20)), + FractionallySizedBox( + widthFactor: _progressAnim.value, + child: Container( + height: 3, + decoration: BoxDecoration( + color: accent.withAlpha(115), + borderRadius: const BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + ), + ), + ), + ], + ), + ), + ], + ), ), - ), - onTap: (_) { + ); + } + + void _closeSnackbar(BuildContext context) { + ScaffoldMessenger.maybeOf(context)?.hideCurrentSnackBar(); + } +} + +int _retryCount = 0; + +void _show(_SnackVariant variant, String message) { + if (Get.context == null) { + if (_retryCount < 3) { + _retryCount++; + WidgetsBinding.instance.addPostFrameCallback((_) => _show(variant, message)); + } + return; + } + _retryCount = 0; + + final context = Get.context; + if (context == null) return; + + final messenger = ScaffoldMessenger.maybeOf(context); + if (messenger == null) return; + + messenger.clearSnackBars(); + + switch (variant) { + case _SnackVariant.error: + case _SnackVariant.warning: + HapticFeedback.mediumImpact(); + case _SnackVariant.success: HapticFeedback.lightImpact(); - Get.closeCurrentSnackbar(); - }, - isDismissible: true, - dismissDirection: DismissDirection.horizontal, - overlayBlur: 0.8, - overlayColor: Colors.black12, + case _SnackVariant.info: + HapticFeedback.selectionClick(); + } + + messenger.showSnackBar( + SnackBar( + content: _SnackContent(message: message, variant: variant), + backgroundColor: Colors.transparent, + elevation: 0, + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 4), + dismissDirection: DismissDirection.up, + ), ); } -SnackbarController mySnackbarWarning(String message) { - HapticFeedback.mediumImpact(); - - return Get.snackbar( - 'Warning'.tr, - message, - backgroundColor: AppColor.yellowColor.withOpacity(0.95), - colorText: AppColor.writeColor, - icon: const Icon( - Icons.warning_amber_rounded, - color: AppColor.writeColor, - size: 28, - ), - shouldIconPulse: true, - snackPosition: SnackPosition.TOP, - margin: SnackbarConfig.margin, - borderRadius: SnackbarConfig.borderRadius, - duration: SnackbarConfig.duration, - animationDuration: SnackbarConfig.animationDuration, - forwardAnimationCurve: Curves.easeOutCirc, - reverseAnimationCurve: Curves.easeInCirc, - boxShadows: [SnackbarConfig.shadow], - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - titleText: Text( - 'Warning'.tr, - style: const TextStyle( - fontWeight: FontWeight.w700, - color: Colors.black87, - fontSize: 16, - letterSpacing: 0.2, - ), - ), - messageText: Text( - message, - style: const TextStyle( - color: Colors.black87, - fontSize: 14, - height: 1.3, - ), - ), - onTap: (_) { - HapticFeedback.lightImpact(); - Get.closeCurrentSnackbar(); - }, - isDismissible: true, - dismissDirection: DismissDirection.horizontal, - overlayBlur: 0.8, - overlayColor: Colors.black12, - ); -} - -SnackbarController mySnackbarSuccess(String message) { - // Trigger success haptic feedback - HapticFeedback.lightImpact(); - - return Get.snackbar( - 'Success'.tr, - message, - backgroundColor: AppColor.greenColor.withOpacity(0.95), - colorText: AppColor.secondaryColor, - icon: const Icon( - Icons.check_circle_outline_rounded, - color: AppColor.secondaryColor, - size: 28, - ), - shouldIconPulse: true, - snackPosition: SnackPosition.TOP, - margin: SnackbarConfig.margin, - borderRadius: SnackbarConfig.borderRadius, - duration: SnackbarConfig.duration, - animationDuration: SnackbarConfig.animationDuration, - forwardAnimationCurve: Curves.easeOutCirc, - reverseAnimationCurve: Curves.easeInCirc, - boxShadows: [SnackbarConfig.shadow], - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), - titleText: Text( - 'Success'.tr, - style: const TextStyle( - fontWeight: FontWeight.w700, - color: Colors.white, - fontSize: 16, - letterSpacing: 0.2, - ), - ), - messageText: Text( - message, - style: TextStyle( - color: Colors.white.withOpacity(0.95), - fontSize: 14, - height: 1.3, - ), - ), - onTap: (_) { - HapticFeedback.lightImpact(); - Get.closeCurrentSnackbar(); - }, - isDismissible: true, - dismissDirection: DismissDirection.horizontal, - overlayBlur: 0.8, - overlayColor: Colors.black12, - ); -} +void mySnackbarSuccess(String message) => _show(_SnackVariant.success, message); +void mySnackbarError(String message) => _show(_SnackVariant.error, message); +void mySnackbarInfo(String message) => _show(_SnackVariant.info, message); +void mySnackbarWarning(String message) => _show(_SnackVariant.warning, message); diff --git a/walletintaleq.intaleq.xyz/v2/main/ride/payment/delete.php b/walletintaleq.intaleq.xyz/v2/main/ride/payment/delete.php deleted file mode 100644 index e69de29b..00000000