Update: 2026-08-03 00:20:40

This commit is contained in:
Hamza-Ayed
2026-08-03 00:20:40 +03:00
parent c23c3c0721
commit 2b9f696372
39 changed files with 38513 additions and 38217 deletions
+28
View File
@@ -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
+12
View File
@@ -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"));
+5 -11
View File
@@ -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);
} }
?> ?>
+46
View File
@@ -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 {
+22
View File
@@ -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;
}
+10 -5
View File
@@ -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';
+9 -6
View File
@@ -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;
+18 -1
View File
@@ -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");
+1 -1
View File
@@ -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. */
} }
}); });
File diff suppressed because one or more lines are too long
+60
View File
@@ -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;
} }
} }
+4 -4
View File
@@ -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,
), ),
+1 -1
View File
@@ -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),
), ),
+3 -2
View File
@@ -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,
), ),
), ),
), ),