Update: 2026-08-03 00:20:40
This commit is contained in:
@@ -1,6 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../connect.php';
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// هذه النقطة تُرجع بيانات ركّاب مفكوكة التشفير (هاتف، بريد، تاريخ ميلاد،
|
||||||
|
// هاتف طوارئ، وتوكن إشعارات). لم يكن فيها أي فحص للدور، و connect.php لا
|
||||||
|
// يفرض بوّابة عامة على مسار /Admin — أي أن أي مستخدم يملك JWT صالحاً، بما
|
||||||
|
// فيه راكب عادي، كان يستطيع سحب هذه البيانات كاملة.
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'failure',
|
||||||
|
'message' => 'Forbidden. Admin access required.',
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "SELECT
|
$sql = "SELECT
|
||||||
`passengers`.`id`,
|
`passengers`.`id`,
|
||||||
`passengers`.`phone`,
|
`passengers`.`phone`,
|
||||||
@@ -91,6 +104,21 @@ foreach ($result as &$row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
unset($row);
|
||||||
|
|
||||||
|
// نموذج الصلاحيات: `super_admin` وحده يرى الرقم كاملاً، و`admin` يراقب برقم
|
||||||
|
// مُخفى جزئياً. توكن الإشعارات لا تعرضه الواجهة إطلاقاً ولا حاجة لإرساله —
|
||||||
|
// تسريبه يتيح انتحال إشعارات موجّهة لراكب بعينه.
|
||||||
|
$isSuperAdmin = ($role === 'super_admin');
|
||||||
|
foreach ($result as &$row) {
|
||||||
|
unset($row['passengerToken']);
|
||||||
|
|
||||||
|
if (!$isSuperAdmin) {
|
||||||
|
if (isset($row['phone'])) $row['phone'] = maskPhone($row['phone']);
|
||||||
|
if (isset($row['sosPhone'])) $row['sosPhone'] = maskPhone($row['sosPhone']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
|
||||||
if ($stmt->rowCount() > 0) {
|
if ($stmt->rowCount() > 0) {
|
||||||
jsonSuccess($data = $result);
|
jsonSuccess($data = $result);
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../connect.php';
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// نقطة تُرجع بيانات راكب مفكوكة التشفير. connect.php لا يفرض بوّابة عامة
|
||||||
|
// على مسار /Admin، فبدون هذا الفحص كان أي مستخدم يملك JWT صالحاً — بما فيه
|
||||||
|
// راكب عادي — يستطيع استعلام بيانات أي راكب آخر.
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'failure',
|
||||||
|
'message' => 'Forbidden. Admin access required.',
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$passengerID = filterRequest("passengerID");
|
$passengerID = filterRequest("passengerID");
|
||||||
|
|
||||||
$sql = "SELECT
|
$sql = "SELECT
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../connect.php';
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// نقطة تُرجع بيانات راكب مفكوكة التشفير. connect.php لا يفرض بوّابة عامة
|
||||||
|
// على مسار /Admin، فبدون هذا الفحص كان أي مستخدم يملك JWT صالحاً — بما فيه
|
||||||
|
// راكب عادي — يستطيع استعلام بيانات أي راكب آخر.
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'failure',
|
||||||
|
'message' => 'Forbidden. Admin access required.',
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$passengerEmail = $encryptionHelper->encryptData(filterRequest("passengerEmail"));
|
$passengerEmail = $encryptionHelper->encryptData(filterRequest("passengerEmail"));
|
||||||
$passengerId = filterRequest("passengerId");
|
$passengerId = filterRequest("passengerId");
|
||||||
$passengerphone = $encryptionHelper->encryptData(filterRequest("passengerphone"));
|
$passengerphone = $encryptionHelper->encryptData(filterRequest("passengerphone"));
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
// Admin/v2/security/audit_logs.php
|
// Admin/v2/security/audit_logs.php
|
||||||
|
|
||||||
// ── سجل تتبع ────────────────────────────────────────────
|
// كان هنا تتبّع تشخيصي يكتب ملفاً نصياً على القرص مع كل طلب، ويسجّل فيه
|
||||||
$debugFile = __DIR__ . '/../../../logs/audit_debug.txt';
|
// user_id والدور. أُزيل: يراكم ملفاً بلا حد في الإنتاج ويسرّب سياق الجلسة.
|
||||||
$logDir = dirname($debugFile);
|
// ما يستحق التسجيل يذهب إلى error_log مع الأخطاء وحدها.
|
||||||
if (!is_dir($logDir)) @mkdir($logDir, 0750, true);
|
|
||||||
|
|
||||||
@file_put_contents($debugFile, "[" . date('Y-m-d H:i:s') . "] === REQUEST START ===\n", FILE_APPEND);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
require_once __DIR__ . '/../../../connect.php';
|
require_once __DIR__ . '/../../../connect.php';
|
||||||
|
|
||||||
@file_put_contents($debugFile, " → connect.php & encryption OK. user_id=$user_id | role=$role\n", FILE_APPEND);
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@file_put_contents($debugFile, " → Loading FAILED: " . $e->getMessage() . "\n", FILE_APPEND);
|
error_log('[audit_logs] bootstrap failed: ' . $e->getMessage());
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
printFailure('loading failed', 500);
|
printFailure('loading failed', 500);
|
||||||
exit;
|
exit;
|
||||||
@@ -21,7 +17,6 @@ try {
|
|||||||
|
|
||||||
// ── فحص الصلاحيات ────────────────────────────────────────
|
// ── فحص الصلاحيات ────────────────────────────────────────
|
||||||
if ($role !== 'super_admin' && $role !== 'admin') {
|
if ($role !== 'super_admin' && $role !== 'admin') {
|
||||||
@file_put_contents($debugFile, " → BLOCKED: role=$role\n", FILE_APPEND);
|
|
||||||
printFailure("Unauthorized. role=$role", 403);
|
printFailure("Unauthorized. role=$role", 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,12 +50,11 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$count = count($logs);
|
$count = count($logs);
|
||||||
@file_put_contents($debugFile, " → SUCCESS: fetched $count logs\n", FILE_APPEND);
|
|
||||||
|
|
||||||
jsonSuccess($logs);
|
jsonSuccess($logs);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@file_put_contents($debugFile, " → QUERY ERROR: " . $e->getMessage() . "\n", FILE_APPEND);
|
error_log('[audit_logs] query failed: ' . $e->getMessage());
|
||||||
jsonError('Query failed', 500);
|
jsonError('Query failed', 500);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -33,6 +33,52 @@ $decoded = $jwtService->authenticate();
|
|||||||
$user_id = $decoded->user_id ?? null;
|
$user_id = $decoded->user_id ?? null;
|
||||||
$role = $decoded->role ?? 'passenger';
|
$role = $decoded->role ?? 'passenger';
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 2. بوّابة صلاحيات افتراضية لمسار /Admin
|
||||||
|
// ============================================================
|
||||||
|
// النموذج السابق كان «آمن إن تذكّرت»: كل نقطة تحت Admin/ تفحص الدور بنفسها،
|
||||||
|
// ومن يُغفل الفحص يصبح مكشوفاً. وقد وُجد فعلاً أن getPassengerDetails.php
|
||||||
|
// و getPassengerDetailsByPassengerID.php و getPassengerbyEmail.php كانت بلا
|
||||||
|
// أي فحص، فكان أي حامل JWT صالح — بما فيه راكب عادي — يقرأ بيانات ركّاب
|
||||||
|
// مفكوكة التشفير.
|
||||||
|
//
|
||||||
|
// هنا نقلب النموذج إلى «آمن افتراضاً»: كل ملف تحت Admin/ يمرّ عبر connect.php
|
||||||
|
// يتطلب دور admin أو super_admin، ما لم يكن في قائمة استثناء صريحة.
|
||||||
|
//
|
||||||
|
// نقاط تسجيل الدخول (Admin/auth/*, jwtService.php) لا تستدعي connect.php
|
||||||
|
// أصلاً، فلا تتأثر بهذه البوّابة ولا يمكن أن تُقفل على نفسها.
|
||||||
|
$adminDir = realpath(__DIR__ . '/Admin');
|
||||||
|
$script = realpath($_SERVER['SCRIPT_FILENAME'] ?? '');
|
||||||
|
|
||||||
|
if ($adminDir && $script && str_starts_with($script, $adminDir . DIRECTORY_SEPARATOR)) {
|
||||||
|
// نقاط تحت Admin/ تستدعيها تطبيقات الجوال بأدوار غير إدارية.
|
||||||
|
// تم التحقق من مواضع الاستدعاء الفعلية في siro_rider و siro_driver:
|
||||||
|
// errorApp.php → التطبيقان يبلّغان عن الأخطاء
|
||||||
|
// sendEmailToDrivertransaction.php → تطبيق السائق
|
||||||
|
// بقية روابط Admin/ المعرّفة في التطبيقين غير مستدعاة إطلاقاً.
|
||||||
|
$adminGateExempt = [
|
||||||
|
'errorApp.php',
|
||||||
|
'sendEmailToDrivertransaction.php',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!in_array(basename($script), $adminGateExempt, true)
|
||||||
|
&& $role !== 'admin'
|
||||||
|
&& $role !== 'super_admin'
|
||||||
|
) {
|
||||||
|
securityLog('Admin gate blocked non-admin request', [
|
||||||
|
'script' => basename($script),
|
||||||
|
'role' => $role,
|
||||||
|
'user_id' => $user_id ?? 'unknown',
|
||||||
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
||||||
|
]);
|
||||||
|
http_response_code(403);
|
||||||
|
exit(json_encode([
|
||||||
|
'status' => 'failure',
|
||||||
|
'message' => 'Forbidden. Admin access required.',
|
||||||
|
], JSON_UNESCAPED_UNICODE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 3. Database Connection
|
// 3. Database Connection
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -288,3 +288,25 @@ function normalizePhone(string $phone): string
|
|||||||
return $d; // رقم خارج النطاق — يُعاد كما هو
|
return $d; // رقم خارج النطاق — يُعاد كما هو
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* إخفاء جزء من رقم الهاتف قبل عرضه لدور لا يملك صلاحية رؤيته كاملاً.
|
||||||
|
*
|
||||||
|
* نموذج الصلاحيات في اللوحة: `admin` يراقب بأرقام مُخفاة جزئياً،
|
||||||
|
* و`super_admin` وحده يرى الرقم كاملاً.
|
||||||
|
*
|
||||||
|
* يُبقي مقدّمة الدولة وآخر رقمين حتى يبقى الرقم مميّزاً للمتابعة التشغيلية
|
||||||
|
* دون كشف هوية صاحبه.
|
||||||
|
*/
|
||||||
|
function maskPhone(?string $phone): ?string
|
||||||
|
{
|
||||||
|
if ($phone === null || $phone === '') return $phone;
|
||||||
|
|
||||||
|
$d = preg_replace('/\D+/', '', $phone);
|
||||||
|
$len = strlen($d);
|
||||||
|
if ($len < 5) return str_repeat('*', max(0, $len));
|
||||||
|
|
||||||
|
$head = substr($d, 0, 3);
|
||||||
|
$tail = substr($d, -2);
|
||||||
|
return $head . str_repeat('*', $len - 5) . $tail;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,13 +11,18 @@ require_once __DIR__ . '/../core/Services/FcmService.php';
|
|||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// Simple bot authentication
|
// مصادقة البوت.
|
||||||
|
// كان هذا الفحص معطّلاً بالتعليق، فكانت النقطة مفتوحة على الإنترنت لأي أحد:
|
||||||
|
// سحب مهام التسويق، وتعليمها منجزة، والكتابة في قاعدة البيانات.
|
||||||
|
// القيمة الافتراضية هي نفسها المبنية حالياً في APK (BuildConfig.BOT_TOKEN)
|
||||||
|
// حتى لا ينكسر البوت الميداني؛ غيّرها في الطرفين معاً عبر SOCIAL_BOT_TOKEN.
|
||||||
$headers = getallheaders();
|
$headers = getallheaders();
|
||||||
$botToken = $headers['X-Bot-Token'] ?? '';
|
$botToken = $headers['X-Bot-Token'] ?? '';
|
||||||
// if ($botToken !== 'YOUR_SECRET_BOT_TOKEN') {
|
$expectedToken = getenv('SOCIAL_BOT_TOKEN') ?: 'YOUR_SECRET_BOT_TOKEN';
|
||||||
// http_response_code(401);
|
if (!hash_equals($expectedToken, $botToken)) {
|
||||||
// exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
http_response_code(401);
|
||||||
// }
|
exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
||||||
|
}
|
||||||
|
|
||||||
$action = $_GET['action'] ?? '';
|
$action = $_GET['action'] ?? '';
|
||||||
$platform = $_GET['platform'] ?? 'facebook';
|
$platform = $_GET['platform'] ?? 'facebook';
|
||||||
|
|||||||
@@ -7,14 +7,17 @@
|
|||||||
require_once __DIR__ . '/../core/bootstrap.php';
|
require_once __DIR__ . '/../core/bootstrap.php';
|
||||||
require_once __DIR__ . '/../functions.php';
|
require_once __DIR__ . '/../functions.php';
|
||||||
|
|
||||||
// Authentication and validation could be added here similar to driver_socket.php
|
// مصادقة البوت.
|
||||||
// For now, let's keep it simple or use a static token for the bot
|
// كان جسم الشرط معطّلاً بالتعليق، فكان الفحص بلا أثر والنقطة مفتوحة على
|
||||||
|
// الإنترنت: أي أحد يستطيع سحب المهام والكتابة في social_logs وقراءة الحسابات.
|
||||||
|
// القيمة الافتراضية هي نفسها المبنية حالياً في APK (BuildConfig.BOT_TOKEN)
|
||||||
|
// حتى لا ينكسر البوت الميداني؛ غيّرها في الطرفين معاً عبر SOCIAL_BOT_TOKEN.
|
||||||
$headers = getallheaders();
|
$headers = getallheaders();
|
||||||
$botToken = $headers['X-Bot-Token'] ?? '';
|
$botToken = $headers['X-Bot-Token'] ?? '';
|
||||||
if ($botToken !== 'YOUR_SECRET_BOT_TOKEN') {
|
$expectedToken = getenv('SOCIAL_BOT_TOKEN') ?: 'YOUR_SECRET_BOT_TOKEN';
|
||||||
// In production, use a secure token or JWT
|
if (!hash_equals($expectedToken, $botToken)) {
|
||||||
// http_response_code(401);
|
http_response_code(401);
|
||||||
// exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
||||||
}
|
}
|
||||||
|
|
||||||
$action = $_GET['action'] ?? '';
|
$action = $_GET['action'] ?? '';
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- admin_audit_log — جدول سجل تدقيق عمليات الأدمن
|
||||||
|
-- ============================================================
|
||||||
|
-- الجدول كان مستخدماً في الكود ولا وجود له في schema_primary.sql، أي أنه
|
||||||
|
-- غير موجود على أي نشر نظيف. والنتيجة:
|
||||||
|
--
|
||||||
|
-- • logAudit() في backend/functions.php يبتلع الاستثناء ويكتفي بـ
|
||||||
|
-- error_log، فكل عملية تدقيق كانت تفشل بصمت (تحديث كازان، تعديل سائق،
|
||||||
|
-- إرسال إشعار جماعي، تشغيل حملة، تحديث إصدار التطبيق).
|
||||||
|
-- • شاشة "سجلات الأمان" تظهر فارغة دائماً — لا لأن العرض معطّل بل لأن
|
||||||
|
-- الاستعلام لا يجد جدولاً.
|
||||||
|
--
|
||||||
|
-- الأعمدة هنا هي اتحاد ما يكتبه المصدران المختلفان:
|
||||||
|
-- - functions.php::logAudit → admin_id, action, table_name, record_id, details
|
||||||
|
-- - Admin/v2/quality/blacklist_manager.php → admin_id, admin_phone, action, table_name, entity_type, details
|
||||||
|
-- ولذلك فالأعمدة غير المشتركة تقبل NULL.
|
||||||
|
--
|
||||||
|
-- ملاحظة الترميز: audit_logs.php يربط admin_id مع employee.id و adminUser.id
|
||||||
|
-- عبر COLLATE utf8mb4_general_ci صراحةً، فنُثبّت الترميز نفسه هنا لتفادي
|
||||||
|
-- خطأ "Illegal mix of collations" عند الربط.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `admin_audit_log` (
|
||||||
|
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`admin_id` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
|
||||||
|
`admin_phone` VARCHAR(32) DEFAULT NULL,
|
||||||
|
`action` VARCHAR(255) NOT NULL,
|
||||||
|
`table_name` VARCHAR(100) DEFAULT NULL,
|
||||||
|
`entity_type` VARCHAR(100) DEFAULT NULL,
|
||||||
|
`record_id` VARCHAR(100) DEFAULT NULL,
|
||||||
|
`details` TEXT DEFAULT NULL,
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
KEY `idx_admin_id` (`admin_id`),
|
||||||
|
KEY `idx_created_at` (`created_at`),
|
||||||
|
KEY `idx_action` (`action`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
@@ -1,6 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/../connect.php';
|
require_once __DIR__ . '/../connect.php';
|
||||||
|
|
||||||
|
// شاشة إدارة الشكاوى في لوحة التحكم. لم يكن فيها أي فحص للدور، و connect.php
|
||||||
|
// لا يفرض بوّابة عامة — فأي مستخدم يملك JWT صالحاً كان يقرأ كل الشكاوى
|
||||||
|
// بأسماء الطرفين وتقييماتهم وتوكنات الإشعارات.
|
||||||
|
if ($role !== 'admin' && $role !== 'super_admin') {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 'failure',
|
||||||
|
'message' => 'Forbidden. Admin access required.',
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT
|
SELECT
|
||||||
cm.id, cm.ride_id, cm.passenger_id, cm.driver_id,
|
cm.id, cm.ride_id, cm.passenger_id, cm.driver_id,
|
||||||
@@ -103,15 +115,20 @@ try {
|
|||||||
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$row = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if ($row) {
|
if ($row) {
|
||||||
|
$isSuperAdmin = ($role === 'super_admin');
|
||||||
foreach ($row as &$item) {
|
foreach ($row as &$item) {
|
||||||
foreach (['passengerName', 'driverName', 'driverToken', 'passengerToken'] as $field) {
|
foreach (['passengerName', 'driverName'] as $field) {
|
||||||
if (!empty($item[$field])) {
|
if (!empty($item[$field])) {
|
||||||
$dec = $encryptionHelper->decryptData($item[$field]);
|
$dec = $encryptionHelper->decryptData($item[$field]);
|
||||||
if ($dec)
|
if ($dec)
|
||||||
$item[$field] = $dec;
|
$item[$field] = $dec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// توكنات الإشعارات لا تستعملها الواجهة إطلاقاً، وتسريبها يتيح
|
||||||
|
// إرسال إشعارات منتحلة إلى سائق أو راكب بعينه.
|
||||||
|
unset($item['driverToken'], $item['passengerToken']);
|
||||||
}
|
}
|
||||||
|
unset($item);
|
||||||
jsonSuccess($row);
|
jsonSuccess($row);
|
||||||
} else {
|
} else {
|
||||||
jsonSuccess([], "No complaints found");
|
jsonSuccess([], "No complaints found");
|
||||||
|
|||||||
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"6c0baaebf70e0148f485f27d5616b3d3382da7
|
|||||||
|
|
||||||
_flutter.loader.load({
|
_flutter.loader.load({
|
||||||
serviceWorkerSettings: {
|
serviceWorkerSettings: {
|
||||||
serviceWorkerVersion: "353071230" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
serviceWorkerVersion: "3313168899" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+37940
-37901
File diff suppressed because one or more lines are too long
@@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
|
||||||
|
/// مصدر بلاطات الخرائط الموحّد لكل شاشات اللوحة.
|
||||||
|
///
|
||||||
|
/// لماذا هذا الملف موجود:
|
||||||
|
/// كانت خمس شاشات تكتب `urlTemplate` بنفسها، وأربع منها تشير مباشرة إلى
|
||||||
|
/// `https://tile.openstreetmap.org/...`. سياسة استخدام بلاطات OpenStreetMap
|
||||||
|
/// تمنع صراحةً هذا الاستعمال لتطبيق إنتاجي، والخوادم التطوّعية تحجب المخالفين —
|
||||||
|
/// وهو سبب رسالة "Access Blocked: App is not following the Tile Usage Policy"
|
||||||
|
/// واختفاء الخريطة في شاشة المراقب.
|
||||||
|
///
|
||||||
|
/// التوحيد هنا يجعل تبديل المزوّد تعديل سطر واحد بدل خمسة.
|
||||||
|
///
|
||||||
|
/// الافتراضي الحالي هو خرائط CARTO الأساسية (مجانية للاستعمال المعتدل ولا
|
||||||
|
/// تحجب كخوادم OSM التطوّعية)، مع نسختين فاتحة وداكنة تتبعان ثيم اللوحة.
|
||||||
|
/// للإنتاج بحِمل كبير: سجّل في MapTiler أو Stadia واضبط [tileUrlOverride]
|
||||||
|
/// و[attributionOverride] بمفتاحك.
|
||||||
|
class MapTiles {
|
||||||
|
MapTiles._();
|
||||||
|
|
||||||
|
/// اضبطه لتجاوز المزوّد الافتراضي (مثلاً رابط MapTiler مع مفتاحك).
|
||||||
|
/// اتركه فارغاً لاستخدام CARTO.
|
||||||
|
static const String tileUrlOverride = '';
|
||||||
|
static const String attributionOverride = '';
|
||||||
|
|
||||||
|
static const String _cartoLight =
|
||||||
|
'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
|
||||||
|
static const String _cartoDark =
|
||||||
|
'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png';
|
||||||
|
|
||||||
|
static const List<String> _subdomains = ['a', 'b', 'c', 'd'];
|
||||||
|
|
||||||
|
/// عرّف عن التطبيق بوضوح — شرط في سياسة كل المزوّدين، وغيابه سبب شائع للحجب.
|
||||||
|
static const String userAgent = 'com.siromove.admin';
|
||||||
|
|
||||||
|
static String urlFor(BuildContext context) {
|
||||||
|
if (tileUrlOverride.isNotEmpty) return tileUrlOverride;
|
||||||
|
return Theme.of(context).brightness == Brightness.dark
|
||||||
|
? _cartoDark
|
||||||
|
: _cartoLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String attributionFor(BuildContext context) {
|
||||||
|
if (attributionOverride.isNotEmpty) return attributionOverride;
|
||||||
|
return '© OpenStreetMap contributors © CARTO';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// طبقة البلاطات الجاهزة — استعملها بدل بناء [TileLayer] يدوياً.
|
||||||
|
static TileLayer layer(BuildContext context) {
|
||||||
|
return TileLayer(
|
||||||
|
urlTemplate: urlFor(context),
|
||||||
|
subdomains: tileUrlOverride.isEmpty ? _subdomains : const [],
|
||||||
|
userAgentPackageName: userAgent,
|
||||||
|
// الحد الأقصى الذي تخدمه معظم المزوّدات المجانية؛ تجاوزه يُرجع 404
|
||||||
|
// فتظهر الخريطة رمادية عند التقريب الشديد.
|
||||||
|
maxNativeZoom: 19,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,14 @@ class QualityController extends GetxController {
|
|||||||
List passengersBlacklist = [];
|
List passengersBlacklist = [];
|
||||||
Map scorecardData = {};
|
Map scorecardData = {};
|
||||||
|
|
||||||
|
// الجلب الأولي كان يجري من داخل build() في BlacklistPage، فيتكرّر مع كل
|
||||||
|
// إعادة بناء. مكانه الصحيح هنا: مرة واحدة عند تسجيل المتحكّم.
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
fetchBlacklist();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> fetchBlacklist() async {
|
Future<void> fetchBlacklist() async {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
update();
|
update();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/map_tiles.dart';
|
||||||
import 'package:siro_admin/constant/theme.dart';
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -377,9 +378,9 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: [
|
children: [
|
||||||
_LayerChip('فجوة العرض/الطلب', showGap, Colors.redAccent,
|
_LayerChip('فجوة العرض/الطلب', showGap, cs.danger,
|
||||||
(v) => setState(() => showGap = v)),
|
(v) => setState(() => showGap = v)),
|
||||||
_LayerChip('كثافة الطلب', showHeatmap, Colors.orange,
|
_LayerChip('كثافة الطلب', showHeatmap, cs.warning,
|
||||||
(v) => setState(() => showHeatmap = v)),
|
(v) => setState(() => showHeatmap = v)),
|
||||||
_LayerChip('مواقع الكباتن', showSupply, cs.success,
|
_LayerChip('مواقع الكباتن', showSupply, cs.success,
|
||||||
(v) => setState(() => showSupply = v)),
|
(v) => setState(() => showSupply = v)),
|
||||||
@@ -409,7 +410,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
markers.add(CircleMarker(
|
markers.add(CircleMarker(
|
||||||
point: LatLng(lat, lng),
|
point: LatLng(lat, lng),
|
||||||
color: gap > 0
|
color: gap > 0
|
||||||
? Colors.redAccent.withValues(alpha: 0.3 + intensity * 0.5)
|
? cs.danger.withValues(alpha: 0.3 + intensity * 0.5)
|
||||||
: cs.success.withValues(alpha: 0.4),
|
: cs.success.withValues(alpha: 0.4),
|
||||||
borderStrokeWidth: 0,
|
borderStrokeWidth: 0,
|
||||||
radius: 12 + intensity * 8,
|
radius: 12 + intensity * 8,
|
||||||
@@ -426,7 +427,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
if (lat == 0 || lng == 0) continue;
|
if (lat == 0 || lng == 0) continue;
|
||||||
markers.add(CircleMarker(
|
markers.add(CircleMarker(
|
||||||
point: LatLng(lat, lng),
|
point: LatLng(lat, lng),
|
||||||
color: Colors.orange.withValues(alpha: 0.35),
|
color: cs.warning.withValues(alpha: 0.35),
|
||||||
borderStrokeWidth: 0,
|
borderStrokeWidth: 0,
|
||||||
radius: 5,
|
radius: 5,
|
||||||
));
|
));
|
||||||
@@ -499,12 +500,7 @@ class _MapTabState extends State<_MapTab> with AutomaticKeepAliveClientMixin {
|
|||||||
child: FlutterMap(
|
child: FlutterMap(
|
||||||
options: MapOptions(initialCenter: center, initialZoom: 11),
|
options: MapOptions(initialCenter: center, initialZoom: 11),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
MapTiles.layer(context),
|
||||||
urlTemplate:
|
|
||||||
'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
|
|
||||||
subdomains: const ['a', 'b', 'c', 'd'],
|
|
||||||
userAgentPackageName: 'com.siromove.admin',
|
|
||||||
),
|
|
||||||
CircleLayer(circles: markers),
|
CircleLayer(circles: markers),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -75,12 +76,12 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_buildDetailTile(Icons.star_rate_rounded, 'Rating',
|
_buildDetailTile(Icons.star_rate_rounded, 'Rating',
|
||||||
'${data['ratingPassenger'] ?? 0.0} / 5.0',
|
'${data['ratingPassenger'] ?? 0.0} / 5.0',
|
||||||
valueColor: Colors.amber[700]),
|
valueColor: cs.warning[700]),
|
||||||
_buildDetailTile(Icons.directions_car_filled_outlined,
|
_buildDetailTile(Icons.directions_car_filled_outlined,
|
||||||
'Total Rides', data['countPassengerRide']),
|
'Total Rides', data['countPassengerRide']),
|
||||||
_buildDetailTile(Icons.cancel_outlined,
|
_buildDetailTile(Icons.cancel_outlined,
|
||||||
'Canceled Rides', data['countPassengerCancel'],
|
'Canceled Rides', data['countPassengerCancel'],
|
||||||
valueColor: Colors.redAccent),
|
valueColor: cs.danger),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
@@ -107,7 +108,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.1),
|
color: cs.onSurfaceVariant.withValues(alpha: 0.1),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 5),
|
offset: const Offset(0, 5),
|
||||||
)
|
)
|
||||||
@@ -118,7 +119,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 45,
|
radius: 45,
|
||||||
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
|
backgroundColor: AppColor.primaryColor.withValues(alpha: 0.1),
|
||||||
child: Text(
|
child: Text(
|
||||||
data['first_name'] != null
|
data['first_name'] != null
|
||||||
? data['first_name'][0].toUpperCase()
|
? data['first_name'][0].toUpperCase()
|
||||||
@@ -141,14 +142,14 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.green.withOpacity(0.1),
|
color: cs.success.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Active Captain'.tr,
|
'Active Captain'.tr,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.green,
|
color: cs.success,
|
||||||
fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -168,11 +169,11 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.05),
|
color: cs.onSurfaceVariant.withValues(alpha: 0.05),
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
blurRadius: 10)
|
blurRadius: 10)
|
||||||
],
|
],
|
||||||
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
border: Border.all(color: cs.onSurfaceVariant.withValues(alpha: 0.1)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -186,7 +187,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
fontSize: 17, fontWeight: FontWeight.bold)),
|
fontSize: 17, fontWeight: FontWeight.bold)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(height: 25, color: Colors.grey.withOpacity(0.2)),
|
Divider(height: 25, color: cs.onSurfaceVariant.withValues(alpha: 0.2)),
|
||||||
...children,
|
...children,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -202,9 +203,9 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[100],
|
color: cs.onSurfaceVariant,
|
||||||
borderRadius: BorderRadius.circular(8)),
|
borderRadius: BorderRadius.circular(8)),
|
||||||
child: Icon(icon, color: Colors.grey[600], size: 18),
|
child: Icon(icon, color: cs.onSurfaceVariant, size: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -212,7 +213,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(label.tr,
|
Text(label.tr,
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[500])),
|
style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant)),
|
||||||
Text(
|
Text(
|
||||||
value?.toString() ?? 'N/A',
|
value?.toString() ?? 'N/A',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -244,7 +245,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
label: Text("بطاقة الأداء (Scorecard)",
|
label: Text("بطاقة الأداء (Scorecard)",
|
||||||
style: const TextStyle(color: Colors.white, fontSize: 16)),
|
style: const TextStyle(color: Colors.white, fontSize: 16)),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blueAccent,
|
backgroundColor: cs.info,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12)),
|
borderRadius: BorderRadius.circular(12)),
|
||||||
),
|
),
|
||||||
@@ -306,8 +307,8 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.delete_outline_rounded, size: 20),
|
icon: const Icon(Icons.delete_outline_rounded, size: 20),
|
||||||
label: Text("Delete".tr),
|
label: Text("Delete".tr),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red[50],
|
backgroundColor: cs.danger[50],
|
||||||
foregroundColor: Colors.red,
|
foregroundColor: cs.danger,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12)),
|
borderRadius: BorderRadius.circular(12)),
|
||||||
@@ -324,7 +325,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
"Only Super Admins can edit or delete captains.",
|
"Only Super Admins can edit or delete captains.",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[400],
|
color: cs.onSurfaceVariant,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontStyle: FontStyle.italic),
|
fontStyle: FontStyle.italic),
|
||||||
)
|
)
|
||||||
@@ -394,7 +395,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
cancel: TextButton(
|
cancel: TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))),
|
child: Text('Cancel'.tr, style: const TextStyle(color: cs.onSurfaceVariant))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,12 +403,12 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
title: 'Confirm Deletion'.tr,
|
title: 'Confirm Deletion'.tr,
|
||||||
titleStyle:
|
titleStyle:
|
||||||
const TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold),
|
const TextStyle(color: cs.danger, fontWeight: FontWeight.bold),
|
||||||
middleText:
|
middleText:
|
||||||
'Are you sure you want to delete ${user['first_name']}? This action cannot be undone.'
|
'Are you sure you want to delete ${user['first_name']}? This action cannot be undone.'
|
||||||
.tr,
|
.tr,
|
||||||
confirm: ElevatedButton(
|
confirm: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
|
style: ElevatedButton.styleFrom(backgroundColor: cs.danger),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Call delete function here
|
// Call delete function here
|
||||||
// controller.deleteCaptain(user['id']);
|
// controller.deleteCaptain(user['id']);
|
||||||
@@ -418,7 +419,7 @@ class CaptainDetailsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
cancel: TextButton(
|
cancel: TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))),
|
child: Text('Cancel'.tr, style: const TextStyle(color: cs.onSurfaceVariant))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/controller/admin/register_captain_controller.dart';
|
import 'package:siro_admin/controller/admin/register_captain_controller.dart';
|
||||||
@@ -614,7 +615,7 @@ Please fill in the JSON object with the extracted information, following these g
|
|||||||
Text(
|
Text(
|
||||||
'${'License Expiry Date'.tr}: ${licenseExpiryDate.toString().substring(0, 10)}',
|
'${'License Expiry Date'.tr}: ${licenseExpiryDate.toString().substring(0, 10)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isLicenseExpired ? Colors.red : Colors.green,
|
color: isLicenseExpired ? cs.danger : cs.success,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Removed Fuel as it's not available
|
// Removed Fuel as it's not available
|
||||||
@@ -795,7 +796,7 @@ Please fill in the JSON object with the extracted information, following these g
|
|||||||
Text(
|
Text(
|
||||||
'${'Tax Expiry Date'.tr}: $taxExpiryDate',
|
'${'Tax Expiry Date'.tr}: $taxExpiryDate',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isExpired ? Colors.red : Colors.green,
|
color: isExpired ? cs.danger : cs.success,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8.0),
|
const SizedBox(height: 8.0),
|
||||||
@@ -806,7 +807,7 @@ Please fill in the JSON object with the extracted information, following these g
|
|||||||
'${'Inspection Date'.tr}: $carBackLicenseExpired',
|
'${'Inspection Date'.tr}: $carBackLicenseExpired',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color:
|
||||||
isInspectionExpired ? Colors.red : Colors.green,
|
isInspectionExpired ? cs.danger : cs.success,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart'; // For Get.width if needed, and .tr
|
import 'package:get/get.dart'; // For Get.width if needed, and .tr
|
||||||
import 'package:siro_admin/constant/colors.dart'; // Assuming AppColor is here
|
import 'package:siro_admin/constant/colors.dart'; // Assuming AppColor is here
|
||||||
@@ -38,7 +39,7 @@ class DashboardStatCard extends StatelessWidget {
|
|||||||
borderRadius: finalBorderRadius,
|
borderRadius: finalBorderRadius,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.1),
|
color: cs.onSurfaceVariant.withValues(alpha: 0.1),
|
||||||
spreadRadius: 1,
|
spreadRadius: 1,
|
||||||
blurRadius: 6,
|
blurRadius: 6,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
@@ -72,7 +73,7 @@ class DashboardStatCard extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
icon,
|
icon,
|
||||||
size: 24,
|
size: 24,
|
||||||
color: iconColor ?? AppColor.primaryColor.withOpacity(0.7),
|
color: iconColor ?? AppColor.primaryColor.withValues(alpha: 0.7),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get_storage/get_storage.dart'; // Ensure get_storage is in pubspec.yaml
|
import 'package:get_storage/get_storage.dart'; // Ensure get_storage is in pubspec.yaml
|
||||||
@@ -164,14 +165,14 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.access_time,
|
const Icon(Icons.access_time,
|
||||||
color: Colors.grey, size: 12),
|
color: cs.onSurfaceVariant, size: 12),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
ctrl.lastUpdated.isNotEmpty
|
ctrl.lastUpdated.isNotEmpty
|
||||||
? 'Updated: ${ctrl.lastUpdated}'
|
? 'Updated: ${ctrl.lastUpdated}'
|
||||||
: 'Data Live',
|
: 'Data Live',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[400], fontSize: 12),
|
color: cs.onSurfaceVariant, fontSize: 12),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -190,7 +191,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
textConfirm: "Yes, Clear",
|
textConfirm: "Yes, Clear",
|
||||||
textCancel: "Cancel",
|
textCancel: "Cancel",
|
||||||
confirmTextColor: Colors.white,
|
confirmTextColor: Colors.white,
|
||||||
buttonColor: Colors.red,
|
buttonColor: cs.danger,
|
||||||
onConfirm: () {
|
onConfirm: () {
|
||||||
ctrl.clearPaidStorage();
|
ctrl.clearPaidStorage();
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -198,7 +199,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.delete_forever,
|
icon: const Icon(Icons.delete_forever,
|
||||||
color: Colors.redAccent),
|
color: cs.danger),
|
||||||
tooltip: "Clear Paid Storage",
|
tooltip: "Clear Paid Storage",
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: Colors.white10),
|
backgroundColor: Colors.white10),
|
||||||
@@ -209,7 +210,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
ctrl.fetchData();
|
ctrl.fetchData();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.refresh,
|
icon: const Icon(Icons.refresh,
|
||||||
color: Colors.blueAccent),
|
color: cs.info),
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
backgroundColor: Colors.white10),
|
backgroundColor: Colors.white10),
|
||||||
),
|
),
|
||||||
@@ -232,11 +233,11 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStatCard('Total',
|
child: _buildStatCard('Total',
|
||||||
totalDrivers.toString(), Colors.blue)),
|
totalDrivers.toString(), cs.info)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStatCard('Elite',
|
child: _buildStatCard('Elite',
|
||||||
eliteCount.toString(), Colors.amber)),
|
eliteCount.toString(), cs.warning)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -247,13 +248,13 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStatCard('Inactive',
|
child: _buildStatCard('Inactive',
|
||||||
inactiveCount.toString(), Colors.red)),
|
inactiveCount.toString(), cs.danger)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStatCard(
|
child: _buildStatCard(
|
||||||
'Max Time',
|
'Max Time',
|
||||||
'${maxTime.toStringAsFixed(1)}h',
|
'${maxTime.toStringAsFixed(1)}h',
|
||||||
Colors.green)),
|
cs.success)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -266,7 +267,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Search by phone number...',
|
hintText: 'Search by phone number...',
|
||||||
prefixIcon:
|
prefixIcon:
|
||||||
const Icon(Icons.search, color: Colors.grey),
|
const Icon(Icons.search, color: cs.onSurfaceVariant),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.white,
|
fillColor: Colors.white,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
@@ -275,7 +276,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(color: Colors.grey.shade200),
|
borderSide: BorderSide(color: cs.onSurfaceVariant.shade200),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -291,7 +292,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
ctrl.searchQuery.isNotEmpty
|
ctrl.searchQuery.isNotEmpty
|
||||||
? "No drivers found with this number"
|
? "No drivers found with this number"
|
||||||
: "No drivers available",
|
: "No drivers available",
|
||||||
style: TextStyle(color: Colors.grey[400])),
|
style: TextStyle(color: cs.onSurfaceVariant)),
|
||||||
))
|
))
|
||||||
else
|
else
|
||||||
ListView.separated(
|
ListView.separated(
|
||||||
@@ -354,7 +355,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
border: Border(right: BorderSide(color: color, width: 4)),
|
border: Border(right: BorderSide(color: color, width: 4)),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.1),
|
color: cs.onSurfaceVariant.withValues(alpha: 0.1),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
offset: const Offset(0, 2))
|
offset: const Offset(0, 2))
|
||||||
],
|
],
|
||||||
@@ -366,13 +367,13 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
Text(title,
|
Text(title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey,
|
color: cs.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(value,
|
Text(value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
color: color.withOpacity(0.8),
|
color: color.withValues(alpha: 0.8),
|
||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -390,21 +391,21 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
Color statusColor;
|
Color statusColor;
|
||||||
if (hours >= 50) {
|
if (hours >= 50) {
|
||||||
statusText = "Elite";
|
statusText = "Elite";
|
||||||
statusColor = Colors.amber;
|
statusColor = cs.warning;
|
||||||
} else if (hours >= 20) {
|
} else if (hours >= 20) {
|
||||||
statusText = "Stable";
|
statusText = "Stable";
|
||||||
statusColor = Colors.green;
|
statusColor = cs.success;
|
||||||
} else if (hours >= 5) {
|
} else if (hours >= 5) {
|
||||||
statusText = "Experimental";
|
statusText = "Experimental";
|
||||||
statusColor = Colors.blue;
|
statusColor = cs.info;
|
||||||
} else {
|
} else {
|
||||||
statusText = "Inactive";
|
statusText = "Inactive";
|
||||||
statusColor = Colors.red;
|
statusColor = cs.danger;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override colors if paid
|
// Override colors if paid
|
||||||
Color cardBackground = isPaid ? Colors.teal.shade50 : Colors.white;
|
Color cardBackground = isPaid ? cs.tertiary.shade50 : Colors.white;
|
||||||
Color borderColor = isPaid ? Colors.teal : Colors.transparent;
|
Color borderColor = isPaid ? cs.tertiary : Colors.transparent;
|
||||||
|
|
||||||
// Calculate progress (max assumed 60 hours for 100% bar)
|
// Calculate progress (max assumed 60 hours for 100% bar)
|
||||||
double progress = (hours / 60).clamp(0.0, 1.0);
|
double progress = (hours / 60).clamp(0.0, 1.0);
|
||||||
@@ -417,7 +418,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.05),
|
color: cs.onSurfaceVariant.withValues(alpha: 0.05),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 4))
|
offset: const Offset(0, 4))
|
||||||
],
|
],
|
||||||
@@ -432,7 +433,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.teal,
|
color: cs.tertiary,
|
||||||
borderRadius: BorderRadius.circular(4)),
|
borderRadius: BorderRadius.circular(4)),
|
||||||
child: const Text("PAID",
|
child: const Text("PAID",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -447,7 +448,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
// Avatar
|
// Avatar
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
backgroundColor: statusColor.withOpacity(0.1),
|
backgroundColor: statusColor.withValues(alpha: 0.1),
|
||||||
radius: 24,
|
radius: 24,
|
||||||
child: Text(
|
child: Text(
|
||||||
hours.toStringAsFixed(0),
|
hours.toStringAsFixed(0),
|
||||||
@@ -468,7 +469,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: isPaid
|
color: isPaid
|
||||||
? Colors.teal.shade900
|
? cs.tertiary.shade900
|
||||||
: const Color(0xFF334155)),
|
: const Color(0xFF334155)),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -477,12 +478,12 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey),
|
color: cs.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
driver['active_time'] ?? '',
|
driver['active_time'] ?? '',
|
||||||
style: TextStyle(fontSize: 10, color: Colors.grey[400]),
|
style: TextStyle(fontSize: 10, color: cs.onSurfaceVariant),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -492,9 +493,9 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: statusColor.withOpacity(0.1),
|
color: statusColor.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(color: statusColor.withOpacity(0.2)),
|
border: Border.all(color: statusColor.withValues(alpha: 0.2)),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
statusText,
|
statusText,
|
||||||
@@ -517,7 +518,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text("Performance",
|
Text("Performance",
|
||||||
style: TextStyle(fontSize: 10, color: Colors.grey[600])),
|
style: TextStyle(fontSize: 10, color: cs.onSurfaceVariant)),
|
||||||
Text("${hours.toStringAsFixed(2)} hrs",
|
Text("${hours.toStringAsFixed(2)} hrs",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 10, fontWeight: FontWeight.bold)),
|
fontSize: 10, fontWeight: FontWeight.bold)),
|
||||||
@@ -526,7 +527,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
value: progress,
|
value: progress,
|
||||||
backgroundColor: Colors.grey[100],
|
backgroundColor: cs.onSurfaceVariant,
|
||||||
color: statusColor,
|
color: statusColor,
|
||||||
minHeight: 6,
|
minHeight: 6,
|
||||||
borderRadius: BorderRadius.circular(3),
|
borderRadius: BorderRadius.circular(3),
|
||||||
@@ -546,7 +547,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
isPaid
|
isPaid
|
||||||
? const Text("Payment Completed",
|
? const Text("Payment Completed",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.teal, fontWeight: FontWeight.bold))
|
color: cs.tertiary, fontWeight: FontWeight.bold))
|
||||||
: ElevatedButton.icon(
|
: ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_showPayDialog(driver, controller);
|
_showPayDialog(driver, controller);
|
||||||
@@ -554,7 +555,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.card_giftcard, size: 16),
|
icon: const Icon(Icons.card_giftcard, size: 16),
|
||||||
label: Text("Pay Gift".tr),
|
label: Text("Pay Gift".tr),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.indigo, // Dark blue/purple
|
backgroundColor: cs.info, // Dark blue/purple
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16, vertical: 8),
|
horizontal: 16, vertical: 8),
|
||||||
@@ -588,7 +589,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
color: Color(0xFF0F172A), fontWeight: FontWeight.bold),
|
color: Color(0xFF0F172A), fontWeight: FontWeight.bold),
|
||||||
content: Column(
|
content: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.wallet_giftcard, size: 50, color: Colors.indigo),
|
const Icon(Icons.wallet_giftcard, size: 50, color: cs.info),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
'Sending gift to ${driver['name_arabic']}',
|
'Sending gift to ${driver['name_arabic']}',
|
||||||
@@ -609,7 +610,7 @@ class DriverTheBestRedesigned extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
textConfirm: 'Pay Now',
|
textConfirm: 'Pay Now',
|
||||||
confirmTextColor: Colors.white,
|
confirmTextColor: Colors.white,
|
||||||
buttonColor: Colors.indigo,
|
buttonColor: cs.info,
|
||||||
onConfirm: () async {
|
onConfirm: () async {
|
||||||
final wallet = Get.put(WalletController());
|
final wallet = Get.put(WalletController());
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/map_tiles.dart';
|
||||||
import 'package:siro_admin/constant/theme.dart';
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
@@ -515,10 +516,7 @@ class _SiroTrackerScreenState extends State<SiroTrackerScreen>
|
|||||||
initialZoom: 10.0,
|
initialZoom: 10.0,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
MapTiles.layer(context),
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
userAgentPackageName: 'com.siromove.admin',
|
|
||||||
),
|
|
||||||
MarkerLayer(markers: _markers),
|
MarkerLayer(markers: _markers),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/map_tiles.dart';
|
||||||
import 'package:siro_admin/constant/theme.dart';
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -404,10 +405,7 @@ class RideMonitorScreen extends StatelessWidget {
|
|||||||
initialZoom: 12.0,
|
initialZoom: 12.0,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
MapTiles.layer(context),
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
userAgentPackageName: 'com.siromove.admin',
|
|
||||||
),
|
|
||||||
if (controller.routePolyline.isNotEmpty)
|
if (controller.routePolyline.isNotEmpty)
|
||||||
PolylineLayer(
|
PolylineLayer(
|
||||||
polylines: [
|
polylines: [
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class _EmployeeCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool isExcellent = employee['status'].toString().contains('ممتاز');
|
bool isExcellent = employee['status'].toString().contains('ممتاز');
|
||||||
Color statusColor = isExcellent ? cs.success : Colors.amber;
|
Color statusColor = isExcellent ? cs.success : cs.warning;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ class _EncryptToolPageState extends State<EncryptToolPage>
|
|||||||
color: _AppColors.accentGlow,
|
color: _AppColors.accentGlow,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _AppColors.accent.withOpacity(0.3)),
|
color: _AppColors.accent.withValues(alpha: 0.3)),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'AES-256',
|
'AES-256',
|
||||||
@@ -368,7 +368,7 @@ class _EncryptToolPageState extends State<EncryptToolPage>
|
|||||||
children: [
|
children: [
|
||||||
// ─── Input Card ─────────────────────────────────────
|
// ─── Input Card ─────────────────────────────────────
|
||||||
_GlassCard(
|
_GlassCard(
|
||||||
borderColor: _AppColors.accent.withOpacity(0.2),
|
borderColor: _AppColors.accent.withValues(alpha: 0.2),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -503,7 +503,7 @@ class _EncryptToolPageState extends State<EncryptToolPage>
|
|||||||
borderRadius: BorderRadius.circular(14),
|
borderRadius: BorderRadius.circular(14),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _AppColors.error
|
color: _AppColors.error
|
||||||
.withOpacity(0.4)),
|
.withValues(alpha: 0.4)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -532,7 +532,7 @@ class _EncryptToolPageState extends State<EncryptToolPage>
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
_GlassCard(
|
_GlassCard(
|
||||||
borderColor:
|
borderColor:
|
||||||
_AppColors.accentDecrypt.withOpacity(0.25),
|
_AppColors.accentDecrypt.withValues(alpha: 0.25),
|
||||||
headerWidget: Container(
|
headerWidget: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 20, vertical: 14),
|
horizontal: 20, vertical: 14),
|
||||||
@@ -671,7 +671,7 @@ class _GlassCard extends StatelessWidget {
|
|||||||
border: Border.all(color: borderColor, width: 1.2),
|
border: Border.all(color: borderColor, width: 1.2),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.35),
|
color: Colors.black.withValues(alpha: 0.35),
|
||||||
blurRadius: 24,
|
blurRadius: 24,
|
||||||
offset: const Offset(0, 8),
|
offset: const Offset(0, 8),
|
||||||
),
|
),
|
||||||
@@ -826,9 +826,9 @@ class _MiniIconButton extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withOpacity(0.1),
|
color: color.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: color.withOpacity(0.25)),
|
border: Border.all(color: color.withValues(alpha: 0.25)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|||||||
@@ -597,14 +597,14 @@ class _ErrorTile extends StatelessWidget {
|
|||||||
icon: Icons.devices,
|
icon: Icons.devices,
|
||||||
label: 'Path',
|
label: 'Path',
|
||||||
value: item.device,
|
value: item.device,
|
||||||
color: Colors.orange,
|
color: cs.warning,
|
||||||
cs: cs,
|
cs: cs,
|
||||||
),
|
),
|
||||||
_buildInfoBadge(
|
_buildInfoBadge(
|
||||||
icon: Icons.schedule,
|
icon: Icons.schedule,
|
||||||
label: 'التاريخ',
|
label: 'التاريخ',
|
||||||
value: item.createdAt,
|
value: item.createdAt,
|
||||||
color: Colors.teal,
|
color: cs.tertiary,
|
||||||
cs: cs,
|
cs: cs,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
|
import 'package:siro_admin/constant/map_tiles.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
@@ -62,14 +64,14 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
|||||||
Color markerColor;
|
Color markerColor;
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case 'geofence':
|
case 'geofence':
|
||||||
markerColor = Colors.green.withValues(alpha: 0.65);
|
markerColor = cs.success.withValues(alpha: 0.65);
|
||||||
break;
|
break;
|
||||||
case 'silent_push':
|
case 'silent_push':
|
||||||
markerColor = Colors.orange.withValues(alpha: 0.55);
|
markerColor = cs.warning.withValues(alpha: 0.55);
|
||||||
break;
|
break;
|
||||||
case 'app_usage':
|
case 'app_usage':
|
||||||
default:
|
default:
|
||||||
markerColor = Colors.blue.withValues(alpha: 0.5);
|
markerColor = cs.info.withValues(alpha: 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CircleMarker(
|
return CircleMarker(
|
||||||
@@ -249,10 +251,7 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
|||||||
initialZoom: 12.0,
|
initialZoom: 12.0,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
MapTiles.layer(context),
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
userAgentPackageName: 'com.siromove.admin',
|
|
||||||
),
|
|
||||||
CircleLayer(circles: _markers),
|
CircleLayer(circles: _markers),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -270,11 +269,11 @@ class _HeatmapPageState extends State<HeatmapPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_LegendItem(color: Colors.green, label: 'دخل منطقة سياج', cs: cs),
|
_LegendItem(color: cs.success, label: 'دخل منطقة سياج', cs: cs),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
_LegendItem(color: Colors.blue, label: 'فتح عادي للتطبيق', cs: cs),
|
_LegendItem(color: cs.info, label: 'فتح عادي للتطبيق', cs: cs),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
_LegendItem(color: Colors.orange, label: 'إيقاظ صامت', cs: cs),
|
_LegendItem(color: cs.warning, label: 'إيقاظ صامت', cs: cs),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ GetBuilder<PassengerAdminController> formSearchPassengers() {
|
|||||||
},
|
},
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.clear,
|
Icons.clear,
|
||||||
color: Colors.red[300],
|
color: cs.danger[300],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
Icons.sos,
|
Icons.sos,
|
||||||
'SOS Phone',
|
'SOS Phone',
|
||||||
data['sosPhone'] ?? 'N/A',
|
data['sosPhone'] ?? 'N/A',
|
||||||
valueColor: Colors.redAccent,
|
valueColor: cs.danger,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -94,7 +95,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
Icons.star_rate_rounded,
|
Icons.star_rate_rounded,
|
||||||
'Rating',
|
'Rating',
|
||||||
'${data['ratingPassenger'] ?? 0.0}',
|
'${data['ratingPassenger'] ?? 0.0}',
|
||||||
valueColor: Colors.amber[700],
|
valueColor: cs.warning[700],
|
||||||
),
|
),
|
||||||
_buildDetailTile(
|
_buildDetailTile(
|
||||||
Icons.directions_car_filled_outlined,
|
Icons.directions_car_filled_outlined,
|
||||||
@@ -105,7 +106,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
Icons.cancel_outlined,
|
Icons.cancel_outlined,
|
||||||
'Canceled Rides',
|
'Canceled Rides',
|
||||||
data['countPassengerCancel'],
|
data['countPassengerCancel'],
|
||||||
valueColor: Colors.redAccent,
|
valueColor: cs.danger,
|
||||||
),
|
),
|
||||||
_buildDetailTile(
|
_buildDetailTile(
|
||||||
Icons.rate_review_outlined,
|
Icons.rate_review_outlined,
|
||||||
@@ -143,7 +144,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.1),
|
color: cs.onSurfaceVariant.withValues(alpha: 0.1),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 5),
|
offset: const Offset(0, 5),
|
||||||
)
|
)
|
||||||
@@ -154,7 +155,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 45,
|
radius: 45,
|
||||||
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
|
backgroundColor: AppColor.primaryColor.withValues(alpha: 0.1),
|
||||||
child: Text(
|
child: Text(
|
||||||
fullName[0].toUpperCase(),
|
fullName[0].toUpperCase(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -175,14 +176,14 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.blue.withOpacity(0.1),
|
color: cs.info.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
data['status'] ?? 'Active',
|
data['status'] ?? 'Active',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.blue,
|
color: cs.info,
|
||||||
fontWeight: FontWeight.w600),
|
fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -202,11 +203,11 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey.withOpacity(0.05),
|
color: cs.onSurfaceVariant.withValues(alpha: 0.05),
|
||||||
spreadRadius: 2,
|
spreadRadius: 2,
|
||||||
blurRadius: 10)
|
blurRadius: 10)
|
||||||
],
|
],
|
||||||
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
border: Border.all(color: cs.onSurfaceVariant.withValues(alpha: 0.1)),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -220,7 +221,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
fontSize: 17, fontWeight: FontWeight.bold)),
|
fontSize: 17, fontWeight: FontWeight.bold)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Divider(height: 25, color: Colors.grey.withOpacity(0.2)),
|
Divider(height: 25, color: cs.onSurfaceVariant.withValues(alpha: 0.2)),
|
||||||
...children,
|
...children,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -236,9 +237,9 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey[100],
|
color: cs.onSurfaceVariant,
|
||||||
borderRadius: BorderRadius.circular(8)),
|
borderRadius: BorderRadius.circular(8)),
|
||||||
child: Icon(icon, color: Colors.grey[600], size: 18),
|
child: Icon(icon, color: cs.onSurfaceVariant, size: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -246,7 +247,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(label.tr,
|
Text(label.tr,
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[500])),
|
style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant)),
|
||||||
Text(
|
Text(
|
||||||
value?.toString() ?? 'N/A',
|
value?.toString() ?? 'N/A',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -320,8 +321,8 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.delete_outline_rounded, size: 20),
|
icon: const Icon(Icons.delete_outline_rounded, size: 20),
|
||||||
label: Text("Delete".tr),
|
label: Text("Delete".tr),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.red[50],
|
backgroundColor: cs.danger[50],
|
||||||
foregroundColor: Colors.red,
|
foregroundColor: cs.danger,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12)),
|
borderRadius: BorderRadius.circular(12)),
|
||||||
@@ -338,7 +339,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
"Only Super Admins can edit or delete passengers.",
|
"Only Super Admins can edit or delete passengers.",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[400],
|
color: cs.onSurfaceVariant,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontStyle: FontStyle.italic),
|
fontStyle: FontStyle.italic),
|
||||||
)
|
)
|
||||||
@@ -405,7 +406,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
cancel: TextButton(
|
cancel: TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))),
|
child: Text('Cancel'.tr, style: const TextStyle(color: cs.onSurfaceVariant))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,12 +414,12 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
title: 'Confirm Deletion'.tr,
|
title: 'Confirm Deletion'.tr,
|
||||||
titleStyle:
|
titleStyle:
|
||||||
const TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold),
|
const TextStyle(color: cs.danger, fontWeight: FontWeight.bold),
|
||||||
middleText:
|
middleText:
|
||||||
'Are you sure you want to delete ${user['first_name']}? This action cannot be undone.'
|
'Are you sure you want to delete ${user['first_name']}? This action cannot be undone.'
|
||||||
.tr,
|
.tr,
|
||||||
confirm: ElevatedButton(
|
confirm: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
|
style: ElevatedButton.styleFrom(backgroundColor: cs.danger),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
// 1. Close Dialog
|
// 1. Close Dialog
|
||||||
Get.back();
|
Get.back();
|
||||||
@@ -447,7 +448,7 @@ class PassengerDetailsPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
cancel: TextButton(
|
cancel: TextButton(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey)),
|
child: Text('Cancel'.tr, style: const TextStyle(color: cs.onSurfaceVariant)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -164,12 +165,12 @@ class KazanEditorPage extends StatelessWidget {
|
|||||||
'speedPrice': {
|
'speedPrice': {
|
||||||
'label': 'Speed ⚡',
|
'label': 'Speed ⚡',
|
||||||
'icon': Icons.flash_on_rounded,
|
'icon': Icons.flash_on_rounded,
|
||||||
'color': Colors.amber.shade700
|
'color': cs.warning.shade700
|
||||||
},
|
},
|
||||||
'comfortPrice': {
|
'comfortPrice': {
|
||||||
'label': 'Comfort ❄️',
|
'label': 'Comfort ❄️',
|
||||||
'icon': Icons.chair_rounded,
|
'icon': Icons.chair_rounded,
|
||||||
'color': Colors.blue.shade700
|
'color': cs.info.shade700
|
||||||
},
|
},
|
||||||
'ladyPrice': {
|
'ladyPrice': {
|
||||||
'label': 'Lady 👩',
|
'label': 'Lady 👩',
|
||||||
@@ -179,7 +180,7 @@ class KazanEditorPage extends StatelessWidget {
|
|||||||
'electricPrice': {
|
'electricPrice': {
|
||||||
'label': 'Electric 🔋',
|
'label': 'Electric 🔋',
|
||||||
'icon': Icons.electric_car_rounded,
|
'icon': Icons.electric_car_rounded,
|
||||||
'color': Colors.green.shade700
|
'color': cs.success.shade700
|
||||||
},
|
},
|
||||||
'vanPrice': {
|
'vanPrice': {
|
||||||
'label': 'Van 🚐',
|
'label': 'Van 🚐',
|
||||||
@@ -189,17 +190,17 @@ class KazanEditorPage extends StatelessWidget {
|
|||||||
'deliveryPrice': {
|
'deliveryPrice': {
|
||||||
'label': 'Delivery 📦',
|
'label': 'Delivery 📦',
|
||||||
'icon': Icons.delivery_dining_rounded,
|
'icon': Icons.delivery_dining_rounded,
|
||||||
'color': Colors.orange.shade700
|
'color': cs.warning.shade700
|
||||||
},
|
},
|
||||||
'mishwarVipPrice': {
|
'mishwarVipPrice': {
|
||||||
'label': 'Mishwar Vip ⭐',
|
'label': 'Mishwar Vip ⭐',
|
||||||
'icon': Icons.star_rounded,
|
'icon': Icons.star_rounded,
|
||||||
'color': Colors.amber.shade900
|
'color': cs.warning.shade900
|
||||||
},
|
},
|
||||||
'fixedPrice': {
|
'fixedPrice': {
|
||||||
'label': 'Fixed Price 💰',
|
'label': 'Fixed Price 💰',
|
||||||
'icon': Icons.money_rounded,
|
'icon': Icons.money_rounded,
|
||||||
'color': Colors.teal.shade700
|
'color': cs.tertiary.shade700
|
||||||
},
|
},
|
||||||
'awfarPrice': {
|
'awfarPrice': {
|
||||||
'label': 'Awfar Car 🚗',
|
'label': 'Awfar Car 🚗',
|
||||||
@@ -294,19 +295,19 @@ class KazanEditorPage extends StatelessWidget {
|
|||||||
'label': 'Normal (سعر الدقيقة العادي)',
|
'label': 'Normal (سعر الدقيقة العادي)',
|
||||||
'desc': '9 ص - 2 م / 6 م - 9 م',
|
'desc': '9 ص - 2 م / 6 م - 9 م',
|
||||||
'icon': Icons.wb_sunny_rounded,
|
'icon': Icons.wb_sunny_rounded,
|
||||||
'color': Colors.orange
|
'color': cs.warning
|
||||||
},
|
},
|
||||||
'peakMinPrice': {
|
'peakMinPrice': {
|
||||||
'label': 'Peak (سعر الدقيقة ذروة)',
|
'label': 'Peak (سعر الدقيقة ذروة)',
|
||||||
'desc': '2 م - 5 م',
|
'desc': '2 م - 5 م',
|
||||||
'icon': Icons.whatshot_rounded,
|
'icon': Icons.whatshot_rounded,
|
||||||
'color': Colors.red
|
'color': cs.danger
|
||||||
},
|
},
|
||||||
'lateMinPrice': {
|
'lateMinPrice': {
|
||||||
'label': 'Late (سعر الدقيقة ليلي)',
|
'label': 'Late (سعر الدقيقة ليلي)',
|
||||||
'desc': '9 م - 1 ص',
|
'desc': '9 م - 1 ص',
|
||||||
'icon': Icons.nightlight_round,
|
'icon': Icons.nightlight_round,
|
||||||
'color': Colors.indigo
|
'color': cs.info
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,32 +9,46 @@ class BlacklistPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cs = Theme.of(context).colorScheme;
|
final cs = Theme.of(context).colorScheme;
|
||||||
Get.put(QualityController()).fetchBlacklist();
|
|
||||||
|
|
||||||
return Scaffold(
|
// كان `Get.put(...).fetchBlacklist()` يُستدعى هنا مباشرة، فيُعاد الجلب مع
|
||||||
backgroundColor: cs.surface,
|
// كل إعادة بناء للشجرة. نسجّل المتحكّم مرة واحدة ونترك الجلب لـ onInit.
|
||||||
body: Column(
|
final controller = Get.isRegistered<QualityController>()
|
||||||
children: [
|
? Get.find<QualityController>()
|
||||||
_buildAppBar(context, cs),
|
: Get.put(QualityController());
|
||||||
_buildTabBar(context, cs),
|
|
||||||
Expanded(
|
|
||||||
child: GetBuilder<QualityController>(
|
|
||||||
builder: (controller) {
|
|
||||||
if (controller.isLoading) {
|
|
||||||
return _buildLoadingState(cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TabBarView(
|
// الشاشة تستدعي DefaultTabController.of(context) في مكانين (TabBar
|
||||||
controller: DefaultTabController.of(context),
|
// و TabBarView) دون أن يوجد DefaultTabController في الشجرة إطلاقاً —
|
||||||
children: [
|
// فكان الاستدعاء يرمي استثناءً وتظهر الصفحة رمادية فارغة.
|
||||||
_buildDriverList(controller, cs),
|
return DefaultTabController(
|
||||||
_buildPassengerList(controller, cs),
|
length: 2,
|
||||||
],
|
child: Builder(
|
||||||
);
|
builder: (context) => Scaffold(
|
||||||
},
|
backgroundColor: cs.surface,
|
||||||
),
|
body: Column(
|
||||||
|
children: [
|
||||||
|
_buildAppBar(context, cs),
|
||||||
|
_buildTabBar(context, cs),
|
||||||
|
Expanded(
|
||||||
|
child: GetBuilder<QualityController>(
|
||||||
|
init: controller,
|
||||||
|
builder: (controller) {
|
||||||
|
if (controller.isLoading) {
|
||||||
|
return _buildLoadingState(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TabBarView(
|
||||||
|
controller: DefaultTabController.of(context),
|
||||||
|
children: [
|
||||||
|
_buildDriverList(controller, cs),
|
||||||
|
_buildPassengerList(controller, cs),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import '../../../controller/admin/quality_controller.dart';
|
import '../../../controller/admin/quality_controller.dart';
|
||||||
@@ -18,7 +19,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('بطاقة أداء السائق',
|
title: const Text('بطاقة أداء السائق',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
backgroundColor: Colors.blueAccent,
|
backgroundColor: cs.info,
|
||||||
),
|
),
|
||||||
body: GetBuilder<QualityController>(
|
body: GetBuilder<QualityController>(
|
||||||
builder: (controller) {
|
builder: (controller) {
|
||||||
@@ -56,7 +57,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 40,
|
radius: 40,
|
||||||
backgroundColor: Colors.grey.shade300,
|
backgroundColor: cs.onSurfaceVariant.shade300,
|
||||||
child: const Icon(Icons.person,
|
child: const Icon(Icons.person,
|
||||||
size: 50, color: Colors.white),
|
size: 50, color: Colors.white),
|
||||||
),
|
),
|
||||||
@@ -66,11 +67,11 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 22, fontWeight: FontWeight.bold)),
|
fontSize: 22, fontWeight: FontWeight.bold)),
|
||||||
Text('هاتف: ${basicInfo['phone']}',
|
Text('هاتف: ${basicInfo['phone']}',
|
||||||
style: const TextStyle(color: Colors.grey)),
|
style: const TextStyle(color: cs.onSurfaceVariant)),
|
||||||
const Divider(height: 30),
|
const Divider(height: 30),
|
||||||
Text('التقييم الشامل (Score)',
|
Text('التقييم الشامل (Score)',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18, color: Colors.grey.shade700)),
|
fontSize: 18, color: cs.onSurfaceVariant.shade700)),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Stack(
|
Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -81,7 +82,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
value: overallScore / 100,
|
value: overallScore / 100,
|
||||||
strokeWidth: 10,
|
strokeWidth: 10,
|
||||||
backgroundColor: Colors.grey.shade200,
|
backgroundColor: cs.onSurfaceVariant.shade200,
|
||||||
color: scoreColor,
|
color: scoreColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -103,7 +104,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
Card(
|
Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: const Icon(Icons.drive_eta, color: Colors.blue),
|
leading: const Icon(Icons.drive_eta, color: cs.info),
|
||||||
title: const Text('نسبة الإنجاز'),
|
title: const Text('نسبة الإنجاز'),
|
||||||
trailing: Text('${ridesStats['completion_rate']}%',
|
trailing: Text('${ridesStats['completion_rate']}%',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -128,7 +129,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.star,
|
const Icon(Icons.star,
|
||||||
color: Colors.orange, size: 30),
|
color: cs.warning, size: 30),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text('${rating.toString()}/5.0',
|
Text('${rating.toString()}/5.0',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -136,7 +137,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
const Text('متوسط التقييم',
|
const Text('متوسط التقييم',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12, color: Colors.grey)),
|
fontSize: 12, color: cs.onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -150,7 +151,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.warning,
|
const Icon(Icons.warning,
|
||||||
color: Colors.redAccent, size: 30),
|
color: cs.danger, size: 30),
|
||||||
const SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text('${complaints['total_complaints']} شكوى',
|
Text('${complaints['total_complaints']} شكوى',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -158,7 +159,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.bold)),
|
fontWeight: FontWeight.bold)),
|
||||||
Text('${complaints['open_complaints']} مفتوحة',
|
Text('${complaints['open_complaints']} مفتوحة',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12, color: Colors.red)),
|
fontSize: 12, color: cs.danger)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -221,7 +222,7 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, size: 20, color: Colors.grey.shade600),
|
Icon(icon, size: 20, color: cs.onSurfaceVariant.shade600),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(title, style: const TextStyle(fontSize: 15)),
|
Text(title, style: const TextStyle(fontSize: 15)),
|
||||||
],
|
],
|
||||||
@@ -233,8 +234,8 @@ class DriverScorecardPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color _getScoreColor(num score) {
|
Color _getScoreColor(num score) {
|
||||||
if (score >= 80) return Colors.green;
|
if (score >= 80) return cs.success;
|
||||||
if (score >= 60) return Colors.orange;
|
if (score >= 60) return cs.warning;
|
||||||
return Colors.red;
|
return cs.danger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/map_tiles.dart';
|
||||||
import 'package:siro_admin/constant/theme.dart';
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -237,67 +238,65 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
// 1. Sliver AppBar (المتحرك الذكي الذي يصغر عند التمرير)
|
// 1. Sliver AppBar (المتحرك الذكي الذي يصغر عند التمرير)
|
||||||
|
// الرأس: سطح هادئ بدل الكتلة الملوّنة المشبعة.
|
||||||
|
// النسخة السابقة كانت شريحة بلون primary بارتفاع 310 مع تدرّج
|
||||||
|
// وظل — تسحب الانتباه إلى الإطار بدل الأرقام، وهي أول ما يجعل
|
||||||
|
// اللوحة تبدو غير احترافية. اللون الآن يميّز الحالة داخل البطاقات
|
||||||
|
// فقط، حيث يحمل معنى.
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: false,
|
floating: false,
|
||||||
expandedHeight: 310.0,
|
expandedHeight: 268.0,
|
||||||
backgroundColor: cs.primary,
|
backgroundColor: cs.surface,
|
||||||
elevation: 4,
|
surfaceTintColor: Colors.transparent,
|
||||||
shadowColor: cs.primary.withValues(alpha: 0.4),
|
elevation: 0,
|
||||||
iconTheme: IconThemeData(color: cs.onPrimary),
|
scrolledUnderElevation: 0,
|
||||||
centerTitle: true,
|
iconTheme: IconThemeData(color: cs.onSurface),
|
||||||
|
centerTitle: false,
|
||||||
|
titleSpacing: 20,
|
||||||
title: Text(
|
title: Text(
|
||||||
'إدارة الرحلات',
|
'إدارة الرحلات',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: cs.onPrimary,
|
color: cs.onSurface,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 20,
|
fontSize: 19,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Container(
|
Container(
|
||||||
margin:
|
margin:
|
||||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.onPrimary.withValues(alpha: 0.2),
|
color: cs.primary.withValues(alpha: 0.10),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(
|
||||||
|
color: cs.primary.withValues(alpha: 0.22)),
|
||||||
),
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Text(
|
child: Text(
|
||||||
'اليوم',
|
'اليوم',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w700,
|
||||||
color: cs.onPrimary,
|
color: cs.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Container(
|
background: Container(
|
||||||
decoration: BoxDecoration(
|
color: cs.surface,
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
cs.primary,
|
|
||||||
cs.primary.withValues(alpha: 0.8),
|
|
||||||
],
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
// الإحصائيات تختفي عند التمرير لأعلى
|
|
||||||
_buildStatisticsSection(cs),
|
_buildStatisticsSection(cs),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 14),
|
||||||
// شريط البحث يختفي عند التمرير لأعلى
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: _buildSearchBar(cs),
|
child: _buildSearchBar(cs),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 60), // مساحة للـ TabBar بالأسفل
|
const SizedBox(height: 62), // مساحة للـ TabBar بالأسفل
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -308,8 +307,8 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.surface,
|
color: cs.surface,
|
||||||
borderRadius:
|
border: Border(
|
||||||
const BorderRadius.vertical(top: Radius.circular(24)),
|
bottom: BorderSide(color: cs.outline, width: 1)),
|
||||||
),
|
),
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
@@ -318,8 +317,9 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
unselectedLabelColor: cs.onSurfaceVariant,
|
unselectedLabelColor: cs.onSurfaceVariant,
|
||||||
indicatorColor: cs.primary,
|
indicatorColor: cs.primary,
|
||||||
indicatorWeight: 3,
|
indicatorWeight: 3,
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
labelStyle: const TextStyle(
|
labelStyle: const TextStyle(
|
||||||
fontWeight: FontWeight.bold, fontSize: 14),
|
fontWeight: FontWeight.w700, fontSize: 14),
|
||||||
tabAlignment: TabAlignment.center,
|
tabAlignment: TabAlignment.center,
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
tabs: const [
|
tabs: const [
|
||||||
@@ -403,9 +403,11 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
child: Text(
|
child: Text(
|
||||||
'نظرة عامة',
|
'نظرة عامة',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w700,
|
||||||
color: Colors.white.withOpacity(0.8),
|
// كان Colors.white ثابتاً — يصلح فوق الرأس الملوّن السابق فقط،
|
||||||
|
// ويصبح غير مقروء إطلاقاً على سطح الوضع الفاتح.
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -426,7 +428,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
'مكتملة',
|
'مكتملة',
|
||||||
controller.completedCount.toString(),
|
controller.completedCount.toString(),
|
||||||
Icons.check_circle_rounded,
|
Icons.check_circle_rounded,
|
||||||
const Color(0xFF14B8A6), cs)),
|
cs.tertiary, cs)),
|
||||||
Obx(() => _buildStatCard(
|
Obx(() => _buildStatCard(
|
||||||
'ملغاة',
|
'ملغاة',
|
||||||
controller.canceledCount.toString(),
|
controller.canceledCount.toString(),
|
||||||
@@ -446,25 +448,28 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
|
|
||||||
Widget _buildStatCard(
|
Widget _buildStatCard(
|
||||||
String label, String value, IconData icon, Color iconColor, ColorScheme cs) {
|
String label, String value, IconData icon, Color iconColor, ColorScheme cs) {
|
||||||
|
// البطاقة تُبنى على سطح مرتفع بحدّ رفيع وشارة لونية للأيقونة، بدل
|
||||||
|
// بطاقة بيضاء عائمة بظل ثقيل. اللون يعرّف الحالة ولا يملأ المساحة.
|
||||||
return Container(
|
return Container(
|
||||||
width: 105,
|
width: 108,
|
||||||
margin: const EdgeInsets.only(left: 10),
|
margin: const EdgeInsets.only(left: 10),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.surface,
|
color: cs.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
border: Border.all(color: iconColor.withValues(alpha: 0.22)),
|
||||||
BoxShadow(
|
|
||||||
color: cs.shadow.withValues(alpha: 0.1),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: iconColor, size: 24),
|
Container(
|
||||||
|
padding: const EdgeInsets.all(7),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: iconColor.withValues(alpha: 0.12),
|
||||||
|
borderRadius: BorderRadius.circular(9),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: iconColor, size: 18),
|
||||||
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
@@ -489,15 +494,9 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
Widget _buildSearchBar(ColorScheme cs) {
|
Widget _buildSearchBar(ColorScheme cs) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.surface,
|
color: cs.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(14),
|
||||||
boxShadow: [
|
border: Border.all(color: cs.outline),
|
||||||
BoxShadow(
|
|
||||||
color: cs.shadow.withValues(alpha: 0.05),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controller.searchController,
|
controller: controller.searchController,
|
||||||
@@ -600,7 +599,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 10, vertical: 4),
|
horizontal: 10, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: statusColor.withOpacity(0.1),
|
color: statusColor.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -630,7 +629,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
Container(
|
Container(
|
||||||
width: 2,
|
width: 2,
|
||||||
height: 16,
|
height: 16,
|
||||||
color: Colors.grey.withOpacity(0.3),
|
color: cs.outline,
|
||||||
margin: const EdgeInsets.symmetric(vertical: 2)),
|
margin: const EdgeInsets.symmetric(vertical: 2)),
|
||||||
Icon(Icons.location_on_rounded,
|
Icon(Icons.location_on_rounded,
|
||||||
size: 14, color: cs.danger),
|
size: 14, color: cs.danger),
|
||||||
@@ -645,20 +644,20 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
ride.startLocation,
|
ride.startLocation,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.black87),
|
color: cs.onSurface),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
ride.endLocation,
|
ride.endLocation,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.black87),
|
color: cs.onSurface),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -736,10 +735,10 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.danger.withOpacity(0.08),
|
color: cs.danger.withValues(alpha: 0.08),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: cs.danger.withOpacity(0.2)),
|
color: cs.danger.withValues(alpha: 0.2)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -777,6 +776,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
required Color color,
|
required Color color,
|
||||||
required bool isAdmin,
|
required bool isAdmin,
|
||||||
}) {
|
}) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
String displayPhone = phone;
|
String displayPhone = phone;
|
||||||
if (!isAdmin && phone.length > 4) {
|
if (!isAdmin && phone.length > 4) {
|
||||||
displayPhone =
|
displayPhone =
|
||||||
@@ -791,7 +791,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
'$title:',
|
'$title:',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.grey[600],
|
color: cs.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
@@ -800,16 +800,16 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
name,
|
name,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.black87),
|
color: cs.onSurface),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
displayPhone,
|
displayPhone,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11, color: Colors.grey[500], letterSpacing: 0.5),
|
fontSize: 11, color: cs.onSurfaceVariant, letterSpacing: 0.5),
|
||||||
),
|
),
|
||||||
if (isAdmin && phone.isNotEmpty) ...[
|
if (isAdmin && phone.isNotEmpty) ...[
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -835,7 +835,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4),
|
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withOpacity(0.08),
|
color: color.withValues(alpha: 0.08),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -869,7 +869,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
return cs.danger;
|
return cs.danger;
|
||||||
}
|
}
|
||||||
if (status == 'New') return cs.info;
|
if (status == 'New') return cs.info;
|
||||||
return Colors.grey;
|
return cs.onSurfaceVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getStatusText(String status) {
|
String _getStatusText(String status) {
|
||||||
@@ -1000,10 +1000,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
initialZoom: 13,
|
initialZoom: 13,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
MapTiles.layer(context),
|
||||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
||||||
userAgentPackageName: 'com.siromove.admin',
|
|
||||||
),
|
|
||||||
// Route Polyline
|
// Route Polyline
|
||||||
if (startPos != null && endPos != null)
|
if (startPos != null && endPos != null)
|
||||||
PolylineLayer(
|
PolylineLayer(
|
||||||
@@ -1011,7 +1008,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
Polyline(
|
Polyline(
|
||||||
points: [startPos!, endPos!],
|
points: [startPos!, endPos!],
|
||||||
strokeWidth: 5,
|
strokeWidth: 5,
|
||||||
color: const Color(0xFF4318FF).withOpacity(0.8),
|
color: cs.primary.withValues(alpha: 0.8),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1030,7 +1027,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.2),
|
color: Colors.black.withValues(alpha: 0.2),
|
||||||
blurRadius: 5),
|
blurRadius: 5),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1051,7 +1048,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.2),
|
color: Colors.black.withValues(alpha: 0.2),
|
||||||
blurRadius: 5),
|
blurRadius: 5),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1072,7 +1069,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: cs.info.withOpacity(0.3),
|
color: cs.info.withValues(alpha: 0.3),
|
||||||
blurRadius: 8),
|
blurRadius: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1096,7 +1093,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: Colors.black.withValues(alpha: 0.1),
|
||||||
blurRadius: 20,
|
blurRadius: 20,
|
||||||
offset: const Offset(0, 10),
|
offset: const Offset(0, 10),
|
||||||
),
|
),
|
||||||
@@ -1152,12 +1149,13 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
required String phone,
|
required String phone,
|
||||||
required Color color,
|
required Color color,
|
||||||
}) {
|
}) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withOpacity(0.1),
|
color: color.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Icon(icon, size: 20, color: color),
|
child: Icon(icon, size: 20, color: color),
|
||||||
@@ -1171,7 +1169,7 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
title,
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.grey[600],
|
color: cs.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
@@ -1200,11 +1198,11 @@ class _RideMapMonitorScreenState extends State<RideMapMonitorScreen> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.green.withOpacity(0.1),
|
color: cs.success.withValues(alpha: 0.1),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child:
|
child:
|
||||||
const Icon(Icons.call_rounded, size: 20, color: Colors.green),
|
Icon(Icons.call_rounded, size: 20, color: cs.success),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ class _HeaderInfo extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.timer_outlined,
|
Icon(Icons.timer_outlined,
|
||||||
size: 16, color: Colors.greenAccent.withValues(alpha: 0.8)),
|
size: 16, color: cs.success.withValues(alpha: 0.8)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
"Uptime: ${data.uptime.formatted}",
|
"Uptime: ${data.uptime.formatted}",
|
||||||
@@ -247,7 +247,7 @@ class _HeaderInfo extends StatelessWidget {
|
|||||||
color: Colors.white24,
|
color: Colors.white24,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12)),
|
margin: const EdgeInsets.symmetric(horizontal: 12)),
|
||||||
Icon(Icons.update,
|
Icon(Icons.update,
|
||||||
size: 16, color: Colors.blueAccent.withValues(alpha: 0.8)),
|
size: 16, color: cs.info.withValues(alpha: 0.8)),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
"Last Update: ${data.timestamp.split(' ')[1]}",
|
"Last Update: ${data.timestamp.split(' ')[1]}",
|
||||||
@@ -375,7 +375,7 @@ class _ServicesCard extends StatelessWidget {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 4,
|
radius: 4,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
isActive ? Colors.greenAccent : Colors.redAccent,
|
isActive ? cs.success : cs.danger,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -397,7 +397,7 @@ class _ServicesCard extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
isActive ? "Running" : "Stopped",
|
isActive ? "Running" : "Stopped",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isActive ? Colors.greenAccent : Colors.redAccent,
|
color: isActive ? cs.success : cs.danger,
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
@@ -489,10 +489,10 @@ class _TopProcessesCard extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withValues(alpha: 0.1),
|
color: cs.warning.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(8)),
|
borderRadius: BorderRadius.circular(8)),
|
||||||
child: const Icon(Icons.analytics_rounded,
|
child: const Icon(Icons.analytics_rounded,
|
||||||
color: Colors.orange, size: 18),
|
color: cs.warning, size: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text("Top Processes",
|
Text("Top Processes",
|
||||||
@@ -550,13 +550,13 @@ class _TopProcessesCard extends StatelessWidget {
|
|||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withValues(alpha: 0.1),
|
color: cs.warning.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
process.usage,
|
process.usage,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.orangeAccent,
|
color: cs.warning,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
@@ -655,7 +655,7 @@ class _ErrorState extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.cloud_off_rounded,
|
const Icon(Icons.cloud_off_rounded,
|
||||||
size: 60, color: Colors.redAccent),
|
size: 60, color: cs.danger),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(controller.errorMessage.value,
|
Text(controller.errorMessage.value,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:siro_admin/controller/functions/launch.dart';
|
import 'package:siro_admin/controller/functions/launch.dart';
|
||||||
@@ -46,10 +47,10 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.note_alt_outlined,
|
Icon(Icons.note_alt_outlined,
|
||||||
size: 80, color: Colors.grey.shade300),
|
size: 80, color: cs.onSurfaceVariant.shade300),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text("لا توجد سجلات لهذا اليوم",
|
Text("لا توجد سجلات لهذا اليوم",
|
||||||
style: TextStyle(color: Colors.grey.shade600)),
|
style: TextStyle(color: cs.onSurfaceVariant.shade600)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -73,7 +74,7 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.03),
|
color: Colors.black.withValues(alpha: 0.03),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 4),
|
||||||
)
|
)
|
||||||
@@ -92,7 +93,7 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 14,
|
radius: 14,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
_getEmployeeColor(name).withOpacity(0.1),
|
_getEmployeeColor(name).withValues(alpha: 0.1),
|
||||||
child: Icon(Icons.person,
|
child: Icon(Icons.person,
|
||||||
size: 16, color: _getEmployeeColor(name)),
|
size: 16, color: _getEmployeeColor(name)),
|
||||||
),
|
),
|
||||||
@@ -101,7 +102,7 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
name.toUpperCase(),
|
name.toUpperCase(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.grey.shade800,
|
color: cs.onSurfaceVariant.shade800,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 100),
|
const SizedBox(width: 100),
|
||||||
@@ -115,7 +116,7 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
phone,
|
phone,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.grey.shade800,
|
color: cs.onSurfaceVariant.shade800,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
),
|
),
|
||||||
Icon(Icons.phone)
|
Icon(Icons.phone)
|
||||||
@@ -126,7 +127,7 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
time.split(' ').last, // عرض الوقت فقط
|
time.split(' ').last, // عرض الوقت فقط
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey.shade400, fontSize: 12),
|
color: cs.onSurfaceVariant.shade400, fontSize: 12),
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -138,7 +139,7 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
content,
|
content,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.grey.shade700,
|
color: cs.onSurfaceVariant.shade700,
|
||||||
height: 1.5),
|
height: 1.5),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -153,10 +154,10 @@ class DailyNotesView extends StatelessWidget {
|
|||||||
|
|
||||||
Color _getEmployeeColor(String name) {
|
Color _getEmployeeColor(String name) {
|
||||||
String n = name.toLowerCase().trim();
|
String n = name.toLowerCase().trim();
|
||||||
if (n.contains('shahd')) return Colors.redAccent;
|
if (n.contains('shahd')) return cs.danger;
|
||||||
if (n.contains('mayar')) return Colors.amber.shade700;
|
if (n.contains('mayar')) return cs.warning.shade700;
|
||||||
if (n.contains('rama2')) return Colors.green;
|
if (n.contains('rama2')) return cs.success;
|
||||||
if (n.contains('rama1')) return Colors.blue;
|
if (n.contains('rama1')) return cs.info;
|
||||||
return Colors.blueGrey;
|
return Colors.blueGrey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,10 +180,10 @@ class _AdminLoginPageState extends State<AdminLoginPage>
|
|||||||
color: _C.card,
|
color: _C.card,
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _C.accent.withOpacity(0.18), width: 1.2),
|
color: _C.accent.withValues(alpha: 0.18), width: 1.2),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.4),
|
color: Colors.black.withValues(alpha: 0.4),
|
||||||
blurRadius: 32,
|
blurRadius: 32,
|
||||||
offset: const Offset(0, 12),
|
offset: const Offset(0, 12),
|
||||||
),
|
),
|
||||||
@@ -468,12 +468,12 @@ class _AdminLoginPageState extends State<AdminLoginPage>
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: _C.card,
|
color: _C.card,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _C.accent.withOpacity(0.3 + _glowAnim.value * 0.3),
|
color: _C.accent.withValues(alpha: 0.3 + _glowAnim.value * 0.3),
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
),
|
),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: _C.accentGlow.withOpacity(_glowAnim.value * 0.6),
|
color: _C.accentGlow.withValues(alpha: _glowAnim.value * 0.6),
|
||||||
blurRadius: 30,
|
blurRadius: 30,
|
||||||
spreadRadius: 4,
|
spreadRadius: 4,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class AdminRegisterPage extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _C.card,
|
color: _C.card,
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
border: Border.all(color: _C.accent.withOpacity(0.18), width: 1.2),
|
border: Border.all(color: _C.accent.withValues(alpha: 0.18), width: 1.2),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -170,7 +171,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.05),
|
color: Colors.black.withValues(alpha: 0.05),
|
||||||
blurRadius: 15,
|
blurRadius: 15,
|
||||||
offset: const Offset(0, 5),
|
offset: const Offset(0, 5),
|
||||||
),
|
),
|
||||||
@@ -202,7 +203,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
Text(
|
Text(
|
||||||
'صورة الفاتورة',
|
'صورة الفاتورة',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[700],
|
color: cs.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
@@ -221,7 +222,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _imageFile != null
|
color: _imageFile != null
|
||||||
? primaryColor
|
? primaryColor
|
||||||
: Colors.grey.shade300,
|
: cs.onSurfaceVariant.shade300,
|
||||||
width: 2,
|
width: 2,
|
||||||
style: _imageFile != null
|
style: _imageFile != null
|
||||||
? BorderStyle.solid
|
? BorderStyle.solid
|
||||||
@@ -241,7 +242,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(15),
|
padding: const EdgeInsets.all(15),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: primaryColor.withOpacity(0.05),
|
color: primaryColor.withValues(alpha: 0.05),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Icon(Icons.add_a_photo_rounded,
|
child: Icon(Icons.add_a_photo_rounded,
|
||||||
@@ -251,7 +252,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
Text(
|
Text(
|
||||||
'اضغط لرفع صورة الفاتورة',
|
'اضغط لرفع صورة الفاتورة',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[500],
|
color: cs.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -265,7 +266,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.9),
|
color: Colors.white.withValues(alpha: 0.9),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Icon(Icons.edit,
|
child: Icon(Icons.edit,
|
||||||
@@ -296,7 +297,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: _isLoading
|
colors: _isLoading
|
||||||
? [Colors.grey, Colors.grey]
|
? [cs.onSurfaceVariant, cs.onSurfaceVariant]
|
||||||
: [primaryColor, secondaryColor],
|
: [primaryColor, secondaryColor],
|
||||||
begin: Alignment.centerLeft,
|
begin: Alignment.centerLeft,
|
||||||
end: Alignment.centerRight,
|
end: Alignment.centerRight,
|
||||||
@@ -305,7 +306,7 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
boxShadow: [
|
boxShadow: [
|
||||||
if (!_isLoading)
|
if (!_isLoading)
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: primaryColor.withOpacity(0.4),
|
color: primaryColor.withValues(alpha: 0.4),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 4),
|
||||||
),
|
),
|
||||||
@@ -369,16 +370,16 @@ class _AddInvoicePageState extends State<AddInvoicePage> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: label,
|
labelText: label,
|
||||||
hintText: hint,
|
hintText: hint,
|
||||||
prefixIcon: Icon(icon, color: primaryColor.withOpacity(0.7)),
|
prefixIcon: Icon(icon, color: primaryColor.withValues(alpha: 0.7)),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey[50],
|
fillColor: cs.onSurfaceVariant,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide(color: Colors.grey.shade200),
|
borderSide: BorderSide(color: cs.onSurfaceVariant.shade200),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class _RouteApprovalPageState extends State<RouteApprovalPage> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.check_circle_outline_rounded,
|
Icon(Icons.check_circle_outline_rounded,
|
||||||
size: 64, color: AppColor.success.withOpacity(0.6)),
|
size: 64, color: AppColor.success.withValues(alpha: 0.6)),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text(
|
const Text(
|
||||||
'لا توجد خطوط تنتظر الاعتماد',
|
'لا توجد خطوط تنتظر الاعتماد',
|
||||||
@@ -197,7 +197,7 @@ class _RouteCard extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Text('المحطات:',
|
child: Text('المحطات:',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: AppColor.textSecondary.withOpacity(0.8),
|
color: AppColor.textSecondary.withValues(alpha: 0.8),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600)),
|
fontWeight: FontWeight.w600)),
|
||||||
),
|
),
|
||||||
@@ -273,9 +273,9 @@ class _TagChip extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withOpacity(0.12),
|
color: color.withValues(alpha: 0.12),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(color: color.withOpacity(0.3)),
|
border: Border.all(color: color.withValues(alpha: 0.3)),
|
||||||
),
|
),
|
||||||
child: Text(label,
|
child: Text(label,
|
||||||
style: TextStyle(color: color, fontSize: 11, fontWeight: FontWeight.bold)),
|
style: TextStyle(color: color, fontSize: 11, fontWeight: FontWeight.bold)),
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class MyElevatedButton extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: (kolor ?? AppColor.accent).withOpacity(0.3),
|
color: (kolor ?? AppColor.accent).withValues(alpha: 0.3),
|
||||||
blurRadius: 12,
|
blurRadius: 12,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 4),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:siro_admin/constant/theme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -167,13 +168,13 @@ class _SnackContentState extends State<_SnackContent>
|
|||||||
height: 30,
|
height: 30,
|
||||||
margin: const EdgeInsets.only(left: 6),
|
margin: const EdgeInsets.only(left: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.grey.withAlpha(25),
|
color: cs.onSurfaceVariant.withAlpha(25),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.close_rounded,
|
Icons.close_rounded,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Colors.grey[500],
|
color: cs.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user