From 3af99cc18afd3733c29dec41aae750d4efa00b4a Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sun, 26 Jul 2026 11:33:20 +0300 Subject: [PATCH] =?UTF-8?q?=D8=A5=D8=B5=D9=84=D8=A7=D8=AD=20=D8=AA=D9=88?= =?UTF-8?q?=D8=A7=D9=81=D9=82=20=D9=81=D9=84=D8=A7=D8=AA=D8=B1=20=D9=85?= =?UTF-8?q?=D8=B9=20Theme=20=D8=A7=D9=84=D8=AC=D8=AF=D9=8A=D8=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/siro-admin/flutter_bootstrap.js | 2 +- .../lib/views/admin/admin_home_page.dart | 1 - .../lib/views/admin/captain/captain.dart | 454 +++++++----- .../captain/syrian_driver_not_active.dart | 283 ++++++- .../admin/complaints/complaint_list_page.dart | 229 +++--- .../lib/views/admin/dashboard_v2_widget.dart | 1 - .../drivers/driver_documents_review_page.dart | 242 +++--- .../admin/drivers/driver_gift_check_page.dart | 350 ++++++--- .../admin/drivers/driver_tracker_screen.dart | 196 +++-- .../lib/views/admin/drivers/monitor_ride.dart | 217 ++---- .../views/admin/employee/employee_page.dart | 207 +++--- .../views/admin/error/error/error_page.dart | 144 ++-- .../admin/financial/financial_v2_page.dart | 109 +-- .../views/admin/marketing/heatmap_page.dart | 93 ++- .../views/admin/marketing/marketing_page.dart | 700 +++++++++-------- .../marketing/social_intelligence_screen.dart | 45 +- siro_admin/lib/views/admin/packages.dart | 215 +++--- .../lib/views/admin/passenger/passenger.dart | 676 +++++++++++------ .../admin/pricing/kazan_editor_page.dart | 186 ++--- .../admin/promo/promo_management_page.dart | 128 ++-- .../views/admin/quality/blacklist_page.dart | 425 ++++++++--- siro_admin/lib/views/admin/rides/rides.dart | 637 ++++++++++------ .../views/admin/security/audit_logs_page.dart | 60 +- .../lib/views/admin/staff/add_staff_page.dart | 67 +- .../admin/staff/pending_admins_page.dart | 46 +- .../admin/static/advanced_analytics_page.dart | 701 +++++++++++++----- siro_admin/lib/views/admin/static/static.dart | 1 - siro_admin/lib/views/admin/wallet/wallet.dart | 306 ++++++-- .../lib/views/invoice/invoice_list_page.dart | 304 ++++---- .../lib/views/transit/org_list_page.dart | 62 +- siro_admin/lib/views/widgets/web_sidebar.dart | 2 - 31 files changed, 4256 insertions(+), 2833 deletions(-) diff --git a/dashboard/siro-admin/flutter_bootstrap.js b/dashboard/siro-admin/flutter_bootstrap.js index 511814fc..630f561c 100644 --- a/dashboard/siro-admin/flutter_bootstrap.js +++ b/dashboard/siro-admin/flutter_bootstrap.js @@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"6c0baaebf70e0148f485f27d5616b3d3382da7 _flutter.loader.load({ serviceWorkerSettings: { - serviceWorkerVersion: "658515598" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */ + serviceWorkerVersion: "1676594277" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */ } }); diff --git a/siro_admin/lib/views/admin/admin_home_page.dart b/siro_admin/lib/views/admin/admin_home_page.dart index 617bc5a0..c42b1c57 100644 --- a/siro_admin/lib/views/admin/admin_home_page.dart +++ b/siro_admin/lib/views/admin/admin_home_page.dart @@ -8,7 +8,6 @@ import 'package:siro_admin/views/admin/drivers/driver_tracker_screen.dart'; import 'package:siro_admin/views/admin/quality/blacklist_page.dart'; import '../../constant/box_name.dart'; -import '../../constant/theme.dart'; import '../../controller/admin/dashboard_controller.dart'; import '../../controller/admin/static_controller.dart'; import '../../controller/functions/crud.dart'; diff --git a/siro_admin/lib/views/admin/captain/captain.dart b/siro_admin/lib/views/admin/captain/captain.dart index 6728c427..78bd27c0 100644 --- a/siro_admin/lib/views/admin/captain/captain.dart +++ b/siro_admin/lib/views/admin/captain/captain.dart @@ -2,10 +2,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../constant/box_name.dart'; -import '../../../main.dart'; -import '../../widgets/my_scafold.dart'; -import '../../widgets/mycircular.dart'; import '../../../controller/admin/captain_admin_controller.dart'; +import '../../../main.dart'; import 'captain_details.dart'; class CaptainsPage extends StatelessWidget { @@ -21,159 +19,84 @@ class CaptainsPage extends StatelessWidget { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; - return MyScafolld( - title: 'Search for Captain'.tr, - isleading: true, - body: [ - Container( - height: MediaQuery.of(context).size.height, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Theme.of(context).primaryColor.withOpacity(0.03), - Colors.white, - ], + return Scaffold( + backgroundColor: cs.surface, + body: Column( + children: [ + _buildAppBar(context, cs), + _buildSearchSection(context, cs), + Expanded( + child: GetBuilder( + builder: (controller) { + if (controller.isLoading) { + return _buildLoadingState(cs); + } + + final message = controller.captainData['message']; + + if (message == null) { + return _buildEmptyState(cs); + } + + final List captains = + message is List ? message : [message]; + + if (captains.isEmpty) { + return _buildEmptyState(cs); + } + + return _buildResultsList(context, captains, cs); + }, ), ), - child: Column( - children: [ - _buildHeaderSection(context), - Expanded( - child: GetBuilder( - builder: (controller) { - if (controller.isLoading) { - return _buildLoadingState(); - } - - final message = controller.captainData['message']; - - if (message == null) { - return _buildEmptyState(); - } - - // 🔥 الحل هنا: توحيد الشكل إلى List - final List captains = - message is List ? message : [message]; - - if (captains.isEmpty) { - return _buildEmptyState(); - } - - return _buildResultsList(context, captains); - }, - ), - ), - ], - ), - ), - ], - ); - } - - // ================= HEADER ================= - - Widget _buildHeaderSection(BuildContext context) { - return Container( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 16), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.04), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - Icons.manage_search_rounded, - color: Theme.of(context).primaryColor, - size: 24, - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Find Captain'.tr, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - Text( - 'Search by phone number'.tr, - style: TextStyle( - fontSize: 13, - color: Colors.grey[600], - ), - ), - ], - ), - ], - ), - const SizedBox(height: 20), - _buildModernSearchBar(context), ], ), ); } - Widget _buildModernSearchBar(BuildContext context) { + Widget _buildAppBar(BuildContext context, ColorScheme cs) { return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey[200]!), + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), ), child: Row( children: [ - Expanded( - child: TextField( - controller: searchController, - keyboardType: TextInputType.phone, - style: const TextStyle(fontSize: 15), - decoration: InputDecoration( - hintText: '0990000000'.tr, - hintStyle: TextStyle(color: Colors.grey[400], fontSize: 14), - prefixIcon: Icon(Icons.phone_android_rounded, - color: Colors.grey[400], size: 22), - border: InputBorder.none, - contentPadding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), ), - onSubmitted: (_) => _performSearch(), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), ), ), - Padding( - padding: const EdgeInsets.all(6.0), - child: Material( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(12), - child: InkWell( - onTap: _performSearch, - borderRadius: BorderRadius.circular(12), - child: const Padding( - padding: EdgeInsets.all(12), - child: Icon(Icons.search, color: Colors.white, size: 24), - ), - ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.drive_eta_rounded, + color: cs.primary, size: 18), + ), + const SizedBox(width: 10), + Text( + 'قائمة الكباتن', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, ), ), ], @@ -181,6 +104,58 @@ class CaptainsPage extends StatelessWidget { ); } + Widget _buildSearchSection(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), + child: Container( + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: cs.outline), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: searchController, + keyboardType: TextInputType.phone, + style: TextStyle(color: cs.onSurface, fontSize: 14), + cursorColor: cs.primary, + decoration: InputDecoration( + hintText: '0990000000', + hintStyle: TextStyle( + color: cs.onSurfaceVariant, fontSize: 13), + prefixIcon: Icon(Icons.phone_android_rounded, + color: cs.onSurfaceVariant, size: 20), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 14), + ), + onSubmitted: (_) => _performSearch(), + ), + ), + Padding( + padding: const EdgeInsets.all(6.0), + child: Material( + color: cs.primary, + borderRadius: BorderRadius.circular(10), + child: InkWell( + onTap: _performSearch, + borderRadius: BorderRadius.circular(10), + child: const Padding( + padding: EdgeInsets.all(10), + child: Icon(Icons.search_rounded, + color: Colors.white, size: 20), + ), + ), + ), + ), + ], + ), + ), + ); + } + void _performSearch() { final phone = searchController.text.trim(); if (phone.isNotEmpty) { @@ -188,37 +163,98 @@ class CaptainsPage extends StatelessWidget { } } - // ================= RESULTS ================= + Widget _buildLoadingState(ColorScheme cs) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: cs.primary, + strokeWidth: 2, + backgroundColor: cs.primary.withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text('جاري التحميل...', + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 13)), + ], + ), + ); + } - Widget _buildResultsList(BuildContext context, List captains) { + Widget _buildEmptyState(ColorScheme cs) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon(Icons.person_off_outlined, + size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text('لا يوجد كباتن', + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 14)), + ], + ), + ); + } + + Widget _buildResultsList( + BuildContext context, List captains, ColorScheme cs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.fromLTRB(20, 20, 20, 12), + padding: const EdgeInsets.fromLTRB(20, 8, 20, 8), child: Row( children: [ - Text( - 'Search Results'.tr, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.black87, + Container( + width: 3, + height: 14, + decoration: BoxDecoration( + color: cs.primary, + borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), + Text( + 'نتائج البحث', + style: TextStyle( + color: cs.onSurface, + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Container(height: 1, color: cs.outline), + ), + const SizedBox(width: 8), Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Theme.of(context).primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), + color: cs.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: cs.primary.withValues(alpha: 0.2)), ), child: Text( '${captains.length}', style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + color: cs.primary, + fontSize: 12, + fontWeight: FontWeight.w700, ), ), ), @@ -228,12 +264,12 @@ class CaptainsPage extends StatelessWidget { Expanded( child: ListView.separated( physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.fromLTRB(20, 0, 20, 80), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 80), itemCount: captains.length, - separatorBuilder: (_, __) => const SizedBox(height: 12), + separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, index) { final captain = captains[index] as Map; - return _buildModernCaptainCard(context, captain); + return _buildCaptainCard(context, captain, cs); }, ), ), @@ -241,10 +277,10 @@ class CaptainsPage extends StatelessWidget { ); } - // ================= CARD ================= - - Widget _buildModernCaptainCard( - BuildContext context, Map captain) { + Widget _buildCaptainCard( + BuildContext context, + Map captain, + ColorScheme cs) { final String fullName = '${captain['first_name'] ?? ''} ${captain['last_name'] ?? ''}'; final String phone = captain['phone']?.toString() ?? ''; @@ -252,67 +288,103 @@ class CaptainsPage extends StatelessWidget { return Container( decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.04), - blurRadius: 12, - offset: const Offset(0, 4), - ), - ], + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), ), child: Material( color: Colors.transparent, child: InkWell( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(16), onTap: () { Get.to(() => const CaptainDetailsPage(), arguments: {'data': captain}); }, child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(14), child: Row( children: [ Container( - width: 56, - height: 56, + width: 52, + height: 52, decoration: BoxDecoration( gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, colors: [ - Theme.of(context).primaryColor.withOpacity(0.8), - Theme.of(context).primaryColor, + cs.primary.withValues(alpha: 0.8), + cs.primary, ], ), - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(14), + boxShadow: [ + BoxShadow( + color: cs.primary.withValues(alpha: 0.2), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], ), child: const Icon( Icons.person_rounded, color: Colors.white, - size: 28, + size: 26, ), ), - const SizedBox(width: 16), + const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( fullName, - style: const TextStyle( - fontSize: 16, + style: TextStyle( + color: cs.onSurface, + fontSize: 15, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 6), - Text(phone), + Row( + children: [ + Icon(Icons.phone_rounded, + size: 13, color: cs.onSurfaceVariant), + const SizedBox(width: 4), + Text( + phone, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 12, + fontFamily: 'monospace', + ), + ), + ], + ), if (isSuperAdmin && email != null) ...[ - const SizedBox(height: 4), - Text(email), + const SizedBox(height: 3), + Row( + children: [ + Icon(Icons.email_outlined, + size: 12, + color: cs.onSurfaceVariant), + const SizedBox(width: 4), + Text( + email, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), ], ], ), ), + Icon(Icons.arrow_forward_ios_rounded, + size: 14, color: cs.onSurfaceVariant), ], ), ), @@ -320,16 +392,4 @@ class CaptainsPage extends StatelessWidget { ), ); } - - // ================= STATES ================= - - Widget _buildLoadingState() { - return const Center(child: MyCircularProgressIndicator()); - } - - Widget _buildEmptyState() { - return const Center( - child: Text("No captains found"), - ); - } } diff --git a/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart b/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart index 67e655c0..5436915d 100644 --- a/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart +++ b/siro_admin/lib/views/admin/captain/syrian_driver_not_active.dart @@ -1,14 +1,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:secure_string_operations/secure_string_operations.dart'; import 'package:siro_admin/constant/box_name.dart'; - -import '../../../constant/info.dart'; -import '../../../constant/char_map.dart'; - -import '../../../controller/drivers/driver_not_active_controller.dart'; -import '../../../main.dart'; -import '../../../print.dart'; +import 'package:siro_admin/constant/info.dart'; +import 'package:siro_admin/constant/char_map.dart'; +import 'package:siro_admin/controller/drivers/driver_not_active_controller.dart'; +import 'package:siro_admin/main.dart'; +import 'package:siro_admin/print.dart'; +import 'package:siro_admin/constant/theme.dart'; import 'driver_details_not_active_page.dart'; class DriversPendingPage extends StatelessWidget { @@ -18,35 +16,256 @@ class DriversPendingPage extends StatelessWidget { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; controller.getDriversPending(); Log.print( ': ${X.r(X.r(X.r(box.read(BoxName.jwt), cn), cC), cs).toString().split(AppInformation.addd)[0]}'); return Scaffold( - appBar: AppBar(title: const Text("Drivers Pending")), - body: GetBuilder( - id: 'drivers', - builder: (c) { - if (c.drivers.isEmpty) { - return Center( - child: const Text('no drivers found yet', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), - ); - } - return ListView.builder( - itemCount: c.drivers.length, - itemBuilder: (ctx, i) { - final d = c.drivers[i]; - return ListTile( - title: Text(d["first_name"] + d['last_name'] ?? ""), - subtitle: Text(d["phone"] ?? ""), - onTap: () { - Get.to(() => DriverDetailsPage(driverId: d["id"].toString())); - }, - ); - }, - ); - }, + backgroundColor: cs.surface, + body: Column( + children: [ + _buildAppBar(context, cs), + Expanded( + child: GetBuilder( + id: 'drivers', + builder: (c) { + if (c.isLoading) { + return _buildLoadingState(cs); + } + + if (c.drivers.isEmpty) { + return _buildEmptyState(cs); + } + + return RefreshIndicator( + onRefresh: () => controller.getDriversPending(), + color: cs.primary, + child: ListView.separated( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 80), + physics: const BouncingScrollPhysics(), + itemCount: c.drivers.length, + separatorBuilder: (_, __) => const SizedBox(height: 10), + itemBuilder: (ctx, i) { + final d = c.drivers[i]; + return _buildDriverCard(d, cs); + }, + ), + ); + }, + ), + ), + ], + ), + ); + } + + Widget _buildAppBar(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: const Color(0xFFF59E0B).withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + ), + child: Icon(Icons.hourglass_top_rounded, + color: const Color(0xFFF59E0B), size: 18), + ), + const SizedBox(width: 10), + Text( + 'طلبات الكباتن المعلقة', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } + + Widget _buildLoadingState(ColorScheme cs) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: cs.primary, + strokeWidth: 2, + backgroundColor: cs.primary.withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text('جاري تحميل الطلبات...', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)), + ], + ), + ); + } + + Widget _buildEmptyState(ColorScheme cs) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon(Icons.how_to_reg_rounded, + size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text('لا يوجد طلبات تسجيل حالياً', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)), + ], + ), + ); + } + + Widget _buildDriverCard(dynamic driver, ColorScheme cs) { + final String fullName = + '${driver['first_name'] ?? ''} ${driver['last_name'] ?? ''}'.trim(); + final String phone = driver['phone'] ?? ''; + + return Container( + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () { + Get.to(() => DriverDetailsPage(driverId: driver['id'].toString())); + }, + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFFF59E0B).withValues(alpha: 0.20), + const Color(0xFFF59E0B).withValues(alpha: 0.08), + ], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: const Color(0xFFF59E0B).withValues(alpha: 0.2)), + ), + child: Center( + child: Text( + fullName.isNotEmpty ? fullName[0].toUpperCase() : 'D', + style: const TextStyle( + color: Color(0xFFF59E0B), + fontWeight: FontWeight.w800, + fontSize: 18, + ), + ), + ), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + fullName, + style: TextStyle( + color: cs.onSurface, + fontWeight: FontWeight.w600, + fontSize: 15, + ), + ), + const SizedBox(height: 4), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: const Color(0xFFF59E0B) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.hourglass_top_rounded, + size: 11, + color: const Color(0xFFF59E0B)), + const SizedBox(width: 3), + const Text( + 'معلق', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + color: Color(0xFFF59E0B), + ), + ), + ], + ), + ), + const SizedBox(width: 8), + Icon(Icons.phone_rounded, + size: 12, color: cs.onSurfaceVariant), + const SizedBox(width: 4), + Text( + phone, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 12, + fontFamily: 'monospace', + ), + ), + ], + ), + ], + ), + ), + Icon(Icons.arrow_forward_ios_rounded, + size: 14, color: cs.onSurfaceVariant), + ], + ), + ), + ), ), ); } diff --git a/siro_admin/lib/views/admin/complaints/complaint_list_page.dart b/siro_admin/lib/views/admin/complaints/complaint_list_page.dart index 5f6a19e9..89c63772 100644 --- a/siro_admin/lib/views/admin/complaints/complaint_list_page.dart +++ b/siro_admin/lib/views/admin/complaints/complaint_list_page.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:get/get.dart'; -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; import '../../../controller/admin/complaint_controller.dart'; -import '../../widgets/my_scafold.dart'; import '../../widgets/elevated_btn.dart'; import '../../widgets/my_textField.dart'; @@ -14,81 +12,101 @@ class ComplaintListPage extends StatelessWidget { @override Widget build(BuildContext context) { - return MyScafolld( - title: 'إدارة الشكاوى'.tr, - isleading: true, - body: [ - Obx(() { - if (controller.delayedComplaints.isNotEmpty) { - return Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: AppColor.danger.withOpacity(0.1), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColor.danger.withOpacity(0.3)), - ), - child: Column( - children: [ - Row( - children: [ - const Icon(Icons.warning_amber_rounded, color: AppColor.danger), - const SizedBox(width: 12), - Expanded( - child: Text( - 'هناك ${controller.delayedComplaints.length} شكاوى لم يتم حلها منذ أكثر من أسبوع!', - style: AppStyle.body.copyWith(color: AppColor.danger, fontWeight: FontWeight.bold), + final cs = Theme.of(context).colorScheme; + + return Scaffold( + backgroundColor: cs.surface, + appBar: AppBar( + backgroundColor: cs.surface, + elevation: 0, + centerTitle: true, + title: Text('إدارة الشكاوى'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), + leading: GestureDetector( + onTap: () => Get.back(), + child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface), + ), + ), + body: Column( + children: [ + Obx(() { + if (controller.delayedComplaints.isNotEmpty) { + return Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: cs.error.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.error.withValues(alpha: 0.3)), + ), + child: Column( + children: [ + Row( + children: [ + Icon(Icons.warning_amber_rounded, color: cs.error), + const SizedBox(width: 12), + Expanded( + child: Text( + 'هناك ${controller.delayedComplaints.length} شكاوى لم يتم حلها منذ أكثر من أسبوع!', + style: GoogleFonts.inter(color: cs.error, fontSize: 14, fontWeight: FontWeight.bold), + ), ), - ), - ], - ), - const SizedBox(height: 12), - Obx(() => MyElevatedButton( - title: controller.showOnlyDelayed.value ? 'عرض جميع الشكاوى' : 'عرض الشكاوى المتأخرة فقط', - onPressed: () => controller.showOnlyDelayed.toggle(), - kolor: AppColor.danger, - )), - ], - ), - ); - } - return const SizedBox.shrink(); - }), - Obx(() { - final list = controller.showOnlyDelayed.value ? controller.delayedComplaints : controller.complaintList; - if (controller.isLoading.value && list.isEmpty) { - return const Center(child: CircularProgressIndicator()); - } - return RefreshIndicator( - onRefresh: () => controller.getComplaints(), - child: ListView.builder( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), - itemCount: list.length, - itemBuilder: (context, index) { - final complaint = list[index]; - return _buildComplaintCard(context, complaint); - }, - ), - ); - }), - ], + ], + ), + const SizedBox(height: 12), + Obx(() => MyElevatedButton( + title: controller.showOnlyDelayed.value ? 'عرض جميع الشكاوى' : 'عرض الشكاوى المتأخرة فقط', + onPressed: () => controller.showOnlyDelayed.toggle(), + kolor: cs.error, + )), + ], + ), + ); + } + return const SizedBox.shrink(); + }), + Expanded( + child: Obx(() { + final list = controller.showOnlyDelayed.value ? controller.delayedComplaints : controller.complaintList; + if (controller.isLoading.value && list.isEmpty) { + return Center(child: CircularProgressIndicator(color: cs.primary)); + } + return RefreshIndicator( + onRefresh: () => controller.getComplaints(), + child: ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), + itemCount: list.length, + itemBuilder: (context, index) { + final complaint = list[index]; + return _buildComplaintCard(context, complaint); + }, + ), + ); + }), + ), + ], + ), ); } Widget _buildComplaintCard(BuildContext context, dynamic c) { - Color statusColor = _getStatusColor(c['statusComplaint']); - + final cs = Theme.of(context).colorScheme; + Color statusColor = _getStatusColor(c['statusComplaint'], cs); + return Container( margin: const EdgeInsets.only(bottom: 16), - decoration: AppStyle.cardDecoration, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), child: ExpansionTile( tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - leading: _buildStatusIndicator(c['statusComplaint'], statusColor), + leading: _buildStatusIndicator(c['statusComplaint'], statusColor, cs), title: Text( c['description']?.toString() ?? 'بدون وصف', maxLines: 1, overflow: TextOverflow.ellipsis, - style: AppStyle.title, + style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -96,18 +114,18 @@ class ComplaintListPage extends StatelessWidget { const SizedBox(height: 4), Text( 'النوع: ${c['complaint_type'] ?? 'عام'} | الرحلة: ${c['ride_id']}', - style: AppStyle.caption, + style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12), ), const SizedBox(height: 4), Row( children: [ - Icon(Icons.person_rounded, size: 12, color: AppColor.textSecondary), + Icon(Icons.person_rounded, size: 12, color: cs.onSurfaceVariant), const SizedBox(width: 4), - Text(c['passengerName'] ?? 'غير معروف', style: AppStyle.caption), + Text(c['passengerName'] ?? 'غير معروف', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(width: 12), - Icon(Icons.drive_eta_rounded, size: 12, color: AppColor.textSecondary), + Icon(Icons.drive_eta_rounded, size: 12, color: cs.onSurfaceVariant), const SizedBox(width: 4), - Text(c['driverName'] ?? 'غير معروف', style: AppStyle.caption), + Text(c['driverName'] ?? 'غير معروف', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)), ], ), ], @@ -118,12 +136,12 @@ class ComplaintListPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Divider(color: AppColor.divider), - _buildInfoRow('الوصف', c['description'] ?? 'لا يوجد وصف'), + Divider(color: cs.outline), + _buildInfoRow('الوصف', c['description'] ?? 'لا يوجد وصف', cs), const SizedBox(height: 12), - _buildInfoRow('الحل الحالي', c['resolution'] ?? 'لم يتم الحل بعد'), + _buildInfoRow('الحل الحالي', c['resolution'] ?? 'لم يتم الحل بعد', cs), const SizedBox(height: 12), - _buildRideDetails(c), + _buildRideDetails(c, cs), const SizedBox(height: 24), Row( children: [ @@ -131,7 +149,7 @@ class ComplaintListPage extends StatelessWidget { child: MyElevatedButton( title: 'تحديث الحالة / حل الشكوى', onPressed: () => _showResolveDialog(context, c), - kolor: AppColor.accent, + kolor: cs.primary, ), ), ], @@ -144,14 +162,14 @@ class ComplaintListPage extends StatelessWidget { ); } - Widget _buildStatusIndicator(String? status, Color color) { + Widget _buildStatusIndicator(String? status, Color color, ColorScheme cs) { return Container( width: 40, height: 40, decoration: BoxDecoration( - color: color.withOpacity(0.12), + color: color.withValues(alpha: 0.12), shape: BoxShape.circle, - border: Border.all(color: color.withOpacity(0.25)), + border: Border.all(color: color.withValues(alpha: 0.25)), ), child: Icon( status == 'Resolved' ? Icons.check_circle_rounded : Icons.pending_rounded, @@ -161,33 +179,33 @@ class ComplaintListPage extends StatelessWidget { ); } - Widget _buildInfoRow(String label, String value) { + Widget _buildInfoRow(String label, String value, ColorScheme cs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: AppStyle.caption.copyWith(color: AppColor.accent)), + Text(label, style: GoogleFonts.inter(color: cs.primary, fontSize: 12)), const SizedBox(height: 4), - Text(value, style: AppStyle.body), + Text(value, style: GoogleFonts.inter(color: cs.onSurface, fontSize: 14)), ], ); } - Widget _buildRideDetails(dynamic c) { + Widget _buildRideDetails(dynamic c, ColorScheme cs) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: AppColor.surfaceElevated, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColor.divider), + border: Border.all(color: cs.outline), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildSmallStat('السعر', '${c['priceOfRide']} ل.س'), - _buildSmallStat('التقييم', '${c['avgRatingDriverFromPassengers'] ?? 0}★'), - _buildSmallStat('النوع', c['ascarType'] ?? 'N/A'), + _buildSmallStat('السعر', '${c['priceOfRide']} ل.س', cs), + _buildSmallStat('التقييم', '${c['avgRatingDriverFromPassengers'] ?? 0}★', cs), + _buildSmallStat('النوع', c['ascarType'] ?? 'N/A', cs), ], ), ], @@ -195,26 +213,27 @@ class ComplaintListPage extends StatelessWidget { ); } - Widget _buildSmallStat(String label, String value) { + Widget _buildSmallStat(String label, String value, ColorScheme cs) { return Column( children: [ - Text(label, style: AppStyle.caption.copyWith(fontSize: 10)), + Text(label, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 10)), const SizedBox(height: 2), - Text(value, style: AppStyle.number.copyWith(fontSize: 12)), + Text(value, style: GoogleFonts.jetBrainsMono(fontWeight: FontWeight.bold, fontSize: 12, color: cs.onSurface)), ], ); } - Color _getStatusColor(String? status) { + Color _getStatusColor(String? status, ColorScheme cs) { switch (status) { - case 'Open': return AppColor.danger; - case 'In Progress': return AppColor.warning; - case 'Resolved': return AppColor.success; - default: return AppColor.textSecondary; + case 'Open': return cs.error; + case 'In Progress': return cs.tertiary; + case 'Resolved': return const Color(0xFF10B981); + default: return cs.onSurfaceVariant; } } void _showResolveDialog(BuildContext context, dynamic c) { + final cs = Theme.of(context).colorScheme; final TextEditingController resController = TextEditingController(text: c['resolution']); String selectedStatus = c['statusComplaint'] ?? 'Open'; @@ -222,16 +241,16 @@ class ComplaintListPage extends StatelessWidget { StatefulBuilder( builder: (context, setModalState) => Container( padding: const EdgeInsets.all(24), - decoration: const BoxDecoration( - color: AppColor.surfaceElevated, - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text('تحديث حالة الشكوى', style: AppStyle.headTitle), + Text('تحديث حالة الشكوى', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)), const SizedBox(height: 20), - _buildStatusDropdown(selectedStatus, (val) { + _buildStatusDropdown(selectedStatus, cs, (val) { setModalState(() => selectedStatus = val!); }), const SizedBox(height: 20), @@ -263,21 +282,21 @@ class ComplaintListPage extends StatelessWidget { ); } - Widget _buildStatusDropdown(String current, Function(String?) onChanged) { + Widget _buildStatusDropdown(String current, ColorScheme cs, Function(String?) onChanged) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), - border: Border.all(color: AppColor.divider), + border: Border.all(color: cs.outline), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: current, isExpanded: true, - dropdownColor: AppColor.surfaceElevated, + dropdownColor: cs.surfaceContainerHighest, items: ['Open', 'In Progress', 'Resolved'] - .map((s) => DropdownMenuItem(value: s, child: Text(s.tr, style: AppStyle.body))) + .map((s) => DropdownMenuItem(value: s, child: Text(s.tr, style: GoogleFonts.inter(color: cs.onSurface, fontSize: 14)))) .toList(), onChanged: onChanged, ), diff --git a/siro_admin/lib/views/admin/dashboard_v2_widget.dart b/siro_admin/lib/views/admin/dashboard_v2_widget.dart index 8859a1e6..2c4c3792 100644 --- a/siro_admin/lib/views/admin/dashboard_v2_widget.dart +++ b/siro_admin/lib/views/admin/dashboard_v2_widget.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/theme.dart'; import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:siro_admin/views/widgets/glass_container.dart'; diff --git a/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart b/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart index 29ccbf73..3c30295c 100644 --- a/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart +++ b/siro_admin/lib/views/admin/drivers/driver_documents_review_page.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:get/get.dart'; -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; import '../../../controller/admin/driver_docs_controller.dart'; -import '../../widgets/my_scafold.dart'; import '../../widgets/elevated_btn.dart'; import '../../widgets/snackbar.dart'; import '../../../constant/links.dart'; @@ -15,67 +13,86 @@ class DriverDocsReviewPage extends StatelessWidget { @override Widget build(BuildContext context) { - return MyScafolld( - title: 'مراجعة طلبات التسجيل'.tr, - isleading: true, - body: [ - Obx(() => controller.isLoading.value && controller.pendingDrivers.isEmpty - ? const Center(child: CircularProgressIndicator()) - : controller.pendingDrivers.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.how_to_reg_rounded, size: 64, color: AppColor.textMuted), - const SizedBox(height: 16), - Text('لا يوجد طلبات تسجيل حالياً', style: AppStyle.subtitle), - ], - ), - ) - : RefreshIndicator( - onRefresh: () => controller.getPendingDrivers(), - child: NotificationListener( - onNotification: (ScrollNotification scrollInfo) { - if (!controller.isLoading.value && - !controller.isMoreLoading.value && - scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 200) { - controller.loadMore(); - } - return false; - }, - child: ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: controller.pendingDrivers.length + (controller.hasMore.value ? 1 : 0), - itemBuilder: (context, index) { - if (index == controller.pendingDrivers.length) { - return const Padding( - padding: EdgeInsets.all(16.0), - child: Center(child: CircularProgressIndicator()), - ); - } - final driver = controller.pendingDrivers[index]; - return _buildDriverCard(context, driver); - }, + final cs = Theme.of(context).colorScheme; + + return Scaffold( + backgroundColor: cs.surface, + appBar: AppBar( + backgroundColor: cs.surface, + elevation: 0, + centerTitle: true, + title: Text('مراجعة طلبات التسجيل'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), + leading: GestureDetector( + onTap: () => Get.back(), + child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface), + ), + ), + body: Obx(() => controller.isLoading.value && controller.pendingDrivers.isEmpty + ? Center(child: CircularProgressIndicator(color: cs.primary)) + : controller.pendingDrivers.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration(shape: BoxShape.circle, color: cs.onSurfaceVariant.withValues(alpha: 0.1)), + child: Icon(Icons.how_to_reg_rounded, size: 48, color: cs.onSurfaceVariant), ), + const SizedBox(height: 16), + Text('لا يوجد طلبات تسجيل حالياً', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 14)), + ], + ), + ) + : RefreshIndicator( + onRefresh: () => controller.getPendingDrivers(), + child: NotificationListener( + onNotification: (ScrollNotification scrollInfo) { + if (!controller.isLoading.value && + !controller.isMoreLoading.value && + scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 200) { + controller.loadMore(); + } + return false; + }, + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: controller.pendingDrivers.length + (controller.hasMore.value ? 1 : 0), + itemBuilder: (context, index) { + if (index == controller.pendingDrivers.length) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Center(child: CircularProgressIndicator(color: cs.primary)), + ); + } + final driver = controller.pendingDrivers[index]; + return _buildDriverCard(context, driver); + }, ), - )), - ], + ), + )), ); } Widget _buildDriverCard(BuildContext context, dynamic driver) { + final cs = Theme.of(context).colorScheme; + return Container( margin: const EdgeInsets.only(bottom: 12), - decoration: AppStyle.cardDecoration, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), leading: CircleAvatar( - backgroundColor: AppColor.accentSoft, - child: Text(driver['first_name']?[0] ?? 'D', style: const TextStyle(color: AppColor.accent)), + backgroundColor: cs.primary.withValues(alpha: 0.1), + child: Text(driver['first_name']?[0] ?? 'D', style: TextStyle(color: cs.primary)), ), - title: Text('${driver['first_name']} ${driver['last_name']}', style: AppStyle.title), - subtitle: Text(driver['phone'] ?? '', style: AppStyle.caption), - trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16, color: AppColor.textSecondary), + title: Text('${driver['first_name']} ${driver['last_name']}', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), + subtitle: Text(driver['phone'] ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)), + trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16, color: cs.onSurfaceVariant), onTap: () => _showDriverDetails(context, driver['id'].toString()), ), ); @@ -88,45 +105,59 @@ class DriverDocsReviewPage extends StatelessWidget { final driver = details['driver']; final List docs = details['documents']; - Get.to(() => MyScafolld( - title: 'تفاصيل السائق', - isleading: true, - body: [ - SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildDriverHeader(driver), - const SizedBox(height: 24), - Text('الوثائق المرفوعة', style: AppStyle.title), - const SizedBox(height: 12), - ...docs.map((doc) => _buildDocCard(doc)), - const SizedBox(height: 32), - MyElevatedButton( - title: 'اعتماد وتفعيل الحساب', - icon: Icons.check_circle_rounded, - kolor: AppColor.success, - onPressed: () async { - bool success = await controller.approveDriver(id); - if (success) { - Get.back(); - mySnackbarSuccess('تم تفعيل حساب السائق بنجاح'); - } - }, - ), - const SizedBox(height: 100), - ], + Get.to(() => Builder(builder: (ctx) { + final cs = Theme.of(ctx).colorScheme; + return Scaffold( + backgroundColor: cs.surface, + appBar: AppBar( + backgroundColor: cs.surface, + elevation: 0, + centerTitle: true, + title: Text('تفاصيل السائق', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), + leading: GestureDetector( + onTap: () => Get.back(), + child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface), + ), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDriverHeader(driver, cs), + const SizedBox(height: 24), + Text('الوثائق المرفوعة', style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), + const SizedBox(height: 12), + ...docs.map((doc) => _buildDocCard(doc, cs)), + const SizedBox(height: 32), + MyElevatedButton( + title: 'اعتماد وتفعيل الحساب', + icon: Icons.check_circle_rounded, + kolor: const Color(0xFF10B981), + onPressed: () async { + bool success = await controller.approveDriver(id); + if (success) { + Get.back(); + mySnackbarSuccess('تم تفعيل حساب السائق بنجاح'); + } + }, ), - ), - ], - )); + const SizedBox(height: 100), + ], + ), + ), + ); + })); } - Widget _buildDriverHeader(dynamic driver) { + Widget _buildDriverHeader(dynamic driver, ColorScheme cs) { return Container( padding: const EdgeInsets.all(20), - decoration: AppStyle.elevatedCard, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + ), child: Column( children: [ Row( @@ -135,28 +166,28 @@ class DriverDocsReviewPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('${driver['first_name']} ${driver['last_name']}', style: AppStyle.headTitle), - Text(driver['phone'] ?? '', style: AppStyle.subtitle), + Text('${driver['first_name']} ${driver['last_name']}', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)), + Text(driver['phone'] ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant)), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: AppColor.warning.withOpacity(0.2), + color: cs.tertiary.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(20), ), - child: Text('Pending', style: AppStyle.caption.copyWith(color: AppColor.warning)), + child: Text('Pending', style: GoogleFonts.inter(color: cs.tertiary, fontSize: 12)), ), ], ), - const Divider(height: 32, color: AppColor.divider), + Divider(height: 32, color: cs.outline), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildSmallInfo('الرقم الوطني', driver['national_number'] ?? 'N/A'), - _buildSmallInfo('الجنس', driver['gender'] ?? 'N/A'), - _buildSmallInfo('تاريخ الميلاد', driver['birthdate'] ?? 'N/A'), + _buildSmallInfo('الرقم الوطني', driver['national_number'] ?? 'N/A', cs), + _buildSmallInfo('الجنس', driver['gender'] ?? 'N/A', cs), + _buildSmallInfo('تاريخ الميلاد', driver['birthdate'] ?? 'N/A', cs), ], ), ], @@ -164,27 +195,30 @@ class DriverDocsReviewPage extends StatelessWidget { ); } - Widget _buildSmallInfo(String label, String value) { + Widget _buildSmallInfo(String label, String value, ColorScheme cs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: AppStyle.caption.copyWith(fontSize: 10)), + Text(label, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 10)), const SizedBox(height: 2), - Text(value, style: AppStyle.body.copyWith(fontSize: 12, fontWeight: FontWeight.bold)), + Text(value, style: GoogleFonts.inter(fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface)), ], ); } - Widget _buildDocCard(dynamic doc) { + Widget _buildDocCard(dynamic doc, ColorScheme cs) { String imageUrl = doc['link'] ?? ''; - // Ensure URL is absolute if (!imageUrl.startsWith('http')) { imageUrl = '${AppLink.server}/upload/drivers/$imageUrl'; } return Container( margin: const EdgeInsets.only(bottom: 16), - decoration: AppStyle.cardDecoration, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -192,9 +226,9 @@ class DriverDocsReviewPage extends StatelessWidget { padding: const EdgeInsets.all(12), child: Row( children: [ - const Icon(Icons.file_present_rounded, color: AppColor.accent, size: 20), + Icon(Icons.file_present_rounded, color: cs.primary, size: 20), const SizedBox(width: 8), - Text(doc['doc_type'] ?? 'وثيقة', style: AppStyle.title.copyWith(fontSize: 14)), + Text(doc['doc_type'] ?? 'وثيقة', style: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 14, color: cs.onSurface)), ], ), ), @@ -207,8 +241,8 @@ class DriverDocsReviewPage extends StatelessWidget { fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => Container( height: 200, - color: AppColor.surfaceElevated, - child: const Center(child: Icon(Icons.broken_image_rounded, size: 48, color: AppColor.textMuted)), + color: cs.surfaceContainerHighest, + child: Center(child: Icon(Icons.broken_image_rounded, size: 48, color: cs.onSurfaceVariant)), ), ), ), diff --git a/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart b/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart index b6619d4d..15eb3653 100644 --- a/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart +++ b/siro_admin/lib/views/admin/drivers/driver_gift_check_page.dart @@ -3,30 +3,22 @@ import 'package:get/get.dart'; import 'package:siro_admin/controller/functions/crud.dart'; import 'package:siro_admin/controller/functions/wallet.dart'; import 'package:siro_admin/views/widgets/snackbar.dart'; +import 'package:siro_admin/constant/links.dart'; -import '../../../constant/links.dart'; // تأكد من المسار - -// --- Controller: المسؤول عن المنطق (البحث، الفحص، الإضافة) --- class DriverGiftCheckerController extends GetxController { - // للتحكم في حقل النص final TextEditingController phoneController = TextEditingController(); - // لعرض النتائج وحالة التحميل var statusLog = "".obs; var isLoading = false.obs; - // قائمة السائقين (سنقوم بتحميلها للبحث عن الـ ID) List driversCache = []; - - // 1. تحميل قائمة السائقين لاستخراج الـ ID منها Future fetchDriverCache() async { try { final response = await CRUD().post( link: '${AppLink.server}/Admin/driver/getDriverGiftPayment.php', payload: {'phone': phoneController.text.trim()}, ); - // print('response: ${response}'); if (response != 'failure') { driversCache = (response['message']); @@ -36,7 +28,6 @@ class DriverGiftCheckerController extends GetxController { } } - // --- الدالة الرئيسية التي تنفذ العملية المطلوبة --- Future processDriverGift() async { String phoneInput = phoneController.text.trim(); @@ -49,13 +40,11 @@ class DriverGiftCheckerController extends GetxController { statusLog.value = "جاري البحث عن السائق..."; try { - // الخطوة 1: استخراج الـ ID بناءً على رقم الهاتف var driver = driversCache.firstWhere( (d) { String dbPhone = d['phone'].toString().replaceAll(RegExp(r'[^0-9]'), ''); String inputPhone = phoneInput.replaceAll(RegExp(r'[^0-9]'), ''); - // قارن آخر 9 أرقام لتجاوز مشكلة 09 مقابل 963 if (dbPhone.length >= 9 && inputPhone.length >= 9) { return dbPhone.substring(dbPhone.length - 9) == inputPhone.substring(inputPhone.length - 9); @@ -77,17 +66,8 @@ class DriverGiftCheckerController extends GetxController { statusLog.value = "✅ تم العثور على السائق: $driverName (ID: $driverId)\nجاري فحص رصيد الهدايا..."; - // الخطوة 2: فحص السيرفر هل الهدية موجودة؟ - // bool hasGift = await _checkIfGiftExistsOnServer(driverId); - - // if (hasGift) { - // statusLog.value += - // "\n⚠️ هذا السائق لديه هدية الافتتاح (30,000) مسبقاً. لم يتم اتخاذ إجراء."; - // } else { - // الخطوة 3: إضافة الهدية statusLog.value += "\n🎁 الهدية غير موجودة. جاري الإضافة..."; await _addGiftToDriver(driverId, phoneInput, "300"); - // } } catch (e) { statusLog.value = "حدث خطأ غير متوقع: $e"; } finally { @@ -95,130 +75,258 @@ class DriverGiftCheckerController extends GetxController { } } - // دالة إضافة الهدية باستخدام WalletController الموجود عندك Future _addGiftToDriver( String driverId, String phone, String amount) async { final wallet = Get.put(WalletController()); - - // استخدام الدالة الموجودة في نظامك await wallet.addDrivergift300('new driver', driverId, amount, phone); - - // statusLog.value += "\n✅ تمت إضافة مبلغ $amount ل.س بنجاح!"; - - // إضافة تنبيه مرئي - // Get.snackbar("تم بنجاح", "تمت إضافة هدية الافتتاح للسائق", - // backgroundColor: Colors.green, colorText: Colors.white); } } -// --- View: واجهة المستخدم --- class DriverGiftCheckPage extends StatelessWidget { const DriverGiftCheckPage({super.key}); @override Widget build(BuildContext context) { - // حقن الكنترولر final controller = Get.put(DriverGiftCheckerController()); + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: const Color(0xFFF8FAFC), - appBar: AppBar( - title: const Text("فحص ومنح هدية الافتتاح", - style: TextStyle(fontWeight: FontWeight.bold)), - backgroundColor: const Color(0xFF0F172A), // نفس لون الهيدر السابق - foregroundColor: Colors.white, - ), - body: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - children: [ - // كارد الإدخال - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - boxShadow: [ - BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 10) - ], - ), + backgroundColor: cs.surface, + body: Column( + children: [ + _buildAppBar(context, cs), + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(16), child: Column( children: [ - const Icon(Icons.card_giftcard, - size: 50, color: Colors.amber), - const SizedBox(height: 10), - const Text( - "أدخل رقم الهاتف للتحقق", - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 20), - - // حقل الإدخال - TextField( - controller: controller.phoneController, - keyboardType: TextInputType.phone, - decoration: InputDecoration( - hintText: 'مثال: 0912345678', - prefixIcon: const Icon(Icons.phone), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10)), - filled: true, - fillColor: Colors.grey[50], - ), - ), - const SizedBox(height: 20), - - // زر التنفيذ - Obx(() => SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: controller.isLoading.value - ? null - : () => controller.processDriverGift(), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0F172A), - foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10)), - ), - child: controller.isLoading.value - ? const CircularProgressIndicator( - color: Colors.white) - : const Text("تحقق ومنح الهدية (30,000)", - style: TextStyle(fontSize: 16)), - ), - )), + _buildInputCard(context, controller, cs), + const SizedBox(height: 24), + _buildLogSection(controller, cs), ], ), ), + ), + ], + ), + ); + } - const SizedBox(height: 30), + Widget _buildAppBar(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: const Color(0xFFF59E0B).withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + ), + child: Icon(Icons.card_giftcard_rounded, + color: const Color(0xFFF59E0B), size: 18), + ), + const SizedBox(width: 10), + Text( + 'هدايا وعروض الكباتن', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } - // منطقة عرض النتائج (Log) - Expanded( - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.black87, - borderRadius: BorderRadius.circular(12), - ), - child: SingleChildScrollView( - child: Obx(() => Text( - controller.statusLog.value.isEmpty - ? "بانتظار العملية..." - : controller.statusLog.value, - style: const TextStyle( - color: Colors.greenAccent, - fontFamily: 'monospace', - height: 1.5), - )), - ), + Widget _buildInputCard( + BuildContext context, DriverGiftCheckerController controller, ColorScheme cs) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + boxShadow: [ + BoxShadow( + color: cs.shadow.withValues(alpha: 0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color(0xFFF59E0B).withValues(alpha: 0.20), + const Color(0xFFF59E0B).withValues(alpha: 0.08), + ], + ), + shape: BoxShape.circle, + border: Border.all( + color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + ), + child: const Icon(Icons.card_giftcard_rounded, + size: 40, color: Color(0xFFF59E0B)), + ), + const SizedBox(height: 16), + Text( + "أدخل رقم الهاتف للتحقق", + style: TextStyle( + color: cs.onSurface, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 6), + Text( + "سيتم التحقق ومنح هدية الافتتاح (300)", + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 12, + ), + ), + const SizedBox(height: 20), + Container( + decoration: BoxDecoration( + color: cs.surface, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: cs.outline), + ), + child: TextField( + controller: controller.phoneController, + keyboardType: TextInputType.phone, + style: TextStyle(color: cs.onSurface, fontSize: 14), + cursorColor: cs.primary, + decoration: InputDecoration( + hintText: 'مثال: 0912345678', + hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 13), + prefixIcon: Icon(Icons.phone_rounded, + color: cs.onSurfaceVariant, size: 20), + border: InputBorder.none, + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), ), - ], - ), + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + height: 48, + child: Obx(() => ElevatedButton( + onPressed: controller.isLoading.value + ? null + : () => controller.processDriverGift(), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFF59E0B), + foregroundColor: Colors.white, + disabledBackgroundColor: cs.outline, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + elevation: 0, + ), + child: controller.isLoading.value + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, strokeWidth: 2), + ) + : const Text("تحقق ومنح الهدية", + style: TextStyle( + fontSize: 14, fontWeight: FontWeight.bold)), + )), + ), + ], + ), + ); + } + + Widget _buildLogSection(DriverGiftCheckerController controller, ColorScheme cs) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(Icons.terminal_rounded, + color: cs.primary, size: 14), + ), + const SizedBox(width: 8), + Text( + 'سجل العمليات', + style: TextStyle( + color: cs.onSurface, + fontSize: 13, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFF0A0A0B), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: cs.outline), + ), + constraints: const BoxConstraints(minHeight: 200), + child: SingleChildScrollView( + child: Obx(() => Text( + controller.statusLog.value.isEmpty + ? "بانتظار العملية..." + : controller.statusLog.value, + style: const TextStyle( + color: Color(0xFF4ADE80), + fontFamily: 'monospace', + fontSize: 12, + height: 1.6, + ), + )), + ), + ), + ], ), ); } diff --git a/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart b/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart index 18a277dc..7e02e7c5 100644 --- a/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart +++ b/siro_admin/lib/views/admin/drivers/driver_tracker_screen.dart @@ -21,30 +21,24 @@ class SiroTrackerScreen extends StatefulWidget { class _SiroTrackerScreenState extends State with TickerProviderStateMixin { - // === Map Controller === final MapController _mapController = MapController(); List _markers = []; - // === State Variables === bool isLiveMode = true; bool isLoading = false; String lastUpdated = "جاري التحميل..."; - // === Counters === int liveCount = 0; int dayCount = 0; Timer? _timer; - // === Animation Controllers === late AnimationController _fadeController; late AnimationController _scaleController; - // === Admin Info === String myPhone = box.read(BoxName.adminPhone).toString(); bool get isSuperAdmin => myPhone == '963942542053' || myPhone == '963992952235'; - // === URLs === final String _baseDir = "${AppLink.server}/ride/location/"; @override @@ -90,10 +84,11 @@ class _SiroTrackerScreenState extends State } void _showSnackBar(String message) { + final cs = Theme.of(context).colorScheme; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), - backgroundColor: const Color(0xFF2C3E50), + backgroundColor: cs.surfaceContainerHighest, behavior: SnackBarBehavior.floating, margin: const EdgeInsets.all(16), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), @@ -160,6 +155,7 @@ class _SiroTrackerScreenState extends State } void _buildMarkers(List drivers) { + final cs = Theme.of(context).colorScheme; List newMarkers = []; for (var d in drivers) { @@ -192,7 +188,7 @@ class _SiroTrackerScreenState extends State cancelled: cancelled, ); }, - child: _buildMarkerWidget(heading), + child: _buildMarkerWidget(heading, cs), ), ), ); @@ -203,7 +199,7 @@ class _SiroTrackerScreenState extends State }); } - Widget _buildMarkerWidget(double heading) { + Widget _buildMarkerWidget(double heading, ColorScheme cs) { return Stack( alignment: Alignment.center, children: [ @@ -214,17 +210,17 @@ class _SiroTrackerScreenState extends State shape: BoxShape.circle, gradient: LinearGradient( colors: isLiveMode - ? [const Color(0xFF27AE60), const Color(0xFF229954)] - : [const Color(0xFF3498DB), const Color(0xFF2980B9)], + ? [const Color(0xFF10B981), const Color(0xFF10B981)] + : [cs.tertiary, cs.tertiary], begin: Alignment.topLeft, end: Alignment.bottomRight, ), boxShadow: [ BoxShadow( color: (isLiveMode - ? const Color(0xFF27AE60) - : const Color(0xFF3498DB)) - .withOpacity(0.5), + ? const Color(0xFF10B981) + : cs.tertiary) + .withValues(alpha: 0.5), blurRadius: 12, spreadRadius: 2, ) @@ -235,7 +231,7 @@ class _SiroTrackerScreenState extends State angle: heading * (math.pi / 180), child: Icon( Icons.navigation, - color: Colors.white, + color: cs.onPrimary, size: 26, ), ), @@ -252,6 +248,7 @@ class _SiroTrackerScreenState extends State required String completed, required String cancelled, }) { + final cs = Theme.of(context).colorScheme; showDialog( context: context, builder: (_) => Dialog( @@ -260,14 +257,14 @@ class _SiroTrackerScreenState extends State child: Container( decoration: BoxDecoration( gradient: LinearGradient( - colors: [Colors.white, Colors.grey.shade50], + colors: [cs.surface, cs.surfaceContainerHighest], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.15), + color: cs.shadow.withValues(alpha: 0.15), blurRadius: 30, spreadRadius: 5, ) @@ -284,38 +281,38 @@ class _SiroTrackerScreenState extends State decoration: BoxDecoration( gradient: LinearGradient( colors: isLiveMode - ? [const Color(0xFF27AE60), const Color(0xFF229954)] - : [const Color(0xFF3498DB), const Color(0xFF2980B9)], + ? [const Color(0xFF10B981), const Color(0xFF10B981)] + : [cs.tertiary, cs.tertiary], ), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.person_outline, color: Colors.white, size: 20), + Icon(Icons.person_outline, color: cs.onPrimary, size: 20), const SizedBox(width: 8), - const Text( + Text( "معلومات الكابتن", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.white, + color: cs.onPrimary, ), ), ], ), ), const SizedBox(height: 20), - _buildInfoCard(Icons.person, "الاسم", name), + _buildInfoCard(Icons.person, "الاسم", name, cs), const SizedBox(height: 12), - _buildInfoCard(Icons.badge, "المعرف", driverId), + _buildInfoCard(Icons.badge, "المعرف", driverId, cs), const SizedBox(height: 12), - _buildInfoCard(Icons.speed, "السرعة", "$speed كم/س"), + _buildInfoCard(Icons.speed, "السرعة", "$speed كم/س", cs), const SizedBox(height: 20), - _buildStatsContainer(completed, cancelled), + _buildStatsContainer(completed, cancelled, cs), if (isSuperAdmin) ...[ const SizedBox(height: 16), - _buildPhoneButton(phone), + _buildPhoneButton(phone, cs), ], const SizedBox(height: 20), SizedBox( @@ -323,8 +320,8 @@ class _SiroTrackerScreenState extends State child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF2C3E50), - foregroundColor: Colors.white, + backgroundColor: cs.onSurface, + foregroundColor: cs.onPrimary, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -348,19 +345,20 @@ class _SiroTrackerScreenState extends State ); } - Widget _buildInfoCard(IconData icon, String label, String value) { + Widget _buildInfoCard( + IconData icon, String label, String value, ColorScheme cs) { return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, borderRadius: BorderRadius.circular(12), border: Border.all( - color: Colors.grey.shade200, + color: cs.outline, width: 1, ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: cs.shadow.withValues(alpha: 0.05), blurRadius: 8, ) ], @@ -370,10 +368,10 @@ class _SiroTrackerScreenState extends State Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.grey.shade100, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), - child: Icon(icon, color: const Color(0xFF2C3E50), size: 20), + child: Icon(icon, color: cs.onSurface, size: 20), ), const SizedBox(width: 12), Column( @@ -383,17 +381,17 @@ class _SiroTrackerScreenState extends State label, style: TextStyle( fontSize: 12, - color: Colors.grey.shade600, + color: cs.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, - color: Color(0xFF2C3E50), + color: cs.onSurface, ), ), ], @@ -403,34 +401,36 @@ class _SiroTrackerScreenState extends State ); } - Widget _buildStatsContainer(String completed, String cancelled) { + Widget _buildStatsContainer( + String completed, String cancelled, ColorScheme cs) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( - colors: [Colors.grey.shade50, Colors.grey.shade100], + colors: [cs.surfaceContainerHighest, cs.surfaceContainerHighest], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade200), + border: Border.all(color: cs.outline), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildStatItem("✓ مكتملة", completed, const Color(0xFF27AE60)), + _buildStatItem("✓ مكتملة", completed, const Color(0xFF10B981), cs), Container( width: 1, height: 40, - color: Colors.grey.shade300, + color: cs.outline, ), - _buildStatItem("✕ ملغاة", cancelled, const Color(0xFFE74C3C)), + _buildStatItem("✕ ملغاة", cancelled, cs.error, cs), ], ), ); } - Widget _buildStatItem(String label, String value, Color color) { + Widget _buildStatItem( + String label, String value, Color color, ColorScheme cs) { return Column( children: [ Text( @@ -446,7 +446,7 @@ class _SiroTrackerScreenState extends State label, style: TextStyle( fontSize: 12, - color: Colors.grey.shade600, + color: cs.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), @@ -454,7 +454,7 @@ class _SiroTrackerScreenState extends State ); } - Widget _buildPhoneButton(String phone) { + Widget _buildPhoneButton(String phone, ColorScheme cs) { return InkWell( onTap: () { if (phone.isNotEmpty) _makePhoneCall(phone); @@ -465,14 +465,14 @@ class _SiroTrackerScreenState extends State padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 14), decoration: BoxDecoration( gradient: LinearGradient( - colors: [const Color(0xFFFFA500), const Color(0xFFFF8C00)], + colors: [cs.tertiary, cs.tertiary], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: const Color(0xFFFFA500).withOpacity(0.4), + color: cs.tertiary.withValues(alpha: 0.4), blurRadius: 12, spreadRadius: 2, ) @@ -481,13 +481,13 @@ class _SiroTrackerScreenState extends State child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon(Icons.call, color: Colors.white, size: 20), + Icon(Icons.call, color: cs.onPrimary, size: 20), const SizedBox(width: 10), Expanded( child: Text( phone, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: cs.onPrimary, fontWeight: FontWeight.bold, fontSize: 15, ), @@ -502,6 +502,7 @@ class _SiroTrackerScreenState extends State @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; return Scaffold( extendBodyBehindAppBar: true, appBar: AppBar( @@ -511,20 +512,19 @@ class _SiroTrackerScreenState extends State title: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: const Color(0xFF2C3E50).withOpacity(0.9), + color: cs.onSurface.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(12), - // backdropFilter: const BackdropFilter(blur: 10), ), - child: const Text( + child: Text( "نظام تتبع الكابتن", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Colors.white, + color: cs.onPrimary, ), ), ), - foregroundColor: Colors.white, + foregroundColor: cs.onPrimary, ), body: Stack( children: [ @@ -542,13 +542,13 @@ class _SiroTrackerScreenState extends State MarkerLayer(markers: _markers), ], ), - _buildDashboard(), + _buildDashboard(cs), ], ), ); } - Widget _buildDashboard() { + Widget _buildDashboard(ColorScheme cs) { return Positioned( top: 100, right: 16, @@ -559,11 +559,11 @@ class _SiroTrackerScreenState extends State child: Container( width: 300, decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.15), + color: cs.shadow.withValues(alpha: 0.15), blurRadius: 30, spreadRadius: 5, ) @@ -573,15 +573,11 @@ class _SiroTrackerScreenState extends State borderRadius: BorderRadius.circular(20), child: Column( children: [ - // Header Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( - colors: [ - const Color(0xFF2C3E50), - const Color(0xFF34495E) - ], + colors: [cs.onSurface, cs.onSurfaceVariant], begin: Alignment.topLeft, end: Alignment.bottomRight, ), @@ -589,12 +585,11 @@ class _SiroTrackerScreenState extends State child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Icon(Icons.dashboard, - color: Colors.white, size: 22), - const Text( + Icon(Icons.dashboard, color: cs.onPrimary, size: 22), + Text( "لوحة التحكم", style: TextStyle( - color: Colors.white, + color: cs.onPrimary, fontSize: 16, fontWeight: FontWeight.bold, ), @@ -607,7 +602,6 @@ class _SiroTrackerScreenState extends State child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - // Mode Buttons Row( children: [ Expanded( @@ -618,7 +612,8 @@ class _SiroTrackerScreenState extends State setState(() => isLiveMode = false); fetchData(); }, - const Color(0xFF3498DB), + cs.tertiary, + cs, ), ), const SizedBox(width: 10), @@ -630,36 +625,35 @@ class _SiroTrackerScreenState extends State setState(() => isLiveMode = true); fetchData(); }, - const Color(0xFF27AE60), + const Color(0xFF10B981), + cs, ), ), ], ), const SizedBox(height: 16), - - // Stats _buildStatRow( icon: Icons.live_tv, label: "نشط الآن (مباشر)", value: liveCount.toString(), - color: const Color(0xFF27AE60), + color: const Color(0xFF10B981), + cs: cs, ), const SizedBox(height: 12), _buildStatRow( icon: Icons.history, label: "إجمالي اليوم", value: dayCount.toString(), - color: const Color(0xFF3498DB), + color: cs.tertiary, + cs: cs, ), const SizedBox(height: 14), - - // Last Update Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: Colors.grey.shade50, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey.shade200), + border: Border.all(color: cs.outline), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -670,7 +664,7 @@ class _SiroTrackerScreenState extends State : "تحديث: $lastUpdated", style: TextStyle( fontSize: 11, - color: Colors.grey.shade600, + color: cs.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), @@ -679,23 +673,24 @@ class _SiroTrackerScreenState extends State ? Icons.hourglass_bottom : Icons.check_circle, size: 14, - color: isLoading ? Colors.orange : Colors.green, + color: isLoading + ? cs.tertiary + : const Color(0xFF10B981), ), ], ), ), const SizedBox(height: 12), - - // Refresh Button SizedBox( width: double.infinity, child: ElevatedButton( onPressed: isLoading ? null : fetchData, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF2C3E50), - foregroundColor: Colors.white, - disabledBackgroundColor: Colors.grey.shade300, - padding: const EdgeInsets.symmetric(vertical: 12), + backgroundColor: cs.onSurface, + foregroundColor: cs.onPrimary, + disabledBackgroundColor: cs.outline, + padding: + const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -705,13 +700,14 @@ class _SiroTrackerScreenState extends State mainAxisAlignment: MainAxisAlignment.center, children: [ if (isLoading) - const SizedBox( + SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - Colors.white, + valueColor: + AlwaysStoppedAnimation( + cs.onPrimary, ), ), ) @@ -746,6 +742,7 @@ class _SiroTrackerScreenState extends State bool active, VoidCallback onTap, Color color, + ColorScheme cs, ) { return InkWell( onTap: onTap, @@ -757,21 +754,21 @@ class _SiroTrackerScreenState extends State decoration: BoxDecoration( gradient: active ? LinearGradient( - colors: [color, color.withOpacity(0.8)], + colors: [color, color.withValues(alpha: 0.8)], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : null, - color: active ? null : Colors.grey.shade100, + color: active ? null : cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), border: Border.all( - color: active ? color : Colors.grey.shade300, + color: active ? color : cs.outline, width: 1.5, ), boxShadow: active ? [ BoxShadow( - color: color.withOpacity(0.3), + color: color.withValues(alpha: 0.3), blurRadius: 8, spreadRadius: 1, ) @@ -781,7 +778,7 @@ class _SiroTrackerScreenState extends State child: Text( title, style: TextStyle( - color: active ? Colors.white : Colors.grey.shade700, + color: active ? cs.onPrimary : cs.onSurfaceVariant, fontWeight: active ? FontWeight.bold : FontWeight.w600, fontSize: 13, ), @@ -795,13 +792,14 @@ class _SiroTrackerScreenState extends State required String label, required String value, required Color color, + required ColorScheme cs, }) { return Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: color.withOpacity(0.1), + color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 18), @@ -815,7 +813,7 @@ class _SiroTrackerScreenState extends State label, style: TextStyle( fontSize: 12, - color: Colors.grey.shade600, + color: cs.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), diff --git a/siro_admin/lib/views/admin/drivers/monitor_ride.dart b/siro_admin/lib/views/admin/drivers/monitor_ride.dart index d4d14bef..31e2a1d2 100644 --- a/siro_admin/lib/views/admin/drivers/monitor_ride.dart +++ b/siro_admin/lib/views/admin/drivers/monitor_ride.dart @@ -4,14 +4,9 @@ import 'package:get/get.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:siro_admin/constant/links.dart'; -// Keep your specific imports import 'package:siro_admin/controller/functions/crud.dart'; import 'package:siro_admin/views/widgets/snackbar.dart'; -/// -------------------------------------------------------------------------- -/// 1. DATA MODELS -/// -------------------------------------------------------------------------- - class DriverLocation { final double latitude; final double longitude; @@ -38,32 +33,21 @@ class DriverLocation { } } -/// -------------------------------------------------------------------------- -/// 2. GETX CONTROLLER -/// -------------------------------------------------------------------------- - -/// تطبيع رقم الهاتف تلقائياً حسب الدولة -/// مثال: 0992952235 ← 963992952235 (سوريا) -/// مثال: 079XXXXXXX ← 96279XXXXXXX (أردن) -/// مثال: 010XXXXXXXX ← 2010XXXXXXXX (مصر) String normalizePhone(String input) { final clean = input.replaceAll(RegExp(r'\D+'), ''); - // Syria: 099XXXXXXX or 9639XXXXXXX if (clean.length == 10 && clean.startsWith('09')) { return '963${clean.substring(1)}'; } if (clean.length == 12 && clean.startsWith('963')) return clean; if (clean.length == 9 && clean.startsWith('9')) return '963$clean'; - // Jordan: 079XXXXXXX or 9627XXXXXXX if (clean.length == 10 && clean.startsWith('07')) { return '962${clean.substring(1)}'; } if (clean.length == 12 && clean.startsWith('962')) return clean; if (clean.length == 9 && clean.startsWith('7')) return '962$clean'; - // Egypt: 010XXXXXXXX or 2010XXXXXXXX if (clean.length == 11 && clean.startsWith('01')) { return '20${clean.substring(1)}'; } @@ -73,32 +57,26 @@ String normalizePhone(String input) { } class RideMonitorController extends GetxController { - // CONFIGURATION final String apiUrl = "${AppLink.server}/Admin/rides/monitorRide.php"; - // INPUT CONTROLLERS final TextEditingController phoneInputController = TextEditingController(); - // OBSERVABLES var isTracking = false.obs; var isLoading = false.obs; var hasError = false.obs; var errorMessage = ''.obs; - // Driver & Ride Data var driverLocation = Rxn(); var driverName = "Unknown Driver".obs; var rideStatus = "Waiting...".obs; - // Route Data var startPoint = Rxn(); var endPoint = Rxn(); - var routePolyline = [].obs; // List of points for the line + var routePolyline = [].obs; - // Map Variables final MapController mapController = MapController(); Timer? _timer; - bool _isFirstLoad = true; // To trigger auto-fit bounds only on first success + bool _isFirstLoad = true; @override void onClose() { @@ -107,15 +85,12 @@ class RideMonitorController extends GetxController { super.onClose(); } - // --- ACTIONS --- - void startSearch() { if (phoneInputController.text.trim().isEmpty) { mySnackbarWarning("يرجى إدخال رقم الهاتف أولاً"); return; } - // Reset state hasError.value = false; errorMessage.value = ''; driverLocation.value = null; @@ -126,14 +101,11 @@ class RideMonitorController extends GetxController { rideStatus.value = "جاري التحميل..."; _isFirstLoad = true; - // Switch UI isTracking.value = true; isLoading.value = true; - // Start fetching fetchRideData(); - // Start Polling _timer?.cancel(); _timer = Timer.periodic(const Duration(seconds: 10), (timer) { fetchRideData(); @@ -144,7 +116,6 @@ class RideMonitorController extends GetxController { _timer?.cancel(); isTracking.value = false; isLoading.value = false; - // phoneInputController.clear(); // اختياري: يمكنك إبقائه لتسهيل البحث مرة أخرى } Future fetchRideData() async { @@ -152,7 +123,6 @@ class RideMonitorController extends GetxController { if (phone.isEmpty) return; try { - // تطبيع رقم الهاتف تلقائياً حسب الدولة String normalizedPhone = normalizePhone(phone); final response = await CRUD().post( link: apiUrl, @@ -168,17 +138,14 @@ class RideMonitorController extends GetxController { final data = jsonResponse['message'] ?? jsonResponse['data'] ?? jsonResponse; - // 1. Parse Driver Info if (data['driver_details'] != null) { driverName.value = data['driver_details']['fullname'] ?? "سائق غير معروف"; } - // 2. Parse Ride Info & Route if (data['ride_details'] != null) { rideStatus.value = data['ride_details']['status'] ?? "غير معروف"; - // Parse Start/End Locations (Format: "lat,lng") String? startStr = data['ride_details']['start_location']; String? endStr = data['ride_details']['end_location']; @@ -188,20 +155,17 @@ class RideMonitorController extends GetxController { if (s != null && e != null) { startPoint.value = s; endPoint.value = e; - routePolyline.value = [s, e]; // Straight line for now + routePolyline.value = [s, e]; } } - // 3. Parse Live Location final locData = data['driver_location']; if (locData is Map) { final newLocation = DriverLocation.fromJson(locData); driverLocation.value = newLocation; - // 4. Update Camera Bounds _updateMapBounds(); } else { - // Even if no live driver, we might want to show the route if (startPoint.value != null && endPoint.value != null) { _updateMapBounds(); } @@ -227,7 +191,6 @@ class RideMonitorController extends GetxController { } } - // Helper to parse "lat,lng" string LatLng? _parseLatLngString(String? str) { if (str == null || !str.contains(',')) return null; try { @@ -240,7 +203,6 @@ class RideMonitorController extends GetxController { } } - // Logic to fit start, end, and driver on screen void _updateMapBounds() { if (!_isFirstLoad) return; @@ -264,48 +226,36 @@ class RideMonitorController extends GetxController { ); _isFirstLoad = false; } catch (e) { - // Map Controller not ready yet } } } } -/// -------------------------------------------------------------------------- -/// 3. UI SCREEN (Modern Light Theme) -/// -------------------------------------------------------------------------- - class RideMonitorScreen extends StatelessWidget { const RideMonitorScreen({super.key}); - // 🎨 الألوان العصرية (Modern Palette) - final Color backgroundColor = const Color(0xFFF4F7FE); - final Color primaryColor = const Color(0xFF4318FF); - final Color textPrimary = const Color(0xFF2B3674); - final Color textSecondary = const Color(0xFFA3AED0); - @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; final RideMonitorController controller = Get.put(RideMonitorController()); return Scaffold( - backgroundColor: backgroundColor, - // الإبقاء على AppBar فقط في شاشة البحث + backgroundColor: cs.surface, appBar: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), child: Obx(() { if (controller.isTracking.value) { - return const SizedBox - .shrink(); // إخفاء الـ AppBar في وضع التتبع للخريطة الكاملة + return const SizedBox.shrink(); } return AppBar( backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, - iconTheme: IconThemeData(color: textPrimary), + iconTheme: IconThemeData(color: cs.onSurface), title: Text( "مراقبة الرحلات", style: TextStyle( - color: textPrimary, + color: cs.onSurface, fontWeight: FontWeight.bold, fontSize: 18, ), @@ -315,18 +265,15 @@ class RideMonitorScreen extends StatelessWidget { ), body: Obx(() { if (!controller.isTracking.value) { - return _buildSearchForm(context, controller); + return _buildSearchForm(context, controller, cs); } - return _buildMapTrackingView(context, controller); + return _buildMapTrackingView(context, controller, cs); }), ); } - // --------------------------------------------------------------------------- - // واجهة البحث (Search View) - // --------------------------------------------------------------------------- Widget _buildSearchForm( - BuildContext context, RideMonitorController controller) { + BuildContext context, RideMonitorController controller, ColorScheme cs) { return Center( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), @@ -335,11 +282,11 @@ class RideMonitorScreen extends StatelessWidget { child: Container( padding: const EdgeInsets.all(32.0), decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, borderRadius: BorderRadius.circular(30), boxShadow: [ BoxShadow( - color: primaryColor.withOpacity(0.08), + color: cs.primary.withValues(alpha: 0.08), blurRadius: 24, offset: const Offset(0, 10), ) @@ -351,17 +298,16 @@ class RideMonitorScreen extends StatelessWidget { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: primaryColor.withOpacity(0.1), + color: cs.primary.withValues(alpha: 0.1), shape: BoxShape.circle, ), - child: - Icon(Icons.radar_rounded, size: 60, color: primaryColor), + child: Icon(Icons.radar_rounded, size: 60, color: cs.primary), ), const SizedBox(height: 24), Text( "تتبع رحلة نشطة", style: TextStyle( - color: textPrimary, + color: cs.onSurface, fontSize: 22, fontWeight: FontWeight.bold, ), @@ -370,7 +316,7 @@ class RideMonitorScreen extends StatelessWidget { Text( "أدخل رقم هاتف السائق أو الراكب للبدء", style: TextStyle( - color: textSecondary, + color: cs.onSurfaceVariant, fontSize: 14, ), textAlign: TextAlign.center, @@ -378,27 +324,27 @@ class RideMonitorScreen extends StatelessWidget { const SizedBox(height: 32), Container( decoration: BoxDecoration( - color: backgroundColor, + color: cs.surface, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white, width: 2), + border: Border.all(color: cs.outline, width: 2), ), child: TextField( controller: controller.phoneInputController, keyboardType: TextInputType.phone, textDirection: TextDirection.ltr, style: TextStyle( - color: textPrimary, + color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600, ), decoration: InputDecoration( hintText: "مثال: 0992952235...", - hintStyle: TextStyle(color: textSecondary), + hintStyle: TextStyle(color: cs.onSurfaceVariant), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( vertical: 18, horizontal: 20), prefixIcon: - Icon(Icons.phone_rounded, color: primaryColor), + Icon(Icons.phone_rounded, color: cs.primary), ), ), ), @@ -409,16 +355,16 @@ class RideMonitorScreen extends StatelessWidget { child: ElevatedButton( onPressed: controller.startSearch, style: ElevatedButton.styleFrom( - backgroundColor: primaryColor, + backgroundColor: cs.primary, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), ), - child: const Text( + child: Text( "بدء المراقبة", style: TextStyle( - color: Colors.white, + color: cs.onPrimary, fontSize: 16, fontWeight: FontWeight.bold, ), @@ -433,11 +379,8 @@ class RideMonitorScreen extends StatelessWidget { ); } - // --------------------------------------------------------------------------- - // واجهة الخريطة (Map View) - // --------------------------------------------------------------------------- Widget _buildMapTrackingView( - BuildContext context, RideMonitorController controller) { + BuildContext context, RideMonitorController controller, ColorScheme cs) { return Stack( children: [ FlutterMap( @@ -451,45 +394,36 @@ class RideMonitorScreen extends StatelessWidget { urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.siromove.admin', ), - - // 1. ROUTE LINE (Polyline) if (controller.routePolyline.isNotEmpty) PolylineLayer( polylines: [ Polyline( points: controller.routePolyline, strokeWidth: 6.0, - color: primaryColor.withOpacity(0.9), + color: cs.primary.withValues(alpha: 0.9), borderStrokeWidth: 2.0, - borderColor: primaryColor.withOpacity(0.3), + borderColor: cs.primary.withValues(alpha: 0.3), strokeCap: StrokeCap.round, strokeJoin: StrokeJoin.round, ), ], ), - - // 2. START & END MARKERS MarkerLayer( markers: [ - // Start Point (Green Dot) if (controller.startPoint.value != null) Marker( point: controller.startPoint.value!, width: 30, height: 30, - child: _buildPointMarker(const Color(0xFF10B981)), + child: _buildPointMarker(const Color(0xFF10B981), cs), ), - - // End Point (Red Dot) if (controller.endPoint.value != null) Marker( point: controller.endPoint.value!, width: 30, height: 30, - child: _buildPointMarker(const Color(0xFFEF4444)), + child: _buildPointMarker(const Color(0xFFEF4444), cs), ), - - // Driver Car Marker if (controller.driverLocation.value != null) Marker( point: LatLng( @@ -507,11 +441,11 @@ class RideMonitorScreen extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: cs.onSurface.withValues(alpha: 0.2), blurRadius: 10, spreadRadius: 2, ) @@ -519,7 +453,7 @@ class RideMonitorScreen extends StatelessWidget { ), child: Icon( Icons.directions_car_rounded, - color: primaryColor, + color: cs.primary, size: 28, ), ), @@ -528,13 +462,13 @@ class RideMonitorScreen extends StatelessWidget { padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: textPrimary, + color: cs.onSurface, borderRadius: BorderRadius.circular(6), ), child: Text( "${controller.driverLocation.value!.speed.toInt()} كم", - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: cs.onPrimary, fontSize: 10, fontWeight: FontWeight.bold, ), @@ -549,48 +483,44 @@ class RideMonitorScreen extends StatelessWidget { ), ], ), - - // زر التراجع (إيقاف التتبع) أعلى الشاشة Positioned( top: MediaQuery.of(context).padding.top + 10, - right: 20, // أو left حسب لغة التطبيق + right: 20, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: cs.onSurface.withValues(alpha: 0.1), blurRadius: 10, offset: const Offset(0, 4), ) ], ), child: IconButton( - icon: Icon(Icons.close_rounded, color: textPrimary, size: 24), + icon: Icon(Icons.close_rounded, color: cs.onSurface, size: 24), onPressed: controller.stopTracking, tooltip: "إيقاف المراقبة", ), ), ), - - // LOADING OVERLAY (Smooth Frosted Glass like) if (controller.isLoading.value && controller.driverLocation.value == null && controller.startPoint.value == null) Container( - color: Colors.white.withOpacity(0.8), + color: cs.surface.withValues(alpha: 0.8), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator( - color: primaryColor, strokeWidth: 3), + color: cs.primary, strokeWidth: 3), const SizedBox(height: 16), Text( "جاري تحديد الموقع...", style: TextStyle( - color: textPrimary, + color: cs.onSurface, fontWeight: FontWeight.bold, fontSize: 16, ), @@ -599,19 +529,17 @@ class RideMonitorScreen extends StatelessWidget { ), ), ), - - // ERROR OVERLAY if (controller.hasError.value) Center( child: Container( margin: const EdgeInsets.all(24), padding: const EdgeInsets.all(24), decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: cs.onSurface.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 10), ) @@ -623,17 +551,17 @@ class RideMonitorScreen extends StatelessWidget { Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), + color: cs.error.withValues(alpha: 0.1), shape: BoxShape.circle, ), - child: const Icon(Icons.error_outline_rounded, - color: Colors.red, size: 40), + child: Icon(Icons.error_outline_rounded, + color: cs.error, size: 40), ), const SizedBox(height: 16), Text( "حدث خطأ", style: TextStyle( - color: textPrimary, + color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.bold, ), @@ -642,7 +570,7 @@ class RideMonitorScreen extends StatelessWidget { Text( controller.errorMessage.value, textAlign: TextAlign.center, - style: TextStyle(color: textSecondary, height: 1.5), + style: TextStyle(color: cs.onSurfaceVariant, height: 1.5), ), const SizedBox(height: 24), SizedBox( @@ -650,8 +578,8 @@ class RideMonitorScreen extends StatelessWidget { child: ElevatedButton( onPressed: controller.stopTracking, style: ElevatedButton.styleFrom( - backgroundColor: backgroundColor, - foregroundColor: textPrimary, + backgroundColor: cs.surface, + foregroundColor: cs.onSurface, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -666,8 +594,6 @@ class RideMonitorScreen extends StatelessWidget { ), ), ), - - // INFO CARD (Bottom Floating Card) if (!controller.hasError.value && !controller.isLoading.value) Positioned( bottom: 30, @@ -675,11 +601,11 @@ class RideMonitorScreen extends StatelessWidget { right: 20, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.08), + color: cs.onSurface.withValues(alpha: 0.08), blurRadius: 24, offset: const Offset(0, 10), ) @@ -696,11 +622,11 @@ class RideMonitorScreen extends StatelessWidget { width: 50, height: 50, decoration: BoxDecoration( - color: primaryColor.withOpacity(0.1), + color: cs.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(15), ), child: Icon(Icons.person_rounded, - color: primaryColor, size: 28), + color: cs.primary, size: 28), ), const SizedBox(width: 16), Expanded( @@ -710,7 +636,7 @@ class RideMonitorScreen extends StatelessWidget { Text( controller.driverName.value, style: TextStyle( - color: textPrimary, + color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.bold, ), @@ -735,7 +661,7 @@ class RideMonitorScreen extends StatelessWidget { Text( controller.rideStatus.value, style: TextStyle( - color: textSecondary, + color: cs.onSurfaceVariant, fontSize: 13, fontWeight: FontWeight.w600, ), @@ -759,17 +685,19 @@ class RideMonitorScreen extends StatelessWidget { Icons.speed_rounded, "${controller.driverLocation.value!.speed.toStringAsFixed(1)} كم/س", const Color(0xFF3B82F6), + cs, ), Container( width: 1, height: 30, - color: Colors.grey.withOpacity(0.2)), + color: cs.outline.withValues(alpha: 0.2)), _buildModernInfoBadge( Icons.access_time_rounded, controller.driverLocation.value!.updatedAt .split(' ') .last, const Color(0xFF8B5CF6), + cs, ), ], ) @@ -781,13 +709,13 @@ class RideMonitorScreen extends StatelessWidget { width: 16, height: 16, child: CircularProgressIndicator( - color: primaryColor, strokeWidth: 2), + color: cs.primary, strokeWidth: 2), ), const SizedBox(width: 10), Text( "جاري الاتصال بالسائق...", style: TextStyle( - color: primaryColor, + color: cs.primary, fontWeight: FontWeight.w600, fontSize: 13, ), @@ -803,12 +731,10 @@ class RideMonitorScreen extends StatelessWidget { ); } - // --- Helper Widgets --- - - Widget _buildPointMarker(Color color) { + Widget _buildPointMarker(Color color, ColorScheme cs) { return Container( decoration: BoxDecoration( - color: color.withOpacity(0.3), + color: color.withValues(alpha: 0.3), shape: BoxShape.circle, ), child: Center( @@ -818,10 +744,10 @@ class RideMonitorScreen extends StatelessWidget { decoration: BoxDecoration( color: color, shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2), + border: Border.all(color: cs.surface, width: 2), boxShadow: [ BoxShadow( - color: color.withOpacity(0.5), + color: color.withValues(alpha: 0.5), blurRadius: 6, spreadRadius: 1, ) @@ -832,13 +758,14 @@ class RideMonitorScreen extends StatelessWidget { ); } - Widget _buildModernInfoBadge(IconData icon, String text, Color iconColor) { + Widget _buildModernInfoBadge( + IconData icon, String text, Color iconColor, ColorScheme cs) { return Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( - color: iconColor.withOpacity(0.1), + color: iconColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, size: 16, color: iconColor), @@ -847,11 +774,11 @@ class RideMonitorScreen extends StatelessWidget { Text( text, style: TextStyle( - color: textPrimary, + color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.bold, ), - textDirection: TextDirection.ltr, // للحفاظ على اتجاه الأرقام + textDirection: TextDirection.ltr, ), ], ); diff --git a/siro_admin/lib/views/admin/employee/employee_page.dart b/siro_admin/lib/views/admin/employee/employee_page.dart index 4f9f1072..bb11e0a8 100644 --- a/siro_admin/lib/views/admin/employee/employee_page.dart +++ b/siro_admin/lib/views/admin/employee/employee_page.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:siro_admin/constant/links.dart'; import 'package:siro_admin/controller/employee_controller/employee_controller.dart'; -import 'package:siro_admin/controller/functions/upload_image copy.dart'; // تأكد من مسار الملف الصحيح +import 'package:siro_admin/controller/functions/upload_image copy.dart'; import 'package:url_launcher/url_launcher.dart'; class EmployeePage extends StatelessWidget { @@ -10,27 +10,21 @@ class EmployeePage extends StatelessWidget { @override Widget build(BuildContext context) { - // حقن الكنترولر Get.put(EmployeeController()); - - // ألوان الثيم - const Color bgColor = Color(0xFF0A0E27); - const Color cardColor = Color(0xFF1A1F3A); - const Color primaryAccent = Color(0xFF6366F1); + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: bgColor, + backgroundColor: cs.surface, body: GetBuilder( builder: (controller) { return CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ - // 1. App Bar SliverAppBar( expandedHeight: 100, floating: true, pinned: true, - backgroundColor: bgColor, + backgroundColor: cs.surface, elevation: 0, flexibleSpace: FlexibleSpaceBar( titlePadding: const EdgeInsets.only(bottom: 16), @@ -40,19 +34,19 @@ class EmployeePage extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: primaryAccent.withOpacity(0.2), + color: cs.primary.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(8), ), - child: const Icon(Icons.badge_rounded, - color: Colors.white, size: 18), + child: Icon(Icons.badge_rounded, + color: cs.onSurface, size: 18), ), const SizedBox(width: 10), - const Text( + Text( 'الموظفون', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, - color: Colors.white, + color: cs.onSurface, fontFamily: 'Segoe UI', ), ), @@ -65,8 +59,8 @@ class EmployeePage extends StatelessWidget { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - primaryAccent.withOpacity(0.15), - bgColor, + cs.primary.withValues(alpha: 0.15), + cs.surface, ], ), ), @@ -74,18 +68,17 @@ class EmployeePage extends StatelessWidget { ), ), - // 2. قائمة الموظفين if (controller.employee.isEmpty) - const SliverFillRemaining( + SliverFillRemaining( child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.people_outline, - size: 60, color: Colors.white24), - SizedBox(height: 16), + size: 60, color: cs.onSurface.withValues(alpha: 0.24)), + const SizedBox(height: 16), Text("لا يوجد موظفين حالياً", - style: TextStyle(color: Colors.white54)), + style: TextStyle(color: cs.onSurfaceVariant)), ], ), ), @@ -100,8 +93,7 @@ class EmployeePage extends StatelessWidget { return _EmployeeCard( employee: employee, index: index, - cardColor: cardColor, - primaryAccent: primaryAccent, + cs: cs, ); }, childCount: controller.employee.length, @@ -118,25 +110,22 @@ class EmployeePage extends StatelessWidget { controller.id = controller.generateRandomId(8); Get.to(() => _EmployeeFormScreen(controller: controller)); }, - backgroundColor: primaryAccent, - child: const Icon(Icons.person_add_rounded, color: Colors.white), + backgroundColor: cs.primary, + child: Icon(Icons.person_add_rounded, color: cs.onPrimary), ), ); } } -// === بطاقة الموظف (تصميم جديد ومحسن) === class _EmployeeCard extends StatelessWidget { final Map employee; final int index; - final Color cardColor; - final Color primaryAccent; + final ColorScheme cs; const _EmployeeCard({ required this.employee, required this.index, - required this.cardColor, - required this.primaryAccent, + required this.cs, }); @override @@ -147,12 +136,12 @@ class _EmployeeCard extends StatelessWidget { return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( - color: cardColor, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.05)), + border: Border.all(color: cs.outline.withValues(alpha: 0.1)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues(alpha: 0.2), blurRadius: 10, offset: const Offset(0, 4), ), @@ -168,28 +157,27 @@ class _EmployeeCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // الصف العلوي: الحالة + أيقونة Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), + color: cs.outline.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Icon(Icons.person, - color: Colors.white.withOpacity(0.7), size: 20), + color: cs.onSurface.withValues(alpha: 0.7), size: 20), ), Flexible( child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6), decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), + color: statusColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: - Border.all(color: statusColor.withOpacity(0.3)), + Border.all(color: statusColor.withValues(alpha: 0.3)), ), child: Text( employee['status'] ?? 'Unknown', @@ -208,23 +196,21 @@ class _EmployeeCard extends StatelessWidget { const SizedBox(height: 12), - // الاسم في سطر كامل ومميز Text( employee['name'] ?? 'Unknown', - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.bold, height: 1.3, ), ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 12), - child: Divider(height: 1, color: Colors.white10), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Divider(height: 1, color: cs.outline.withValues(alpha: 0.1)), ), - // تفاصيل التعليم والهاتف والموقع (مع دعم تعدد الأسطر) Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -233,23 +219,22 @@ class _EmployeeCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInfoRow(Icons.phone_iphone_rounded, - employee['phone'] ?? '', Colors.white54), - const SizedBox(height: 12), // مسافة أكبر بين العناصر + employee['phone'] ?? '', cs.onSurfaceVariant, cs), + const SizedBox(height: 12), _buildInfoRow( Icons.school_outlined, employee['education'] ?? 'غير محدد', - primaryAccent), - const SizedBox(height: 12), // مسافة أكبر + cs.primary, cs), + const SizedBox(height: 12), _buildInfoRow(Icons.location_on_outlined, - employee['site'] ?? 'غير محدد', Colors.blueGrey), + employee['site'] ?? 'غير محدد', Colors.blueGrey, cs), ], ), ), - // زر الاتصال الجانبي const SizedBox(width: 16), Material( - color: Colors.green.withOpacity(0.1), + color: const Color(0xFF10B981).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), child: InkWell( onTap: () => @@ -258,7 +243,7 @@ class _EmployeeCard extends StatelessWidget { child: Container( padding: const EdgeInsets.all(14), child: const Icon(Icons.call, - color: Colors.green, size: 24), + color: Color(0xFF10B981), size: 24), ), ), ), @@ -272,25 +257,23 @@ class _EmployeeCard extends StatelessWidget { ); } - Widget _buildInfoRow(IconData icon, String text, Color iconColor) { + Widget _buildInfoRow(IconData icon, String text, Color iconColor, ColorScheme cs) { return Row( - crossAxisAlignment: - CrossAxisAlignment.start, // محاذاة الأيقونة مع بداية النص + crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.only(top: 2), // ضبط بسيط لموقع الأيقونة - child: Icon(icon, size: 16, color: iconColor.withOpacity(0.8)), + padding: const EdgeInsets.only(top: 2), + child: Icon(icon, size: 16, color: iconColor.withValues(alpha: 0.8)), ), const SizedBox(width: 10), Expanded( child: Text( text, style: TextStyle( - color: Colors.white.withOpacity(0.7), + color: cs.onSurface.withValues(alpha: 0.7), fontSize: 13, - height: 1.5, // تباعد الأسطر لسهولة القراءة + height: 1.5, ), - // تم إزالة maxLines و overflow للسماح بالنص بالنزول لأسطر متعددة ), ), ], @@ -305,25 +288,23 @@ class _EmployeeCard extends StatelessWidget { } } -// === شاشة إضافة موظف === class _EmployeeFormScreen extends StatelessWidget { final EmployeeController controller; const _EmployeeFormScreen({required this.controller}); @override Widget build(BuildContext context) { - const Color bgColor = Color(0xFF0A0E27); - const Color inputColor = Color(0xFF1A1F3A); + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: bgColor, + backgroundColor: cs.surface, appBar: AppBar( - title: const Text("إضافة موظف جديد", - style: TextStyle(color: Colors.white)), - backgroundColor: bgColor, + title: Text("إضافة موظف جديد", + style: TextStyle(color: cs.onSurface)), + backgroundColor: cs.surface, elevation: 0, leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: Icon(Icons.arrow_back, color: cs.onSurface), onPressed: () => Get.back(), ), ), @@ -339,6 +320,7 @@ class _EmployeeFormScreen extends StatelessWidget { child: _UploadButton( title: "الهوية (أمام)", icon: Icons.credit_card, + cs: cs, onPressed: () async { await ImageController().choosImage(AppLink.uploadEgypt, 'idFrontEmployee', controller.id); @@ -350,6 +332,7 @@ class _EmployeeFormScreen extends StatelessWidget { child: _UploadButton( title: "الهوية (خلف)", icon: Icons.credit_card_outlined, + cs: cs, onPressed: () async { await ImageController().choosImage(AppLink.uploadEgypt, 'idbackEmployee', controller.id); @@ -360,20 +343,20 @@ class _EmployeeFormScreen extends StatelessWidget { ), const SizedBox(height: 24), _buildModernTextField( - controller.name, "الاسم الكامل", Icons.person, inputColor), + controller.name, "الاسم الكامل", Icons.person, cs), const SizedBox(height: 16), _buildModernTextField( - controller.phone, "رقم الهاتف", Icons.phone, inputColor, + controller.phone, "رقم الهاتف", Icons.phone, cs, type: TextInputType.phone), const SizedBox(height: 16), _buildModernTextField(controller.education, "التعليم / الملاحظات", - Icons.school, inputColor), + Icons.school, cs), const SizedBox(height: 16), _buildModernTextField(controller.site, "الموقع / العنوان", - Icons.location_on, inputColor), + Icons.location_on, cs), const SizedBox(height: 16), _buildModernTextField(controller.status, "الحالة (مثال: ممتاز)", - Icons.star, inputColor), + Icons.star, cs), const SizedBox(height: 32), SizedBox( width: double.infinity, @@ -385,14 +368,14 @@ class _EmployeeFormScreen extends StatelessWidget { } }, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF6366F1), + backgroundColor: cs.primary, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), - child: const Text("حفظ البيانات", + child: Text("حفظ البيانات", style: TextStyle( - color: Colors.white, + color: cs.onPrimary, fontSize: 16, fontWeight: FontWeight.bold)), ), @@ -405,23 +388,23 @@ class _EmployeeFormScreen extends StatelessWidget { } Widget _buildModernTextField(TextEditingController controller, String hint, - IconData icon, Color fillColor, + IconData icon, ColorScheme cs, {TextInputType type = TextInputType.text}) { return Container( decoration: BoxDecoration( - color: fillColor, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: cs.outline.withValues(alpha: 0.1)), ), child: TextFormField( controller: controller, keyboardType: type, - style: const TextStyle(color: Colors.white), - maxLines: null, // السماح بتعدد الأسطر عند الإدخال أيضاً + style: TextStyle(color: cs.onSurface), + maxLines: null, decoration: InputDecoration( labelText: hint, - labelStyle: TextStyle(color: Colors.white.withOpacity(0.5)), - prefixIcon: Icon(icon, color: Colors.white38, size: 20), + labelStyle: TextStyle(color: cs.onSurface.withValues(alpha: 0.5)), + prefixIcon: Icon(icon, color: cs.onSurfaceVariant, size: 20), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), @@ -437,9 +420,10 @@ class _UploadButton extends StatelessWidget { final String title; final IconData icon; final VoidCallback onPressed; + final ColorScheme cs; const _UploadButton( - {required this.title, required this.icon, required this.onPressed}); + {required this.title, required this.icon, required this.onPressed, required this.cs}); @override Widget build(BuildContext context) { @@ -449,21 +433,21 @@ class _UploadButton extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(vertical: 20), decoration: BoxDecoration( - color: const Color(0xFF1A1F3A), + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), border: Border.all( - color: const Color(0xFF6366F1).withOpacity(0.3), + color: cs.primary.withValues(alpha: 0.3), style: BorderStyle.solid), ), child: Column( children: [ - Icon(icon, color: const Color(0xFF6366F1), size: 30), + Icon(icon, color: cs.primary, size: 30), const SizedBox(height: 8), Text(title, - style: const TextStyle(color: Colors.white70, fontSize: 12)), + style: TextStyle(color: cs.onSurface.withValues(alpha: 0.7), fontSize: 12)), const SizedBox(height: 4), - const Text("اضغط للرفع", - style: TextStyle(color: Colors.white38, fontSize: 10)), + Text("اضغط للرفع", + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)), ], ), ), @@ -471,22 +455,21 @@ class _UploadButton extends StatelessWidget { } } -// === شاشة التفاصيل === class EmployeeDetails extends StatelessWidget { final int index; const EmployeeDetails({super.key, required this.index}); @override Widget build(BuildContext context) { - const Color bgColor = Color(0xFF0A0E27); + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: bgColor, + backgroundColor: cs.surface, appBar: AppBar( title: - const Text('تفاصيل الموظف', style: TextStyle(color: Colors.white)), - backgroundColor: bgColor, - iconTheme: const IconThemeData(color: Colors.white), + Text('تفاصيل الموظف', style: TextStyle(color: cs.onSurface)), + backgroundColor: cs.surface, + iconTheme: IconThemeData(color: cs.onSurface), elevation: 0, ), body: GetBuilder( @@ -497,24 +480,26 @@ class EmployeeDetails extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text("الهوية الأمامية", + Text("الهوية الأمامية", style: TextStyle( - color: Colors.white, + color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 10), _buildImageViewer( '${AppLink.server}/card_image/idFrontEmployee-$employeeId.jpg', + cs, ), const SizedBox(height: 30), - const Text("الهوية الخلفية", + Text("الهوية الخلفية", style: TextStyle( - color: Colors.white, + color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 10), _buildImageViewer( '${AppLink.server}/card_image/idbackEmployee-$employeeId.jpg', + cs, ), ], ), @@ -524,14 +509,14 @@ class EmployeeDetails extends StatelessWidget { ); } - Widget _buildImageViewer(String url) { + Widget _buildImageViewer(String url, ColorScheme cs) { return Container( width: double.infinity, height: 220, decoration: BoxDecoration( - color: const Color(0xFF1A1F3A), + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), + border: Border.all(color: cs.outline.withValues(alpha: 0.1)), ), child: ClipRRect( borderRadius: BorderRadius.circular(16), @@ -539,23 +524,23 @@ class EmployeeDetails extends StatelessWidget { url, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { - return const Center( + return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.broken_image_rounded, - color: Colors.white24, size: 50), - SizedBox(height: 8), + color: cs.onSurface.withValues(alpha: 0.24), size: 50), + const SizedBox(height: 8), Text("فشل تحميل الصورة", - style: TextStyle(color: Colors.white24)), + style: TextStyle(color: cs.onSurface.withValues(alpha: 0.24))), ], ), ); }, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; - return const Center( - child: CircularProgressIndicator(color: Color(0xFF6366F1))); + return Center( + child: CircularProgressIndicator(color: cs.primary)); }, ), ), diff --git a/siro_admin/lib/views/admin/error/error/error_page.dart b/siro_admin/lib/views/admin/error/error/error_page.dart index 50bd7688..feb552c2 100644 --- a/siro_admin/lib/views/admin/error/error/error_page.dart +++ b/siro_admin/lib/views/admin/error/error/error_page.dart @@ -126,47 +126,51 @@ class _ErrorListPageState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Scaffold( - backgroundColor: const Color(0xFFF0F4F8), - appBar: _buildAppBar(), + backgroundColor: cs.surface, + appBar: _buildAppBar(cs), body: Column( children: [ _SearchBar( controller: _phoneCtrl, onSearch: _searchByPhone, onClear: _clearSearch, + cs: cs, ), Expanded( - child: _buildBody(), + child: _buildBody(cs), ), ], ), ); } - PreferredSizeWidget _buildAppBar() { + PreferredSizeWidget _buildAppBar(ColorScheme cs) { return AppBar( elevation: 0, - backgroundColor: const Color(0xFF0F172A), - foregroundColor: Colors.white, + backgroundColor: cs.surfaceContainerHighest, + foregroundColor: cs.onSurface, centerTitle: true, title: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: cs.onSurface.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white.withOpacity(0.2)), + border: Border.all(color: cs.onSurface.withValues(alpha: 0.2)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.error_outline, color: Colors.red, size: 22), + Icon(Icons.error_outline, color: cs.error, size: 22), const SizedBox(width: 8), - const Text( + Text( "سجل الأخطاء", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, + color: cs.onSurface, ), ), ], @@ -175,11 +179,11 @@ class _ErrorListPageState extends State { ); } - Widget _buildBody() { + Widget _buildBody(ColorScheme cs) { if (_loading) { - return const Center( + return Center( child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Color(0xFF0F172A)), + valueColor: AlwaysStoppedAnimation(cs.primary), ), ); } @@ -189,13 +193,13 @@ class _ErrorListPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.error_outline, size: 64, color: Colors.red.shade300), + Icon(Icons.error_outline, size: 64, color: cs.error), const SizedBox(height: 16), Text( _errorMsg!, textAlign: TextAlign.center, style: TextStyle( - color: Colors.red.shade700, + color: cs.error, fontSize: 14, ), ), @@ -209,15 +213,15 @@ class _ErrorListPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( + const Icon( Icons.check_circle_outline, size: 64, - color: Colors.green.shade300, + color: Color(0xFF10B981), ), const SizedBox(height: 16), - const Text( + Text( 'لا توجد سجلات أخطاء', - style: TextStyle(fontSize: 16, color: Colors.grey), + style: TextStyle(fontSize: 16, color: cs.onSurfaceVariant), ), ], ), @@ -232,12 +236,12 @@ class _ErrorListPageState extends State { await _fetchLast20(); } }, - color: const Color(0xFF0F172A), + color: cs.primary, child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), itemCount: _items.length, itemBuilder: (context, index) { - return _ErrorTile(_items[index], index); + return _ErrorTile(_items[index], index, cs); }, ), ); @@ -254,11 +258,13 @@ class _SearchBar extends StatefulWidget { final TextEditingController controller; final VoidCallback onSearch; final VoidCallback onClear; + final ColorScheme cs; const _SearchBar({ required this.controller, required this.onSearch, required this.onClear, + required this.cs, }); @override @@ -270,18 +276,16 @@ class _SearchBarState extends State<_SearchBar> { @override Widget build(BuildContext context) { + final cs = widget.cs; + return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.white, Colors.grey.shade50], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.08), + color: Colors.black.withValues(alpha: 0.08), blurRadius: 16, spreadRadius: 2, ) @@ -303,15 +307,13 @@ class _SearchBarState extends State<_SearchBar> { keyboardType: TextInputType.phone, textDirection: TextDirection.rtl, onSubmitted: (_) => widget.onSearch(), - style: const TextStyle(fontSize: 15), + style: TextStyle(fontSize: 15, color: cs.onSurface), decoration: InputDecoration( hintText: 'بحث برقم الهاتف', - hintStyle: TextStyle(color: Colors.grey.shade400), + hintStyle: TextStyle(color: cs.onSurfaceVariant), prefixIcon: Icon( Icons.search, - color: _isFocused - ? const Color(0xFF0F172A) - : Colors.grey.shade400, + color: _isFocused ? cs.primary : cs.onSurfaceVariant, ), suffixIcon: widget.controller.text.isNotEmpty ? InkWell( @@ -320,28 +322,28 @@ class _SearchBarState extends State<_SearchBar> { setState(() {}); }, child: Icon(Icons.close, - color: Colors.grey.shade400, size: 20), + color: cs.onSurfaceVariant, size: 20), ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: - BorderSide(color: Colors.grey.shade300, width: 1), + BorderSide(color: cs.outline, width: 1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: - BorderSide(color: Colors.grey.shade300, width: 1), + BorderSide(color: cs.outline, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Color(0xFF0F172A), + borderSide: BorderSide( + color: cs.primary, width: 2, ), ), filled: true, - fillColor: Colors.white, + fillColor: cs.surfaceContainerHighest, contentPadding: const EdgeInsets.symmetric(vertical: 12), ), onChanged: (_) => setState(() {}), @@ -352,8 +354,8 @@ class _SearchBarState extends State<_SearchBar> { ElevatedButton( onPressed: widget.onSearch, style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF0F172A), - foregroundColor: Colors.white, + backgroundColor: cs.primary, + foregroundColor: cs.onPrimary, padding: const EdgeInsets.symmetric(horizontal: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -372,9 +374,9 @@ class _SearchBarState extends State<_SearchBar> { OutlinedButton( onPressed: widget.onClear, style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFF0F172A), - side: const BorderSide( - color: Color(0xFF0F172A), + foregroundColor: cs.primary, + side: BorderSide( + color: cs.primary, width: 1.5, ), shape: RoundedRectangleBorder( @@ -395,12 +397,12 @@ class _SearchBarState extends State<_SearchBar> { class _ErrorTile extends StatelessWidget { final ErrorLog item; final int index; + final ColorScheme cs; - const _ErrorTile(this.item, this.index); + const _ErrorTile(this.item, this.index, this.cs); @override Widget build(BuildContext context) { - // تحديد الألوان والأيقونات بناءً على نوع المستخدم final isDriver = item.userType.toLowerCase().contains('driver') || item.userType.toLowerCase().contains('سائق'); @@ -410,22 +412,18 @@ class _ErrorTile extends StatelessWidget { final userTypeLabel = isDriver ? "سائق" : "راكب"; final userTypeBgColor = isDriver - ? const Color(0xFF10B981).withOpacity(0.1) - : const Color(0xFFF59E0B).withOpacity(0.1); + ? const Color(0xFF10B981).withValues(alpha: 0.1) + : const Color(0xFFF59E0B).withValues(alpha: 0.1); return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.white, Colors.grey.shade50], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.grey.shade200, width: 1), + border: Border.all(color: cs.outline, width: 1), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.06), + color: Colors.black.withValues(alpha: 0.06), blurRadius: 12, spreadRadius: 1, ) @@ -435,7 +433,6 @@ class _ErrorTile extends StatelessWidget { borderRadius: BorderRadius.circular(16), child: Stack( children: [ - // خط علوي ملون Positioned( top: 0, left: 0, @@ -446,7 +443,7 @@ class _ErrorTile extends StatelessWidget { gradient: LinearGradient( colors: [ const Color(0xFFEF4444), - Colors.red.shade400, + cs.error, ], begin: Alignment.centerLeft, end: Alignment.centerRight, @@ -459,7 +456,6 @@ class _ErrorTile extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - // الصف الأول: رقم الخطأ ونوع المستخدم Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -493,7 +489,7 @@ class _ErrorTile extends StatelessWidget { '#${item.id}', style: TextStyle( fontSize: 12, - color: Colors.grey.shade500, + color: cs.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), @@ -501,14 +497,13 @@ class _ErrorTile extends StatelessWidget { ), const SizedBox(height: 12), - // عنوان الخطأ (قابل للنسخ) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.red.shade50, + color: cs.error.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8), border: Border.all( - color: Colors.red.shade200, + color: cs.error.withValues(alpha: 0.3), width: 1, ), ), @@ -516,7 +511,7 @@ class _ErrorTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.warning_amber_rounded, - size: 18, color: Colors.red.shade600), + size: 18, color: cs.error), const SizedBox(width: 8), Expanded( child: SelectableText( @@ -524,7 +519,7 @@ class _ErrorTile extends StatelessWidget { style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, - color: Colors.red.shade900, + color: cs.error, height: 1.4, ), ), @@ -534,15 +529,14 @@ class _ErrorTile extends StatelessWidget { ), const SizedBox(height: 12), - // التفاصيل (إن وجدت) if (item.details.isNotEmpty) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.grey.shade50, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), border: Border.all( - color: Colors.grey.shade200, + color: cs.outline, width: 1, ), ), @@ -550,14 +544,14 @@ class _ErrorTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.info_outline, - size: 16, color: Colors.grey.shade600), + size: 16, color: cs.onSurfaceVariant), const SizedBox(width: 8), Expanded( child: SelectableText( item.details, style: TextStyle( fontSize: 12, - color: Colors.grey.shade700, + color: cs.onSurfaceVariant, height: 1.4, ), ), @@ -568,7 +562,6 @@ class _ErrorTile extends StatelessWidget { const SizedBox(height: 12), ], - // معلومات تقنية Wrap( spacing: 6, runSpacing: 6, @@ -578,25 +571,29 @@ class _ErrorTile extends StatelessWidget { icon: Icons.phone, label: 'الهاتف', value: item.phone, - color: Colors.blue, + color: cs.primary, + cs: cs, ), _buildInfoBadge( icon: Icons.person_outline, label: 'المعرف', value: item.userId, color: Colors.purple, + cs: cs, ), _buildInfoBadge( icon: Icons.devices, label: 'Path', value: item.device, color: Colors.orange, + cs: cs, ), _buildInfoBadge( icon: Icons.schedule, label: 'التاريخ', value: item.createdAt, color: Colors.teal, + cs: cs, ), ], ), @@ -614,13 +611,14 @@ class _ErrorTile extends StatelessWidget { required String label, required String value, required Color color, + required ColorScheme cs, }) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( - color: color.withOpacity(0.08), + color: color.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8), - border: Border.all(color: color.withOpacity(0.3), width: 1), + border: Border.all(color: color.withValues(alpha: 0.3), width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -635,7 +633,7 @@ class _ErrorTile extends StatelessWidget { text: "$label: ", style: TextStyle( fontSize: 10, - color: color.withOpacity(0.7), + color: color.withValues(alpha: 0.7), fontWeight: FontWeight.w500, ), ), diff --git a/siro_admin/lib/views/admin/financial/financial_v2_page.dart b/siro_admin/lib/views/admin/financial/financial_v2_page.dart index a505bd21..5b1e12dd 100644 --- a/siro_admin/lib/views/admin/financial/financial_v2_page.dart +++ b/siro_admin/lib/views/admin/financial/financial_v2_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/colors.dart'; import 'package:siro_admin/controller/admin/financial_v2_controller.dart'; class FinancialV2Page extends StatelessWidget { @@ -8,19 +7,21 @@ class FinancialV2Page extends StatelessWidget { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; final controller = Get.put(FinancialV2Controller()); return Scaffold( - backgroundColor: AppColor.bg, + backgroundColor: cs.surface, appBar: AppBar( - title: const Text('الإدارة المالية المتقدمة', - style: TextStyle(fontWeight: FontWeight.bold)), - backgroundColor: AppColor.surface, + title: Text('الإدارة المالية المتقدمة', + style: TextStyle(fontWeight: FontWeight.bold, color: cs.onSurface)), + backgroundColor: cs.surface, elevation: 0, centerTitle: true, + iconTheme: IconThemeData(color: cs.onSurface), actions: [ IconButton( - icon: const Icon(Icons.refresh_rounded), + icon: Icon(Icons.refresh_rounded, color: cs.onSurface), onPressed: () => controller.fetchAllFinancials(), ) ], @@ -28,8 +29,8 @@ class FinancialV2Page extends StatelessWidget { body: GetBuilder( builder: (ctrl) { if (ctrl.isLoading) { - return const Center( - child: CircularProgressIndicator(color: AppColor.accent)); + return Center( + child: CircularProgressIndicator(color: cs.primary)); } return SingleChildScrollView( @@ -37,13 +38,13 @@ class FinancialV2Page extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildMainFinancialStats(ctrl.stats), + _buildMainFinancialStats(ctrl.stats, cs), const SizedBox(height: 24), - _buildSectionTitle('طرق الدفع'), - _buildPaymentMethodBreakdown(ctrl.stats), + _buildSectionTitle('طرق الدفع', cs), + _buildPaymentMethodBreakdown(ctrl.stats, cs), const SizedBox(height: 32), - _buildSectionTitle('تسويات الكباتن (مستحقات معلقة)'), - _buildSettlementsList(ctrl.settlements), + _buildSectionTitle('تسويات الكباتن (مستحقات معلقة)', cs), + _buildSettlementsList(ctrl.settlements, cs), const SizedBox(height: 40), ], ), @@ -53,13 +54,13 @@ class FinancialV2Page extends StatelessWidget { ); } - Widget _buildSectionTitle(String title) { + Widget _buildSectionTitle(String title, ColorScheme cs) { return Padding( padding: const EdgeInsets.only(bottom: 16), child: Text( title, - style: const TextStyle( - color: AppColor.textPrimary, + style: TextStyle( + color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.bold, ), @@ -67,14 +68,15 @@ class FinancialV2Page extends StatelessWidget { ); } - Widget _buildMainFinancialStats(Map stats) { + Widget _buildMainFinancialStats(Map stats, ColorScheme cs) { return Column( children: [ _buildFinancialCard( 'إجمالي عمولة المنصة', '${stats['total_platform_commission'] ?? 0} ج.م', Icons.account_balance_rounded, - AppColor.accent, + cs.primary, + cs, ), const SizedBox(height: 12), Row( @@ -84,7 +86,8 @@ class FinancialV2Page extends StatelessWidget { 'إجمالي دخل الكباتن', '${stats['total_driver_pay'] ?? 0}', Icons.person_pin_rounded, - AppColor.info, + cs.tertiary, + cs, isSmall: true, ), ), @@ -94,7 +97,8 @@ class FinancialV2Page extends StatelessWidget { 'إجمالي الإيرادات', '${stats['total_revenue'] ?? 0}', Icons.payments_rounded, - AppColor.success, + const Color(0xFF10B981), + cs, isSmall: true, ), ), @@ -105,18 +109,18 @@ class FinancialV2Page extends StatelessWidget { } Widget _buildFinancialCard( - String title, String value, IconData icon, Color color, + String title, String value, IconData icon, Color color, ColorScheme cs, {bool isSmall = false}) { return Container( padding: EdgeInsets.all(isSmall ? 16 : 24), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withOpacity(0.2)), + border: Border.all(color: color.withValues(alpha: 0.2)), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [AppColor.surface, color.withOpacity(0.05)], + colors: [cs.surfaceContainerHighest, color.withValues(alpha: 0.05)], ), ), child: Row( @@ -124,7 +128,7 @@ class FinancialV2Page extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: color.withOpacity(0.1), + color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(14), ), child: Icon(icon, color: color, size: isSmall ? 20 : 28), @@ -135,12 +139,12 @@ class FinancialV2Page extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 12)), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(height: 4), Text(value, style: TextStyle( - color: AppColor.textPrimary, + color: cs.onSurface, fontSize: isSmall ? 18 : 24, fontWeight: FontWeight.bold)), ], @@ -151,7 +155,7 @@ class FinancialV2Page extends StatelessWidget { ); } - Widget _buildPaymentMethodBreakdown(Map stats) { + Widget _buildPaymentMethodBreakdown(Map stats, ColorScheme cs) { double cash = double.tryParse(stats['cash_payments'].toString()) ?? 0; double digital = double.tryParse(stats['digital_payments'].toString()) ?? 0; double total = cash + digital; @@ -160,21 +164,21 @@ class FinancialV2Page extends StatelessWidget { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), child: Column( children: [ - _buildPaymentBar('نقدي (Cash)', cash, total, AppColor.warning), + _buildPaymentBar('نقدي (Cash)', cash, total, cs.tertiary, cs), const SizedBox(height: 16), - _buildPaymentBar('إلكتروني / محفظة', digital, total, AppColor.info), + _buildPaymentBar('إلكتروني / محفظة', digital, total, cs.tertiary, cs), ], ), ); } Widget _buildPaymentBar( - String label, double value, double total, Color color) { + String label, double value, double total, Color color, ColorScheme cs) { double percent = value / total; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -183,8 +187,7 @@ class FinancialV2Page extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, - style: - const TextStyle(color: AppColor.textPrimary, fontSize: 13)), + style: TextStyle(color: cs.onSurface, fontSize: 13)), Text('${value.toStringAsFixed(0)} ج.م', style: TextStyle(color: color, fontWeight: FontWeight.bold)), ], @@ -194,7 +197,7 @@ class FinancialV2Page extends StatelessWidget { borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: percent, - backgroundColor: AppColor.divider, + backgroundColor: cs.outline, color: color, minHeight: 8, ), @@ -203,9 +206,11 @@ class FinancialV2Page extends StatelessWidget { ); } - Widget _buildSettlementsList(List settlements) { + Widget _buildSettlementsList(List settlements, ColorScheme cs) { if (settlements.isEmpty) { - return const Center(child: Text('لا توجد تسويات معلقة')); + return Center( + child: Text('لا توجد تسويات معلقة', + style: TextStyle(color: cs.onSurfaceVariant))); } return ListView.builder( @@ -218,9 +223,9 @@ class FinancialV2Page extends StatelessWidget { margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColor.divider), + border: Border.all(color: cs.outline), ), child: Row( children: [ @@ -229,36 +234,36 @@ class FinancialV2Page extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('${s['first_name']} ${s['last_name']}', - style: const TextStyle( - color: AppColor.textPrimary, + style: TextStyle( + color: cs.onSurface, fontWeight: FontWeight.bold)), Text(s['phone'] ?? '', - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 12)), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(height: 4), Text('${s['total_rides']} رحلة مكتملة', - style: const TextStyle( - color: AppColor.info, fontSize: 11)), + style: TextStyle( + color: cs.tertiary, fontSize: 11)), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - const Text('المستحقات', + Text('المستحقات', style: TextStyle( - color: AppColor.textSecondary, fontSize: 10)), + color: cs.onSurfaceVariant, fontSize: 10)), Text('${s['total_earned']} ج.م', - style: const TextStyle( - color: AppColor.accent, + style: TextStyle( + color: cs.primary, fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 4), ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( - backgroundColor: AppColor.accent.withOpacity(0.1), - foregroundColor: AppColor.accent, + backgroundColor: cs.primary.withValues(alpha: 0.1), + foregroundColor: cs.primary, elevation: 0, minimumSize: const Size(80, 32), shape: RoundedRectangleBorder( diff --git a/siro_admin/lib/views/admin/marketing/heatmap_page.dart b/siro_admin/lib/views/admin/marketing/heatmap_page.dart index b8fb74d2..3364126d 100644 --- a/siro_admin/lib/views/admin/marketing/heatmap_page.dart +++ b/siro_admin/lib/views/admin/marketing/heatmap_page.dart @@ -15,16 +15,14 @@ class _HeatmapPageState extends State { List _markers = []; bool _isLoading = true; - // ─── فلاتر ─── - String _selectedSource = 'all'; // all | geofence | app_usage | silent_push - String _selectedCountry = 'all'; // all | JO | SY | EG | IQ + String _selectedSource = 'all'; + String _selectedCountry = 'all'; int _daysFilter = 7; final MapController _mapController = MapController(); - // مراكز كل دولة للانتقال السريع final Map _countryCenters = { - 'all': const LatLng(31.9522, 35.9334), // عمان + 'all': const LatLng(31.9522, 35.9334), 'JO': const LatLng(31.9522, 35.9334), 'SY': const LatLng(33.5138, 36.2765), 'EG': const LatLng(30.0444, 31.2357), @@ -60,18 +58,17 @@ class _HeatmapPageState extends State { final lng = double.tryParse(point['longitude'].toString()) ?? 0.0; final source = point['source'].toString(); - // ألوان حسب المصدر Color markerColor; switch (source) { case 'geofence': - markerColor = Colors.green.withOpacity(0.65); // 🟢 دخل منطقة + markerColor = Colors.green.withValues(alpha: 0.65); break; case 'silent_push': - markerColor = Colors.orange.withOpacity(0.55); // 🟠 إيقاظ صامت + markerColor = Colors.orange.withValues(alpha: 0.55); break; case 'app_usage': default: - markerColor = Colors.blue.withOpacity(0.5); // 🔵 فتح عادي + markerColor = Colors.blue.withValues(alpha: 0.5); } return CircleMarker( @@ -93,22 +90,23 @@ class _HeatmapPageState extends State { void _applyFilter() { _fetchHeatmapData(); - // تحريك الخريطة لمركز الدولة المختارة final center = _countryCenters[_selectedCountry] ?? _countryCenters['all']!; _mapController.move(center, 11.0); } @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Scaffold( - backgroundColor: const Color(0xFF0F0F1A), + backgroundColor: cs.surface, appBar: AppBar( title: const Text( 'خريطة النشاط الحرارية', style: TextStyle(fontWeight: FontWeight.w700, fontSize: 15), ), - backgroundColor: const Color(0xFF1A1A2E), - foregroundColor: Colors.white, + backgroundColor: cs.surface, + foregroundColor: cs.onSurface, centerTitle: true, actions: [ IconButton( @@ -120,14 +118,13 @@ class _HeatmapPageState extends State { ), body: Column( children: [ - // ─── شريط الفلاتر ─── Container( - color: const Color(0xFF1A1A2E), + color: cs.surfaceContainerHighest, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Column( children: [ - // فلتر المصدر _buildFilterRow( + cs: cs, label: 'المصدر:', options: const { 'all': 'الكل', @@ -139,8 +136,8 @@ class _HeatmapPageState extends State { onChanged: (v) => setState(() => _selectedSource = v), ), const SizedBox(height: 6), - // فلتر الدولة _buildFilterRow( + cs: cs, label: 'الدولة:', options: const { 'all': 'الكل', @@ -153,10 +150,9 @@ class _HeatmapPageState extends State { onChanged: (v) => setState(() => _selectedCountry = v), ), const SizedBox(height: 6), - // فلتر الفترة الزمنية + زر تطبيق Row( children: [ - const Text('الفترة:', style: TextStyle(color: Colors.white54, fontSize: 11, fontWeight: FontWeight.bold)), + Text('الفترة:', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.bold)), const SizedBox(width: 8), ...[1, 7, 30].map((days) => Padding( padding: const EdgeInsets.only(right: 6), @@ -166,19 +162,19 @@ class _HeatmapPageState extends State { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: _daysFilter == days - ? const Color(0xFF6366F1) - : const Color(0xFF2D2D42), + ? cs.primary + : cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), border: Border.all( color: _daysFilter == days - ? const Color(0xFF818CF8) + ? cs.primary : Colors.transparent, ), ), child: Text( '$days يوم', style: TextStyle( - color: _daysFilter == days ? Colors.white : Colors.white54, + color: _daysFilter == days ? cs.onSurface : cs.onSurfaceVariant, fontSize: 10, fontWeight: FontWeight.bold, ), @@ -192,8 +188,8 @@ class _HeatmapPageState extends State { icon: const Icon(Icons.filter_alt_rounded, size: 14), label: const Text('تطبيق', style: TextStyle(fontSize: 11)), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF6366F1), - foregroundColor: Colors.white, + backgroundColor: cs.primary, + foregroundColor: cs.onPrimary, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), ), @@ -204,18 +200,17 @@ class _HeatmapPageState extends State { ), ), - // ─── الخريطة ─── Expanded( child: Stack( children: [ _isLoading - ? const Center( + ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - CircularProgressIndicator(color: Color(0xFF6366F1)), - SizedBox(height: 12), - Text('جاري تحميل البيانات...', style: TextStyle(color: Colors.white54, fontSize: 12)), + CircularProgressIndicator(color: cs.primary), + const SizedBox(height: 12), + Text('جاري تحميل البيانات...', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), ], ), ) @@ -234,44 +229,42 @@ class _HeatmapPageState extends State { ], ), - // ─── مفتاح الألوان ─── Positioned( bottom: 12, right: 12, child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: const Color(0xFF1A1A2E).withOpacity(0.9), + color: cs.surfaceContainerHighest.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(10), - border: Border.all(color: Colors.white12), + border: Border.all(color: cs.outline.withValues(alpha: 0.3)), ), - child: const Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _LegendItem(color: Colors.green, label: 'دخل منطقة سياج'), - SizedBox(height: 4), - _LegendItem(color: Colors.blue, label: 'فتح عادي للتطبيق'), - SizedBox(height: 4), - _LegendItem(color: Colors.orange, label: 'إيقاظ صامت'), + _LegendItem(color: Colors.green, label: 'دخل منطقة سياج', cs: cs), + const SizedBox(height: 4), + _LegendItem(color: Colors.blue, label: 'فتح عادي للتطبيق', cs: cs), + const SizedBox(height: 4), + _LegendItem(color: Colors.orange, label: 'إيقاظ صامت', cs: cs), ], ), ), ), - // ─── عدد النقاط ─── Positioned( top: 12, left: 12, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( - color: const Color(0xFF1A1A2E).withOpacity(0.9), + color: cs.surfaceContainerHighest.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.white12), + border: Border.all(color: cs.outline.withValues(alpha: 0.3)), ), child: Text( '${_markers.length} نقطة', - style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold), + style: TextStyle(color: cs.onSurface, fontSize: 11, fontWeight: FontWeight.bold), ), ), ), @@ -284,6 +277,7 @@ class _HeatmapPageState extends State { } Widget _buildFilterRow({ + required ColorScheme cs, required String label, required Map options, required String selected, @@ -291,7 +285,7 @@ class _HeatmapPageState extends State { }) { return Row( children: [ - Text(label, style: const TextStyle(color: Colors.white54, fontSize: 11, fontWeight: FontWeight.bold)), + Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.bold)), const SizedBox(width: 8), Expanded( child: SingleChildScrollView( @@ -306,16 +300,16 @@ class _HeatmapPageState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: isSelected ? const Color(0xFF6366F1) : const Color(0xFF2D2D42), + color: isSelected ? cs.primary : cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), border: Border.all( - color: isSelected ? const Color(0xFF818CF8) : Colors.transparent, + color: isSelected ? cs.primary : Colors.transparent, ), ), child: Text( e.value, style: TextStyle( - color: isSelected ? Colors.white : Colors.white54, + color: isSelected ? cs.onSurface : cs.onSurfaceVariant, fontSize: 10, fontWeight: FontWeight.bold, ), @@ -335,7 +329,8 @@ class _HeatmapPageState extends State { class _LegendItem extends StatelessWidget { final Color color; final String label; - const _LegendItem({required this.color, required this.label}); + final ColorScheme cs; + const _LegendItem({required this.color, required this.label, required this.cs}); @override Widget build(BuildContext context) { @@ -344,7 +339,7 @@ class _LegendItem extends StatelessWidget { children: [ Container(width: 10, height: 10, decoration: BoxDecoration(color: color, shape: BoxShape.circle)), const SizedBox(width: 6), - Text(label, style: const TextStyle(color: Colors.white70, fontSize: 10)), + Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)), ], ); } diff --git a/siro_admin/lib/views/admin/marketing/marketing_page.dart b/siro_admin/lib/views/admin/marketing/marketing_page.dart index 92248e38..6e279c56 100644 --- a/siro_admin/lib/views/admin/marketing/marketing_page.dart +++ b/siro_admin/lib/views/admin/marketing/marketing_page.dart @@ -8,21 +8,11 @@ import '../../../controller/admin/marketing_controller.dart'; class MarketingPage extends StatelessWidget { const MarketingPage({super.key}); - static const Color _bg = AppColor.bg; - static const Color _surface = AppColor.surface; - static const Color _accent = AppColor.accent; - static const Color _danger = AppColor.danger; - static const Color _success = AppColor.success; - static const Color _info = AppColor.info; - static const Color _textPrimary = AppColor.textPrimary; - static const Color _textSecondary = AppColor.textSecondary; - static const Color _divider = AppColor.divider; - @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; final controller = Get.put(MarketingController()); - // Initial load controller.fetchAnomalies(); controller.fetchCampaignsLog(); controller.fetchTelemetry(); @@ -34,26 +24,26 @@ class MarketingPage extends StatelessWidget { return DefaultTabController( length: 3, child: Scaffold( - backgroundColor: _bg, + backgroundColor: cs.surface, appBar: AppBar( - title: const Text( + title: Text( 'أتمتة التسويق الذكي (Siro AI Marketing)', - style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16), + style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: cs.onSurface), ), - backgroundColor: _bg, + backgroundColor: cs.surface, elevation: 0, - foregroundColor: _textPrimary, + foregroundColor: cs.onSurface, bottom: PreferredSize( preferredSize: const Size.fromHeight(98), child: Column( children: [ - _buildCountrySelector(c), - const TabBar( - indicatorColor: _accent, - labelColor: _accent, - unselectedLabelColor: _textSecondary, - labelStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), - tabs: [ + _buildCountrySelector(c, cs), + TabBar( + indicatorColor: cs.primary, + labelColor: cs.primary, + unselectedLabelColor: cs.onSurfaceVariant, + labelStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12), + tabs: const [ Tab(icon: Icon(Icons.analytics_outlined, size: 20), text: 'شواذ الأسعار والمنافسين'), Tab(icon: Icon(Icons.campaign_outlined, size: 20), text: 'سجل الحملات المنجزة'), Tab(icon: Icon(Icons.settings_outlined, size: 20), text: 'إعدادات الأتمتة والتحكم'), @@ -65,9 +55,9 @@ class MarketingPage extends StatelessWidget { ), body: TabBarView( children: [ - _buildAnomaliesTab(context, c), - _buildLogsTab(context, c), - _buildConfigTab(context, c), + _buildAnomaliesTab(context, c, cs), + _buildLogsTab(context, c, cs), + _buildConfigTab(context, c, cs), ], ), ), @@ -76,7 +66,7 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildCountrySelector(MarketingController c) { + Widget _buildCountrySelector(MarketingController c, ColorScheme cs) { final countries = { 'All': 'الكل', 'SY': 'سوريا 🇸🇾', @@ -98,18 +88,18 @@ class MarketingPage extends StatelessWidget { label: Text( entry.value, style: TextStyle( - color: isSelected ? Colors.white : _textSecondary, + color: isSelected ? cs.onSurface : cs.onSurfaceVariant, fontWeight: FontWeight.bold, fontSize: 11, ), ), selected: isSelected, onSelected: (_) => c.changeCountry(entry.key), - selectedColor: _accent, - backgroundColor: _surface, + selectedColor: cs.primary, + backgroundColor: cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), - side: BorderSide(color: isSelected ? _accent : _divider), + side: BorderSide(color: isSelected ? cs.primary : cs.outline), ), ), ); @@ -118,18 +108,18 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildAnomaliesTab(BuildContext context, MarketingController c) { + Widget _buildAnomaliesTab(BuildContext context, MarketingController c, ColorScheme cs) { return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _buildAIControlCard(context, c), - _buildAIPredictionCard(context, c), - _buildWhatIfSimulatorCard(context, c), - _buildMarketShareChart(context, c), - _buildComparisonChart(), - _buildPCIHeatmapSection(), + _buildAIControlCard(context, c, cs), + _buildAIPredictionCard(context, c, cs), + _buildWhatIfSimulatorCard(context, c, cs), + _buildMarketShareChart(context, c, cs), + _buildComparisonChart(c, cs), + _buildPCIHeatmapSection(c, cs), Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 8), child: Row( @@ -137,26 +127,26 @@ class MarketingPage extends StatelessWidget { Container( width: 3, height: 14, - decoration: BoxDecoration(color: _accent, borderRadius: BorderRadius.circular(2)), + decoration: BoxDecoration(color: cs.primary, borderRadius: BorderRadius.circular(2)), ), const SizedBox(width: 8), - const Text( + Text( 'أحدث مقارنات أسعار المنافسين والفرص السوقية', - style: TextStyle(color: _textSecondary, fontSize: 12, fontWeight: FontWeight.bold), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12, fontWeight: FontWeight.bold), ), ], ), ), if (c.isLoading && c.priceAnomalies.isEmpty) - const SizedBox( + SizedBox( height: 200, - child: Center(child: CircularProgressIndicator(color: _accent)), + child: Center(child: CircularProgressIndicator(color: cs.primary)), ) else if (c.priceAnomalies.isEmpty) - const SizedBox( + SizedBox( height: 120, child: Center( - child: Text('لا توجد شواذ سعرية مسجلة حالياً.', style: TextStyle(color: _textSecondary)), + child: Text('لا توجد شواذ سعرية مسجلة حالياً.', style: TextStyle(color: cs.onSurfaceVariant)), ), ) else @@ -174,10 +164,10 @@ class MarketingPage extends StatelessWidget { verticalOffset: 20.0, child: FadeInAnimation( child: Card( - color: _surface, + color: cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: _divider), + side: BorderSide(color: cs.outline), ), margin: const EdgeInsets.only(bottom: 12), child: Padding( @@ -191,49 +181,50 @@ class MarketingPage extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: _accent.withOpacity(0.1), + color: cs.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Text( anomaly['competitor_name'] ?? "منافس غير معروف", - style: const TextStyle(color: _accent, fontWeight: FontWeight.bold, fontSize: 11), + style: TextStyle(color: cs.primary, fontWeight: FontWeight.bold, fontSize: 11), ), ), Text( anomaly['country_code'] ?? "", - style: const TextStyle(color: _textSecondary, fontSize: 11), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11), ), ], ), const SizedBox(height: 12), Row( children: [ - _buildStatItem('سعرنا', '${anomaly['our_price']}', _success), + _buildStatItem('سعرنا', '${anomaly['our_price']}', const Color(0xFF10B981), cs), const SizedBox(width: 24), - _buildStatItem('سعر المنافس', '${anomaly['competitor_price']}', _danger), + _buildStatItem('سعر المنافس', '${anomaly['competitor_price']}', cs.error, cs), const SizedBox(width: 24), _buildStatItem( 'الفارق', ((double.tryParse(anomaly['competitor_price']?.toString() ?? '0') ?? 0) - (double.tryParse(anomaly['our_price']?.toString() ?? '0') ?? 0)).toStringAsFixed(2), - _info, + cs.tertiary, + cs, ), ], ), const SizedBox(height: 12), - Divider(color: _divider, height: 1), + Divider(color: cs.outline, height: 1), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'تاريخ الرصد: ${anomaly['detected_at'] ?? ""}', - style: const TextStyle(color: _textSecondary, fontSize: 10), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10), ), if (anomaly['anomaly_type'] != null) Text( anomaly['anomaly_type'].toString(), style: TextStyle( - color: anomaly['anomaly_type'].toString().contains('high') ? _danger : _info, + color: anomaly['anomaly_type'].toString().contains('high') ? cs.error : cs.tertiary, fontSize: 10, fontWeight: FontWeight.bold, ), @@ -254,236 +245,228 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildComparisonChart() { - return GetBuilder( - builder: (c) { - final hourlyData = c.hourlyPriceData; - final siroBase = c.siroBasePrices; - final double siroSpeedPrice = (siroBase['speedPrice'] != null) - ? double.tryParse(siroBase['speedPrice'].toString()) ?? 0.0 - : 0.0; + Widget _buildComparisonChart(MarketingController c, ColorScheme cs) { + final hourlyData = c.hourlyPriceData; + final siroBase = c.siroBasePrices; + final double siroSpeedPrice = (siroBase['speedPrice'] != null) + ? double.tryParse(siroBase['speedPrice'].toString()) ?? 0.0 + : 0.0; - List competitorSpots = []; - if (hourlyData.isNotEmpty) { - for (int i = 0; i < hourlyData.length && i < 24; i++) { - final val = hourlyData[i]; - final double avg = double.tryParse((val['avg_price_per_km'] ?? 0).toString()) ?? 0.0; - competitorSpots.add(FlSpot(i.toDouble(), avg)); - } - } - if (competitorSpots.isEmpty) { - competitorSpots = const [FlSpot(0, 0), FlSpot(1, 0)]; - } + List competitorSpots = []; + if (hourlyData.isNotEmpty) { + for (int i = 0; i < hourlyData.length && i < 24; i++) { + final val = hourlyData[i]; + final double avg = double.tryParse((val['avg_price_per_km'] ?? 0).toString()) ?? 0.0; + competitorSpots.add(FlSpot(i.toDouble(), avg)); + } + } + if (competitorSpots.isEmpty) { + competitorSpots = const [FlSpot(0, 0), FlSpot(1, 0)]; + } - final double maxY = competitorSpots.fold(0, (p, s) => s.y > p ? s.y : p); - final double chartMaxY = maxY > 0 ? maxY * 1.3 : 10; + final double maxY = competitorSpots.fold(0, (p, s) => s.y > p ? s.y : p); + final double chartMaxY = maxY > 0 ? maxY * 1.3 : 10; - List siroSpots = []; - if (siroSpeedPrice > 0) { - for (int i = 0; i < hourlyData.length; i++) { - siroSpots.add(FlSpot(i.toDouble(), siroSpeedPrice)); - } - } + List siroSpots = []; + if (siroSpeedPrice > 0) { + for (int i = 0; i < hourlyData.length; i++) { + siroSpots.add(FlSpot(i.toDouble(), siroSpeedPrice)); + } + } - return Card( - color: _surface, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: _divider), - ), - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'مقارنة تقلبات الأسعار اللحظية (ساعة/ريال)', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _textPrimary), - ), - const SizedBox(height: 4), - Text( - hourlyData.isNotEmpty - ? 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر ${hourlyData.length} ساعة' - : 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر 24 ساعة', - style: const TextStyle(fontSize: 10, color: _textSecondary), - ), - const SizedBox(height: 24), - SizedBox( - height: 160, - child: LineChart( - LineChartData( - gridData: const FlGridData(show: false), - titlesData: FlTitlesData( - show: true, - rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 30, - getTitlesWidget: (value, meta) { - if (value == 0 || value == chartMaxY / 2 || value == chartMaxY) { - return Text('${value.toInt()}', style: const TextStyle(color: _textSecondary, fontSize: 9)); - } - return const SizedBox.shrink(); - }, - ), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 22, - getTitlesWidget: (value, meta) { - final idx = value.toInt(); - if (idx % 4 == 0 && idx < 24) { - return Text('${idx}h', style: const TextStyle(color: _textSecondary, fontSize: 8)); - } - return const SizedBox.shrink(); - }, - ), - ), - ), - borderData: FlBorderData(show: false), - minX: 0, - maxX: (hourlyData.length - 1).toDouble(), - minY: 0, - maxY: chartMaxY, - lineBarsData: [ - if (siroSpots.length > 1) - LineChartBarData( - spots: siroSpots, - isCurved: true, - color: _accent, - barWidth: 3, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData( - show: true, - color: _accent.withOpacity(0.1), - ), - ), - LineChartBarData( - spots: competitorSpots, - isCurved: true, - color: _danger, - barWidth: 2, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData( - show: true, - color: _danger.withOpacity(0.05), - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - }, - ); - } - - Widget _buildPCIHeatmapSection() { - return GetBuilder( - builder: (c) { - final heatmapList = c.heatmapData; - List> regions = []; - if (heatmapList.isNotEmpty) { - for (final item in heatmapList) { - final double pci = double.tryParse(item['pci'].toString()) ?? 1.0; - final pct = ((1 - pci) * 100).abs().toStringAsFixed(1); - final isCheaper = pci < 1; - regions.add({ - 'name': 'شبكة (${item['lat']}, ${item['lng']})', - 'pci': pci, - 'weight': item['weight'] ?? 0.0, - 'desc': isCheaper ? 'أرخص بـ $pct%' : 'أغلى بـ $pct%', - 'samples': item['sample_size'] ?? 0, - }); - } - } - - if (regions.isEmpty) { - regions = [ - {'name': 'لا توجد بيانات حرارية كافية', 'pci': 1.0, 'weight': 0.0, 'desc': 'يتم جمع البيانات حالياً', 'samples': 0}, - ]; - } - - return Card( - color: _surface, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: _divider), - ), - margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'خارطة الفجوات ومؤشر التنافسية السعري (PCI)', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _textPrimary), - ), - const SizedBox(height: 12), - ...regions.map((region) { - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text(region['name'] as String, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: _textPrimary)), - ), - const SizedBox(width: 8), - Text(region['desc'] as String, style: TextStyle(fontSize: 10, color: (region['pci'] as double) < 1 ? _success : _danger)), - ], - ), - const SizedBox(height: 6), - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: LinearProgressIndicator( - value: ((region['weight'] as double) + 1) / 2, // Map -1..1 to 0..1 - backgroundColor: _success.withOpacity(0.3), - color: (region['pci'] as double) < 1 ? _success : _danger, - minHeight: 6, - ), - ), - ], - ), - ); - }), - ], - ), - ), - ); - }, - ); - } - - Widget _buildWhatIfSimulatorCard(BuildContext context, MarketingController c) { - final TextEditingController priceCtrl = TextEditingController(); return Card( - color: _surface, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: const BorderSide(color: _divider)), + color: cs.surfaceContainerHighest, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: cs.outline), + ), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Row( + Text( + 'مقارنة تقلبات الأسعار اللحظية (ساعة/ريال)', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: cs.onSurface), + ), + const SizedBox(height: 4), + Text( + hourlyData.isNotEmpty + ? 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر ${hourlyData.length} ساعة' + : 'مقارنة أسعار Siro مقابل متوسط المنافسين لآخر 24 ساعة', + style: TextStyle(fontSize: 10, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 24), + SizedBox( + height: 160, + child: LineChart( + LineChartData( + gridData: const FlGridData(show: false), + titlesData: FlTitlesData( + show: true, + rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + getTitlesWidget: (value, meta) { + if (value == 0 || value == chartMaxY / 2 || value == chartMaxY) { + return Text('${value.toInt()}', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 9)); + } + return const SizedBox.shrink(); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 22, + getTitlesWidget: (value, meta) { + final idx = value.toInt(); + if (idx % 4 == 0 && idx < 24) { + return Text('${idx}h', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 8)); + } + return const SizedBox.shrink(); + }, + ), + ), + ), + borderData: FlBorderData(show: false), + minX: 0, + maxX: (hourlyData.length - 1).toDouble(), + minY: 0, + maxY: chartMaxY, + lineBarsData: [ + if (siroSpots.length > 1) + LineChartBarData( + spots: siroSpots, + isCurved: true, + color: cs.primary, + barWidth: 3, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + color: cs.primary.withValues(alpha: 0.1), + ), + ), + LineChartBarData( + spots: competitorSpots, + isCurved: true, + color: cs.error, + barWidth: 2, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + color: cs.error.withValues(alpha: 0.05), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildPCIHeatmapSection(MarketingController c, ColorScheme cs) { + final heatmapList = c.heatmapData; + List> regions = []; + if (heatmapList.isNotEmpty) { + for (final item in heatmapList) { + final double pci = double.tryParse(item['pci'].toString()) ?? 1.0; + final pct = ((1 - pci) * 100).abs().toStringAsFixed(1); + final isCheaper = pci < 1; + regions.add({ + 'name': 'شبكة (${item['lat']}, ${item['lng']})', + 'pci': pci, + 'weight': item['weight'] ?? 0.0, + 'desc': isCheaper ? 'أرخص بـ $pct%' : 'أغلى بـ $pct%', + 'samples': item['sample_size'] ?? 0, + }); + } + } + + if (regions.isEmpty) { + regions = [ + {'name': 'لا توجد بيانات حرارية كافية', 'pci': 1.0, 'weight': 0.0, 'desc': 'يتم جمع البيانات حالياً', 'samples': 0}, + ]; + } + + return Card( + color: cs.surfaceContainerHighest, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: cs.outline), + ), + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'خارطة الفجوات ومؤشر التنافسية السعري (PCI)', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: cs.onSurface), + ), + const SizedBox(height: 12), + ...regions.map((region) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text(region['name'] as String, style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface)), + ), + const SizedBox(width: 8), + Text(region['desc'] as String, style: TextStyle(fontSize: 10, color: (region['pci'] as double) < 1 ? const Color(0xFF10B981) : cs.error)), + ], + ), + const SizedBox(height: 6), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: ((region['weight'] as double) + 1) / 2, + backgroundColor: const Color(0xFF10B981).withValues(alpha: 0.3), + color: (region['pci'] as double) < 1 ? const Color(0xFF10B981) : cs.error, + minHeight: 6, + ), + ), + ], + ), + ); + }), + ], + ), + ), + ); + } + + Widget _buildWhatIfSimulatorCard(BuildContext context, MarketingController c, ColorScheme cs) { + final TextEditingController priceCtrl = TextEditingController(); + return Card( + color: cs.surfaceContainerHighest, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: cs.outline)), + margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - Icon(Icons.science_outlined, color: _accent, size: 20), - SizedBox(width: 8), - Text('محاكي تغيير الأسعار الذكي', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: _textPrimary)), + Icon(Icons.science_outlined, color: cs.primary, size: 20), + const SizedBox(width: 8), + Text('محاكي تغيير الأسعار الذكي', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: cs.onSurface)), ], ), const SizedBox(height: 12), @@ -493,10 +476,10 @@ class MarketingPage extends StatelessWidget { child: TextField( controller: priceCtrl, keyboardType: TextInputType.number, - style: const TextStyle(color: _textPrimary, fontSize: 13), + style: TextStyle(color: cs.onSurface, fontSize: 13), decoration: InputDecoration( hintText: 'سعر الكيلومتر المقترح', - hintStyle: const TextStyle(color: _textSecondary, fontSize: 12), + hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 12), filled: true, fillColor: AppColor.surfaceElevated, border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), @@ -510,7 +493,7 @@ class MarketingPage extends StatelessWidget { if (priceCtrl.text.isNotEmpty) c.runWhatIfSimulation(priceCtrl.text); }, style: ElevatedButton.styleFrom( - backgroundColor: _accent, + backgroundColor: cs.primary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), child: const Text('محاكاة', style: TextStyle(fontSize: 12)), @@ -522,7 +505,7 @@ class MarketingPage extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: c.simulatorRecommendationStatus == 'success' ? _success.withOpacity(0.1) : (c.simulatorRecommendationStatus == 'danger' ? _danger.withOpacity(0.1) : _info.withOpacity(0.1)), + color: c.simulatorRecommendationStatus == 'success' ? const Color(0xFF10B981).withValues(alpha: 0.1) : (c.simulatorRecommendationStatus == 'danger' ? cs.error.withValues(alpha: 0.1) : cs.tertiary.withValues(alpha: 0.1)), borderRadius: BorderRadius.circular(8), ), child: Column( @@ -531,16 +514,16 @@ class MarketingPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('مؤشر PCI المتوقع:', style: TextStyle(fontSize: 12, color: _textSecondary)), - Text(c.simulatedPci.toString(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _textPrimary)), + Text('مؤشر PCI المتوقع:', style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant)), + Text(c.simulatedPci.toString(), style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: cs.onSurface)), ], ), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('الحصة السوقية (أرخص):', style: TextStyle(fontSize: 12, color: _textSecondary)), - Text('${c.simulatedMarketShare}%', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: _textPrimary)), + Text('الحصة السوقية (أرخص):', style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant)), + Text('${c.simulatedMarketShare}%', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface)), ], ), const SizedBox(height: 8), @@ -548,7 +531,7 @@ class MarketingPage extends StatelessWidget { c.simulatorRecommendationMessage ?? '', style: TextStyle( fontSize: 11, - color: c.simulatorRecommendationStatus == 'success' ? _success : (c.simulatorRecommendationStatus == 'danger' ? _danger : _info) + color: c.simulatorRecommendationStatus == 'success' ? const Color(0xFF10B981) : (c.simulatorRecommendationStatus == 'danger' ? cs.error : cs.tertiary) ), ), ], @@ -561,9 +544,9 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildLogsTab(BuildContext context, MarketingController c) { + Widget _buildLogsTab(BuildContext context, MarketingController c, ColorScheme cs) { if (c.isLoading && c.campaignsLog.isEmpty) { - return const Center(child: CircularProgressIndicator(color: _accent)); + return Center(child: CircularProgressIndicator(color: cs.primary)); } return ListView.builder( @@ -573,15 +556,15 @@ class MarketingPage extends StatelessWidget { final log = c.campaignsLog[index]; final channel = log['channel']?.toString().toUpperCase() ?? 'SMS'; - Color channelColor = _info; - if (channel == 'SMS') channelColor = _success; + Color channelColor = cs.tertiary; + if (channel == 'SMS') channelColor = const Color(0xFF10B981); if (channel == 'WHATSAPP') channelColor = const Color(0xFF25D366); return Card( - color: _surface, + color: cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: _divider), + side: BorderSide(color: cs.outline), ), margin: const EdgeInsets.only(bottom: 12), child: Padding( @@ -595,7 +578,7 @@ class MarketingPage extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( - color: channelColor.withOpacity(0.1), + color: channelColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Text( @@ -605,33 +588,33 @@ class MarketingPage extends StatelessWidget { ), Text( log['created_at']?.toString() ?? "", - style: const TextStyle(color: _textSecondary, fontSize: 11), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11), ), ], ), const SizedBox(height: 12), Text( log['campaign_name']?.toString() ?? "حملة استعادة تلقائية", - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: _textPrimary), + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: cs.onSurface), ), const SizedBox(height: 8), Text( log['message_body']?.toString() ?? "", - style: const TextStyle(fontSize: 12, color: _textSecondary, height: 1.4), + style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant, height: 1.4), ), const SizedBox(height: 12), - Divider(color: _divider, height: 1), + Divider(color: cs.outline, height: 1), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ - const Icon(Icons.people_outline_rounded, size: 16, color: _textSecondary), + Icon(Icons.people_outline_rounded, size: 16, color: cs.onSurfaceVariant), const SizedBox(width: 6), Text( 'المستهدفون: ${log['users_targeted'] ?? 0}', - style: const TextStyle(color: _textSecondary, fontSize: 12), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12), ), ], ), @@ -639,13 +622,13 @@ class MarketingPage extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: _accent.withOpacity(0.08), + color: cs.primary.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(6), - border: Border.all(color: _accent.withOpacity(0.2)), + border: Border.all(color: cs.primary.withValues(alpha: 0.2)), ), child: Text( 'كود: ${log['promo_code']}', - style: const TextStyle(color: _accent, fontWeight: FontWeight.bold, fontSize: 11), + style: TextStyle(color: cs.primary, fontWeight: FontWeight.bold, fontSize: 11), ), ), ], @@ -658,7 +641,7 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildConfigTab(BuildContext context, MarketingController c) { + Widget _buildConfigTab(BuildContext context, MarketingController c, ColorScheme cs) { final promptCtrl = TextEditingController(text: c.systemPrompt); return SingleChildScrollView( @@ -666,25 +649,24 @@ class MarketingPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // 1. Telemetry Monitor Card Card( - color: _surface, + color: cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: _divider), + side: BorderSide(color: cs.outline), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Row( + Row( children: [ - Icon(Icons.bar_chart_rounded, color: _info, size: 20), - SizedBox(width: 8), + Icon(Icons.bar_chart_rounded, color: cs.tertiary, size: 20), + const SizedBox(width: 8), Text( 'مراقب استهلاك الرموز والطلبات (API Telemetry)', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _textPrimary), + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: cs.onSurface), ), ], ), @@ -692,9 +674,9 @@ class MarketingPage extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildTelemetryItem('طلبات الـ API', '${c.apiRequestsCount}', _textPrimary), - _buildTelemetryItem('الرموز (Tokens)', '${c.totalTokensUsed}', _textPrimary), - _buildTelemetryItem('التكلفة التقديرية', '\$${c.estimatedCostUSD.toStringAsFixed(5)}', _success), + _buildTelemetryItem('طلبات الـ API', '${c.apiRequestsCount}', cs.onSurface, cs), + _buildTelemetryItem('الرموز (Tokens)', '${c.totalTokensUsed}', cs.onSurface, cs), + _buildTelemetryItem('التكلفة التقديرية', '\$${c.estimatedCostUSD.toStringAsFixed(5)}', const Color(0xFF10B981), cs), ], ), ], @@ -703,50 +685,48 @@ class MarketingPage extends StatelessWidget { ), const SizedBox(height: 16), - // 2. Autopilot Mode Card Card( - color: _surface, + color: cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: _divider), + side: BorderSide(color: cs.outline), ), child: SwitchListTile( - title: const Text( + title: Text( 'التشغيل الآلي بالكامل (Full Autopilot)', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _textPrimary), + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: cs.onSurface), ), subtitle: Text( c.isAutopilotEnabled ? 'يقوم النظام بإرسال الرسائل الترويجية فور رصد فرصة سعرية دون تدخل.' : 'النظام في وضع شبه الآلي (يتطلب مراجعة المدير قبل الإرسال).', - style: const TextStyle(fontSize: 10, color: _textSecondary), + style: TextStyle(fontSize: 10, color: cs.onSurfaceVariant), ), value: c.isAutopilotEnabled, - activeThumbColor: _accent, + activeThumbColor: cs.primary, onChanged: c.toggleAutopilot, ), ), const SizedBox(height: 16), - // 3. Prompt Configuration Editor Card Card( - color: _surface, + color: cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: _divider), + side: BorderSide(color: cs.outline), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const Row( + Row( children: [ - Icon(Icons.edit_note_rounded, color: _accent, size: 20), - SizedBox(width: 8), + Icon(Icons.edit_note_rounded, color: cs.primary, size: 20), + const SizedBox(width: 8), Text( 'تعديل التوجيه الأساسي للذكاء الاصطناعي (System Prompt)', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _textPrimary), + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: cs.onSurface), ), ], ), @@ -754,25 +734,25 @@ class MarketingPage extends StatelessWidget { TextField( controller: promptCtrl, maxLines: 4, - style: const TextStyle(fontSize: 12, color: _textPrimary), + style: TextStyle(fontSize: 12, color: cs.onSurface), decoration: InputDecoration( filled: true, fillColor: AppColor.surfaceElevated, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide(color: _divider), + borderSide: BorderSide(color: cs.outline), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), - borderSide: const BorderSide(color: _divider), + borderSide: BorderSide(color: cs.outline), ), ), ), const SizedBox(height: 12), ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: _accent, - foregroundColor: Colors.white, + backgroundColor: cs.primary, + foregroundColor: cs.onPrimary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(vertical: 12), ), @@ -788,11 +768,11 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildTelemetryItem(String label, String value, Color valueColor) { + Widget _buildTelemetryItem(String label, String value, Color valueColor, ColorScheme cs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(color: _textSecondary, fontSize: 10)), + Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 10)), const SizedBox(height: 4), Text( value, @@ -802,11 +782,11 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildStatItem(String label, String value, Color color) { + Widget _buildStatItem(String label, String value, Color color, ColorScheme cs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: const TextStyle(color: _textSecondary, fontSize: 11)), + Text(label, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11)), const SizedBox(height: 4), Text( value, @@ -816,24 +796,24 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildAIControlCard(BuildContext context, MarketingController c) { + Widget _buildAIControlCard(BuildContext context, MarketingController c, ColorScheme cs) { return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), - gradient: const LinearGradient( + gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ AppColor.surfaceElevated, - AppColor.surface, + cs.surfaceContainerHighest, ], ), border: Border.all(color: AppColor.accentBorder), boxShadow: [ BoxShadow( - color: _accent.withOpacity(0.08), + color: cs.primary.withValues(alpha: 0.08), blurRadius: 20, offset: const Offset(0, 10), ), @@ -847,38 +827,38 @@ class MarketingPage extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: _accent.withOpacity(0.12), + color: cs.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(12), ), - child: const Icon(Icons.psychology_outlined, color: _accent, size: 24), + child: Icon(Icons.psychology_outlined, color: cs.primary, size: 24), ), const SizedBox(width: 12), - const Text( + Text( 'مركز حملات الذكاء الاصطناعي (Gemini)', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15), + style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.bold, fontSize: 15), ), ], ), const SizedBox(height: 12), - const Text( + Text( 'يقوم نظام Autopilot بتحليل الشواذ السعرية لمنافسينا في مختلف المناطق الجغرافية، وصياغة عروض وحملات تسويقية ملائمة ومخصصة لإرسالها فورياً للعملاء المستهدفين.', - style: TextStyle(color: Colors.white70, fontSize: 11, height: 1.5), + style: TextStyle(color: cs.onSurface.withValues(alpha: 0.7), fontSize: 11, height: 1.5), ), const SizedBox(height: 20), ElevatedButton.icon( style: ElevatedButton.styleFrom( - backgroundColor: _accent, - foregroundColor: Colors.white, + backgroundColor: cs.primary, + foregroundColor: cs.onPrimary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), padding: const EdgeInsets.symmetric(vertical: 14), elevation: 4, ), onPressed: c.isLoading ? null : () => c.triggerAICampaign(), icon: c.isLoading - ? const SizedBox( + ? SizedBox( width: 18, height: 18, - child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2), + child: CircularProgressIndicator(color: cs.onPrimary, strokeWidth: 2), ) : const Icon(Icons.offline_bolt_rounded), label: const Text( @@ -891,29 +871,29 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildAIPredictionCard(BuildContext context, MarketingController c) { + Widget _buildAIPredictionCard(BuildContext context, MarketingController c, ColorScheme cs) { if (c.aiPredictionMessage == null) return const SizedBox.shrink(); return Card( - color: _info.withOpacity(0.05), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: _info.withOpacity(0.3))), + color: cs.tertiary.withValues(alpha: 0.05), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: cs.tertiary.withValues(alpha: 0.3))), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon(Icons.psychology, color: _info, size: 28), + Icon(Icons.psychology, color: cs.tertiary, size: 28), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('توقعات الذكاء الاصطناعي (Siro AI Prediction)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _info)), + Text('توقعات الذكاء الاصطناعي (Siro AI Prediction)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: cs.tertiary)), const SizedBox(height: 6), Text( c.aiPredictionMessage!, - style: const TextStyle(fontSize: 11, color: _textPrimary, height: 1.5), + style: TextStyle(fontSize: 11, color: cs.onSurface, height: 1.5), ), ], ), @@ -924,21 +904,21 @@ class MarketingPage extends StatelessWidget { ); } - Widget _buildMarketShareChart(BuildContext context, MarketingController c) { + Widget _buildMarketShareChart(BuildContext context, MarketingController c, ColorScheme cs) { if (c.marketShareData.isEmpty) return const SizedBox.shrink(); return Card( - color: _surface, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: const BorderSide(color: _divider)), + color: cs.surfaceContainerHighest, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16), side: BorderSide(color: cs.outline)), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( 'تطور الحصة السوقية (آخر 12 أسبوع)', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _textPrimary), + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: cs.onSurface), ), const SizedBox(height: 16), SizedBox( @@ -959,11 +939,11 @@ class MarketingPage extends StatelessWidget { return FlSpot(e.key.toDouble(), double.tryParse(e.value['market_share'].toString()) ?? 0.0); }).toList(), isCurved: true, - color: _accent, + color: cs.primary, barWidth: 3, isStrokeCapRound: true, dotData: const FlDotData(show: true), - belowBarData: BarAreaData(show: true, color: _accent.withOpacity(0.1)), + belowBarData: BarAreaData(show: true, color: cs.primary.withValues(alpha: 0.1)), ), ], ), diff --git a/siro_admin/lib/views/admin/marketing/social_intelligence_screen.dart b/siro_admin/lib/views/admin/marketing/social_intelligence_screen.dart index 29b9f4c1..2b67c0de 100644 --- a/siro_admin/lib/views/admin/marketing/social_intelligence_screen.dart +++ b/siro_admin/lib/views/admin/marketing/social_intelligence_screen.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:http/http.dart' as http; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; -import '../../../constant/colors.dart'; import '../../../constant/links.dart'; class SocialIntelligenceController extends GetxController { @@ -42,36 +41,38 @@ class SocialIntelligenceScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; final controller = Get.put(SocialIntelligenceController()); return Scaffold( - backgroundColor: AppColor.bg, + backgroundColor: cs.surface, appBar: AppBar( - title: const Text( + title: Text( 'استخبارات السوشيال ميديا (Social Intelligence)', - style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16), + style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: cs.onSurface), ), - backgroundColor: AppColor.bg, + backgroundColor: cs.surface, elevation: 0, - foregroundColor: AppColor.textPrimary, + foregroundColor: cs.onSurface, + iconTheme: IconThemeData(color: cs.onSurface), actions: [ IconButton( - icon: const Icon(Icons.refresh), + icon: Icon(Icons.refresh, color: cs.onSurface), onPressed: () => controller.fetchReports(), ) ], ), body: Obx(() { if (controller.isLoading.value) { - return const Center( - child: CircularProgressIndicator(color: AppColor.accent)); + return Center( + child: CircularProgressIndicator(color: cs.primary)); } if (controller.reports.isEmpty) { - return const Center( + return Center( child: Text( 'لا توجد تقارير حالياً', - style: TextStyle(color: AppColor.textSecondary), + style: TextStyle(color: cs.onSurfaceVariant), ), ); } @@ -84,45 +85,45 @@ class SocialIntelligenceScreen extends StatelessWidget { final isWeekly = report['is_weekly'].toString() == '1'; return Card( - color: AppColor.surface, + color: cs.surfaceContainerHighest, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide( - color: isWeekly ? AppColor.accent : AppColor.divider, + color: isWeekly ? cs.primary : cs.outline, width: isWeekly ? 1.5 : 1.0), ), margin: const EdgeInsets.only(bottom: 16), child: ExpansionTile( - iconColor: AppColor.accent, - collapsedIconColor: AppColor.textSecondary, + iconColor: cs.primary, + collapsedIconColor: cs.onSurfaceVariant, title: Text( isWeekly ? '📈 تقرير السوق الأسبوعي الشامل' : '📊 تقرير ${report['platform'].toString().toUpperCase()}', style: TextStyle( fontWeight: FontWeight.bold, - color: isWeekly ? AppColor.accent : AppColor.textPrimary, + color: isWeekly ? cs.primary : cs.onSurface, fontSize: 14, ), ), subtitle: Text( report['created_at'].toString(), - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 11), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 11), ), children: [ Container( padding: const EdgeInsets.all(16), width: double.infinity, - decoration: const BoxDecoration( - border: Border(top: BorderSide(color: AppColor.divider)), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: cs.outline)), ), child: Directionality( textDirection: TextDirection.rtl, child: HtmlWidget( report['report_html'] ?? '', - textStyle: const TextStyle( - color: AppColor.textPrimary, + textStyle: TextStyle( + color: cs.onSurface, fontSize: 13, height: 1.6), ), diff --git a/siro_admin/lib/views/admin/packages.dart b/siro_admin/lib/views/admin/packages.dart index c648e40c..7bdf3474 100644 --- a/siro_admin/lib/views/admin/packages.dart +++ b/siro_admin/lib/views/admin/packages.dart @@ -7,19 +7,6 @@ import 'package:siro_admin/views/widgets/snackbar.dart'; import '../../print.dart'; -// ══════════════════════════════════════════════════════════════ -// DESIGN TOKENS (same as AdminHomePage) -// ══════════════════════════════════════════════════════════════ -const Color _bg = Color(0xFF0D1117); -const Color _surface = Color(0xFF161B22); -const Color _surfaceElevated = Color(0xFF1C2333); -const Color _accent = Color(0xFF00D4AA); -const Color _warning = Color(0xFFFFCB6B); -const Color _info = Color(0xFF82AAFF); -const Color _textPrimary = Color(0xFFE6EDF3); -const Color _textSecondary = Color(0xFF7D8590); -const Color _divider = Color(0xFF21262D); - class PackageUpdateScreen extends StatelessWidget { PackageUpdateScreen({super.key}); @@ -27,19 +14,20 @@ class PackageUpdateScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: _bg, - appBar: _buildAppBar(), + backgroundColor: cs.surface, + appBar: _buildAppBar(cs), body: GetBuilder( builder: (controller) { if (controller.isLoading.value) { - return const Center( - child: CircularProgressIndicator(color: _accent, strokeWidth: 2), + return Center( + child: CircularProgressIndicator(color: cs.primary, strokeWidth: 2), ); } if (controller.packages.isEmpty) { - return _buildEmptyState(); + return _buildEmptyState(cs); } return ListView.separated( @@ -48,7 +36,7 @@ class PackageUpdateScreen extends StatelessWidget { separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, index) { final package = controller.packages[index]; - return _buildPackageCard(context, package, controller); + return _buildPackageCard(context, package, controller, cs); }, ); }, @@ -56,10 +44,9 @@ class PackageUpdateScreen extends StatelessWidget { ); } - // ─────────────────────────── APP BAR ─────────────────────────── - PreferredSizeWidget _buildAppBar() { + PreferredSizeWidget _buildAppBar(ColorScheme cs) { return AppBar( - backgroundColor: _bg, + backgroundColor: cs.surface, elevation: 0, surfaceTintColor: Colors.transparent, leading: GestureDetector( @@ -67,12 +54,12 @@ class PackageUpdateScreen extends StatelessWidget { child: Container( margin: const EdgeInsets.all(10), decoration: BoxDecoration( - color: _surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), - border: Border.all(color: _divider), + border: Border.all(color: cs.outline), ), - child: const Icon(Icons.arrow_back_ios_new_rounded, - color: _textSecondary, size: 16), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), ), ), title: Row( @@ -80,18 +67,18 @@ class PackageUpdateScreen extends StatelessWidget { Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( - color: _accent.withOpacity(0.12), + color: cs.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(9), - border: Border.all(color: _accent.withOpacity(0.25)), + border: Border.all(color: cs.primary.withValues(alpha: 0.25)), ), - child: const Icon(Icons.system_update_rounded, - color: _accent, size: 16), + child: Icon(Icons.system_update_rounded, + color: cs.primary, size: 16), ), const SizedBox(width: 10), - const Text( + Text( 'تحديث التطبيق', style: TextStyle( - color: _textPrimary, + color: cs.onSurface, fontSize: 17, fontWeight: FontWeight.w600, ), @@ -105,25 +92,24 @@ class PackageUpdateScreen extends StatelessWidget { margin: const EdgeInsets.only(right: 16), padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: _surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(10), - border: Border.all(color: _divider), + border: Border.all(color: cs.outline), ), - child: const Icon(Icons.refresh_rounded, - color: _textSecondary, size: 18), + child: Icon(Icons.refresh_rounded, + color: cs.onSurfaceVariant, size: 18), ), ), ], bottom: PreferredSize( preferredSize: const Size.fromHeight(1), - child: Container(height: 1, color: _divider), + child: Container(height: 1, color: cs.outline), ), ); } - // ─────────────────────────── PACKAGE CARD ─────────────────────────── Widget _buildPackageCard( - BuildContext context, dynamic package, PackageController controller) { + BuildContext context, dynamic package, PackageController controller, ColorScheme cs) { final platform = package['platform']?.toString() ?? ''; final isAndroid = platform.toLowerCase().contains('android'); final isIOS = platform.toLowerCase().contains('ios'); @@ -131,8 +117,8 @@ class PackageUpdateScreen extends StatelessWidget { final Color platformColor = isAndroid ? const Color(0xFF4CAF50) : isIOS - ? _info - : _warning; + ? cs.tertiary + : cs.tertiary; final IconData platformIcon = isAndroid ? Icons.android_rounded : isIOS @@ -142,20 +128,19 @@ class PackageUpdateScreen extends StatelessWidget { return Material( color: Colors.transparent, child: InkWell( - onTap: () => _showUpdateDialog(context, package, controller), + onTap: () => _showUpdateDialog(context, package, controller, cs), borderRadius: BorderRadius.circular(16), - splashColor: _accent.withOpacity(0.06), + splashColor: cs.primary.withValues(alpha: 0.06), highlightColor: Colors.transparent, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: _surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: _divider), + border: Border.all(color: cs.outline), ), child: Row( children: [ - // Platform Icon Container( width: 48, height: 48, @@ -164,27 +149,24 @@ class PackageUpdateScreen extends StatelessWidget { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - platformColor.withOpacity(0.20), - platformColor.withOpacity(0.06), + platformColor.withValues(alpha: 0.20), + platformColor.withValues(alpha: 0.06), ], ), borderRadius: BorderRadius.circular(13), - border: Border.all(color: platformColor.withOpacity(0.25)), + border: Border.all(color: platformColor.withValues(alpha: 0.25)), ), child: Icon(platformIcon, color: platformColor, size: 22), ), - const SizedBox(width: 14), - - // Info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( package['appName']?.toString() ?? '—', - style: const TextStyle( - color: _textPrimary, + style: TextStyle( + color: cs.onSurface, fontSize: 14, fontWeight: FontWeight.w600, ), @@ -195,31 +177,29 @@ class PackageUpdateScreen extends StatelessWidget { _buildTag(platform, platformColor), const SizedBox(width: 6), _buildVersionBadge( - package['version']?.toString() ?? '?'), + package['version']?.toString() ?? '?', cs), ], ), ], ), ), - - // Update button Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( - color: _accent.withOpacity(0.10), + color: cs.primary.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(10), - border: Border.all(color: _accent.withOpacity(0.25)), + border: Border.all(color: cs.primary.withValues(alpha: 0.25)), ), child: Row( mainAxisSize: MainAxisSize.min, - children: const [ - Icon(Icons.edit_rounded, color: _accent, size: 13), - SizedBox(width: 5), + children: [ + Icon(Icons.edit_rounded, color: cs.primary, size: 13), + const SizedBox(width: 5), Text( 'تعديل', style: TextStyle( - color: _accent, + color: cs.primary, fontSize: 11, fontWeight: FontWeight.w600, ), @@ -238,7 +218,7 @@ class PackageUpdateScreen extends StatelessWidget { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: color.withOpacity(0.10), + color: color.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(6), ), child: Text( @@ -252,17 +232,17 @@ class PackageUpdateScreen extends StatelessWidget { ); } - Widget _buildVersionBadge(String version) { + Widget _buildVersionBadge(String version, ColorScheme cs) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: _divider, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(6), ), child: Text( 'v$version', - style: const TextStyle( - color: _textSecondary, + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 10, fontWeight: FontWeight.w500, fontFamily: 'monospace', @@ -271,8 +251,7 @@ class PackageUpdateScreen extends StatelessWidget { ); } - // ─────────────────────────── EMPTY STATE ─────────────────────────── - Widget _buildEmptyState() { + Widget _buildEmptyState(ColorScheme cs) { return Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -280,30 +259,29 @@ class PackageUpdateScreen extends StatelessWidget { Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: _surface, + color: cs.surfaceContainerHighest, shape: BoxShape.circle, - border: Border.all(color: _divider), + border: Border.all(color: cs.outline), ), - child: const Icon(Icons.inventory_2_outlined, - color: _textSecondary, size: 32), + child: Icon(Icons.inventory_2_outlined, + color: cs.onSurfaceVariant, size: 32), ), const SizedBox(height: 16), - const Text('لا توجد حزم متاحة', + Text('لا توجد حزم متاحة', style: TextStyle( - color: _textPrimary, + color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w600)), const SizedBox(height: 6), - const Text('اسحب للأسفل لإعادة التحميل', - style: TextStyle(color: _textSecondary, fontSize: 12)), + Text('اسحب للأسفل لإعادة التحميل', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), ], ), ); } - // ─────────────────────────── UPDATE DIALOG ─────────────────────────── void _showUpdateDialog( - BuildContext context, dynamic package, PackageController controller) { + BuildContext context, dynamic package, PackageController controller, ColorScheme cs) { controller.versionController.clear(); Get.dialog( @@ -311,9 +289,9 @@ class PackageUpdateScreen extends StatelessWidget { backgroundColor: Colors.transparent, child: Container( decoration: BoxDecoration( - color: _surfaceElevated, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(24), - border: Border.all(color: _divider), + border: Border.all(color: cs.outline), boxShadow: const [ BoxShadow( color: Colors.black54, @@ -327,107 +305,95 @@ class PackageUpdateScreen extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Header Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: _accent.withOpacity(0.12), + color: cs.primary.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(12), - border: Border.all(color: _accent.withOpacity(0.25)), + border: Border.all(color: cs.primary.withValues(alpha: 0.25)), ), - child: const Icon(Icons.system_update_rounded, - color: _accent, size: 20), + child: Icon(Icons.system_update_rounded, + color: cs.primary, size: 20), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( + Text( 'تحديث الإصدار', style: TextStyle( - color: _textPrimary, + color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w700, ), ), Text( package['appName']?.toString() ?? '', - style: const TextStyle( - color: _textSecondary, fontSize: 11), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 11), ), ], ), ), ], ), - const SizedBox(height: 20), - Container(height: 1, color: _divider), + Container(height: 1, color: cs.outline), const SizedBox(height: 20), - - // Current info Row( children: [ _buildInfoChip(Icons.devices_rounded, - package['platform']?.toString() ?? '', _info), + package['platform']?.toString() ?? '', cs.tertiary), const SizedBox(width: 8), _buildInfoChip(Icons.tag_rounded, - 'الحالي: ${package['version']}', _warning), + 'الحالي: ${package['version']}', cs.tertiary), ], ), - const SizedBox(height: 18), - - // Input label - const Text( + Text( 'الإصدار الجديد', style: TextStyle( - color: _textSecondary, + color: cs.onSurfaceVariant, fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 0.8, ), ), const SizedBox(height: 8), - - // Text input Container( decoration: BoxDecoration( - color: _bg, + color: cs.surface, borderRadius: BorderRadius.circular(12), - border: Border.all(color: _divider), + border: Border.all(color: cs.outline), ), child: TextField( controller: controller.versionController, keyboardType: const TextInputType.numberWithOptions(decimal: true), - style: const TextStyle( - color: _textPrimary, + style: TextStyle( + color: cs.onSurface, fontSize: 15, fontFamily: 'monospace', fontWeight: FontWeight.w600, ), decoration: InputDecoration( hintText: package['version'].toString(), - hintStyle: const TextStyle( - color: _textSecondary, + hintStyle: TextStyle( + color: cs.onSurfaceVariant, fontFamily: 'monospace', ), prefixIcon: - const Icon(Icons.tag_rounded, color: _accent, size: 18), + Icon(Icons.tag_rounded, color: cs.primary, size: 18), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 14), ), ), ), - const SizedBox(height: 24), - - // Actions Row( children: [ Expanded( @@ -437,12 +403,12 @@ class PackageUpdateScreen extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), - side: const BorderSide(color: _divider), + side: BorderSide(color: cs.outline), ), ), - child: const Text( + child: Text( 'إلغاء', - style: TextStyle(color: _textSecondary, fontSize: 13), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13), ), ), ), @@ -450,11 +416,11 @@ class PackageUpdateScreen extends StatelessWidget { Expanded( child: Obx(() => ElevatedButton.icon( icon: controller.isLoading.value - ? const SizedBox( + ? SizedBox( width: 14, height: 14, child: CircularProgressIndicator( - color: Colors.white, + color: cs.onPrimary, strokeWidth: 2, ), ) @@ -464,8 +430,8 @@ class PackageUpdateScreen extends StatelessWidget { style: const TextStyle(fontSize: 13), ), style: ElevatedButton.styleFrom( - backgroundColor: _accent, - foregroundColor: _bg, + backgroundColor: cs.primary, + foregroundColor: cs.surface, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -495,9 +461,9 @@ class PackageUpdateScreen extends StatelessWidget { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( - color: color.withOpacity(0.08), + color: color.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8), - border: Border.all(color: color.withOpacity(0.2)), + border: Border.all(color: color.withValues(alpha: 0.2)), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -518,9 +484,6 @@ class PackageUpdateScreen extends StatelessWidget { } } -// ══════════════════════════════════════════════════════════════ -// CONTROLLER -// ══════════════════════════════════════════════════════════════ class PackageController extends GetxController { List packages = []; var isLoading = false.obs; diff --git a/siro_admin/lib/views/admin/passenger/passenger.dart b/siro_admin/lib/views/admin/passenger/passenger.dart index aa47c4be..28e3b951 100644 --- a/siro_admin/lib/views/admin/passenger/passenger.dart +++ b/siro_admin/lib/views/admin/passenger/passenger.dart @@ -2,14 +2,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../constant/box_name.dart'; -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; import '../../../controller/admin/passenger_admin_controller.dart'; -import '../../../main.dart'; // للوصول إلى box -import '../../widgets/elevated_btn.dart'; -import '../../widgets/my_scafold.dart'; -import '../../widgets/my_textField.dart'; -import '../../widgets/mycircular.dart'; +import '../../../main.dart'; import '../../widgets/snackbar.dart'; import 'passenger_details_page.dart'; @@ -21,141 +15,36 @@ class Passengrs extends StatelessWidget { @override Widget build(BuildContext context) { - // 1. منطق السوبر أدمن + final cs = Theme.of(context).colorScheme; + final isDark = Theme.of(context).brightness == Brightness.dark; + String myPhone = box.read(BoxName.adminPhone).toString(); - bool isSuperAdmin = myPhone == '963942542053' || myPhone == '963992952235'; + bool isSuperAdmin = + myPhone == '963942542053' || myPhone == '963992952235'; - return MyScafolld( - title: 'Passengers Management'.tr, - isleading: true, - body: [ - // استخدام Expanded أو Container بطول الشاشة لتجنب المشاكل - SizedBox( - height: Get.height, // تأمين مساحة العمل - child: GetBuilder( - builder: (controller) { - if (controller.isLoading) { - return const Center(child: MyCircularProgressIndicator()); - } - - return Column( - children: [ - // --- قسم الإحصائيات والجوائز (Dashboard) --- - Padding( - padding: const EdgeInsets.all(16.0), - child: _buildDashboardCard(context, controller), - ), - - // --- عنوان القائمة --- - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "All Passengers".tr, - style: AppStyle.title.copyWith( - fontSize: 18, fontWeight: FontWeight.bold), - ), - Text( - "${controller.passengersData['message']?.length ?? 0} Users", - style: - TextStyle(color: Colors.grey[600], fontSize: 12), - ), - ], - ), - ), - const SizedBox(height: 10), - - // --- قائمة الركاب --- - // استخدام Expanded هنا هو الحل الجذري لمكلة Overflow - Expanded( - child: _buildPassengersList(controller, isSuperAdmin), - ), - - // مساحة سفلية صغيرة لضمان عدم التصاق القائمة بالحافة - const SizedBox(height: 20), - ], - ); - }, - ), - ), - ], - ); - } - - // --- تصميم بطاقة الإحصائيات (Dashboard) --- - Widget _buildDashboardCard( - BuildContext context, PassengerAdminController controller) { - // جلب العدد بأمان - final String countValue = (controller.passengersData['message'] != null && - controller.passengersData['message'].isNotEmpty) - ? controller.passengersData['message'][0]['countPassenger'] - ?.toString() ?? - '0' - : '0'; - - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 15, - offset: const Offset(0, 5), - ), - ], - ), - child: Column( + return Scaffold( + backgroundColor: cs.surface, + body: Column( children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColor.primaryColor.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon(Icons.groups_rounded, - color: AppColor.primaryColor, size: 30), - ), - const SizedBox(width: 15), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Total Passengers'.tr, - style: const TextStyle(fontSize: 14, color: Colors.grey), - ), - Text( - countValue, - style: const TextStyle( - fontSize: 24, fontWeight: FontWeight.bold), - ), - ], - ), - ], - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - height: 45, - child: ElevatedButton.icon( - icon: const Icon(Icons.card_giftcard, - color: Colors.white, size: 20), - label: Text('Add Prize to Gold Passengers'.tr, - style: const TextStyle( - color: Colors.white, fontWeight: FontWeight.bold)), - style: ElevatedButton.styleFrom( - backgroundColor: AppColor.yellowColor, // لون ذهبي - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12)), - elevation: 0, - ), - onPressed: () { - _showAddPrizeDialog(controller); + _buildAppBar(context, cs), + Expanded( + child: GetBuilder( + builder: (controller) { + if (controller.isLoading) { + return _buildLoadingState(cs); + } + + return Column( + children: [ + _buildDashboardCard(context, controller, cs), + _buildListHeader(context, controller, cs), + Expanded( + child: _buildPassengersList( + controller, isSuperAdmin, cs), + ), + const SizedBox(height: 20), + ], + ); }, ), ), @@ -164,20 +53,252 @@ class Passengrs extends StatelessWidget { ); } - // --- بناء قائمة الركاب --- + Widget _buildAppBar(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.groups_rounded, color: cs.primary, size: 18), + ), + const SizedBox(width: 10), + Text( + 'إدارة الركاب', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } + + Widget _buildLoadingState(ColorScheme cs) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: cs.primary, + strokeWidth: 2, + backgroundColor: cs.primary.withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text('جاري تحميل الركاب...', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)), + ], + ), + ); + } + + Widget _buildDashboardCard( + BuildContext context, + PassengerAdminController controller, + ColorScheme cs) { + final String countValue = + (controller.passengersData['message'] != null && + controller.passengersData['message'].isNotEmpty) + ? controller.passengersData['message'][0]['countPassenger'] + ?.toString() ?? + '0' + : '0'; + + return Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.primary.withValues(alpha: 0.15)), + boxShadow: [ + BoxShadow( + color: cs.primary.withValues(alpha: 0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + cs.primary.withValues(alpha: 0.20), + cs.primary.withValues(alpha: 0.08), + ], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.groups_rounded, + color: cs.primary, size: 28), + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'إجمالي الركاب', + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 12), + ), + const SizedBox(height: 4), + Text( + countValue, + style: TextStyle( + color: cs.onSurface, + fontSize: 26, + fontWeight: FontWeight.w800, + height: 1, + ), + ), + ], + ), + ], + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton.icon( + icon: const Icon(Icons.card_giftcard, + color: Colors.white, size: 18), + label: Text('إضافة جائزة لركاب الذهب', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 13)), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFF59E0B), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + elevation: 0, + ), + onPressed: () { + _showAddPrizeDialog(controller, cs); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildListHeader( + BuildContext context, + PassengerAdminController controller, + ColorScheme cs) { + return Padding( + padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), + child: Row( + children: [ + Container( + width: 3, + height: 14, + decoration: BoxDecoration( + color: cs.primary, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 8), + Text( + 'جميع الركاب', + style: TextStyle( + color: cs.onSurface, + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Container(height: 1, color: cs.outline), + ), + const SizedBox(width: 8), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: cs.primary.withValues(alpha: 0.2)), + ), + child: Text( + '${controller.passengersData['message']?.length ?? 0}', + style: TextStyle( + color: cs.primary, + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + ), + ], + ), + ); + } + Widget _buildPassengersList( - PassengerAdminController controller, bool isSuperAdmin) { - final List passengers = controller.passengersData['message'] ?? []; + PassengerAdminController controller, + bool isSuperAdmin, + ColorScheme cs) { + final List passengers = + controller.passengersData['message'] ?? []; if (passengers.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.person_off_outlined, size: 60, color: Colors.grey[300]), - const SizedBox(height: 10), - Text("No passengers found".tr, - style: TextStyle(color: Colors.grey[400])), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon(Icons.person_off_outlined, + size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text('لا يوجد ركاب', + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 14)), ], ), ); @@ -187,132 +308,171 @@ class Passengrs extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16), physics: const BouncingScrollPhysics(), itemCount: passengers.length, - separatorBuilder: (context, index) => const SizedBox(height: 12), + separatorBuilder: (context, index) => const SizedBox(height: 10), itemBuilder: (context, index) { final user = passengers[index]; - return _buildPassengerItem(user, isSuperAdmin); + return _buildPassengerItem(user, isSuperAdmin, cs); }, ); } - // --- عنصر الراكب الواحد (Card) --- - Widget _buildPassengerItem(dynamic user, bool isSuperAdmin) { + Widget _buildPassengerItem( + dynamic user, bool isSuperAdmin, ColorScheme cs) { String firstName = user['first_name'] ?? ''; String lastName = user['last_name'] ?? ''; String fullName = '$firstName $lastName'.trim(); - if (fullName.isEmpty) fullName = 'Unknown User'; + if (fullName.isEmpty) fullName = 'مستخدم'; return Container( decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - border: Border.all(color: Colors.grey.withOpacity(0.1)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.05), - blurRadius: 5, - offset: const Offset(0, 2), - ), - ], + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), ), child: Material( color: Colors.transparent, child: InkWell( - borderRadius: BorderRadius.circular(15), + borderRadius: BorderRadius.circular(16), onTap: () { - // الانتقال للتفاصيل مع تمرير صلاحية الأدمن Get.to( () => const PassengerDetailsPage(), arguments: {'data': user, 'isSuperAdmin': isSuperAdmin}, ); }, child: Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.all(14), child: Row( children: [ - // Avatar - CircleAvatar( - radius: 25, - backgroundColor: AppColor.primaryColor.withOpacity(0.1), - child: Text( - fullName.isNotEmpty ? fullName[0].toUpperCase() : 'U', - style: TextStyle( - color: AppColor.primaryColor, - fontWeight: FontWeight.bold, - fontSize: 18), + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + cs.primary.withValues(alpha: 0.20), + cs.primary.withValues(alpha: 0.08), + ], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: cs.primary.withValues(alpha: 0.2)), + ), + child: Center( + child: Text( + fullName.isNotEmpty + ? fullName[0].toUpperCase() + : 'U', + style: TextStyle( + color: cs.primary, + fontWeight: FontWeight.w800, + fontSize: 18, + ), + ), ), ), - const SizedBox(width: 15), - - // Info + const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( fullName, - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 16), + style: TextStyle( + color: cs.onSurface, + fontWeight: FontWeight.w600, + fontSize: 15, + ), ), - const SizedBox(height: 4), - - // Stats Row + const SizedBox(height: 6), Row( children: [ - Icon(Icons.star_rounded, - size: 16, color: Colors.amber[700]), - Text( - " ${user['ratingPassenger'] ?? '0.0'} ", - style: const TextStyle( - fontSize: 12, fontWeight: FontWeight.bold), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: const Color(0xFFF59E0B) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.star_rounded, + size: 12, color: Color(0xFFF59E0B)), + const SizedBox(width: 3), + Text( + user['ratingPassenger']?.toString() ?? + '0.0', + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + color: Color(0xFFF59E0B), + ), + ), + ], + ), ), const SizedBox(width: 8), - Icon(Icons.directions_car, - size: 14, color: Colors.grey[400]), - Text( - " ${user['countPassengerRide'] ?? '0'} Trips", - style: TextStyle( - fontSize: 12, color: Colors.grey[600]), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.directions_car_rounded, + size: 12, color: cs.primary), + const SizedBox(width: 3), + Text( + '${user['countPassengerRide'] ?? '0'} رحلة', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: cs.primary, + ), + ), + ], + ), ), ], ), - - const SizedBox(height: 4), - // Phone Number (Masked logic) + const SizedBox(height: 6), Row( children: [ - Icon(Icons.phone_iphone, - size: 12, color: Colors.grey[400]), + Icon(Icons.phone_iphone_rounded, + size: 12, color: cs.onSurfaceVariant), const SizedBox(width: 4), Text( _formatPhoneNumber( user['phone'].toString(), isSuperAdmin), style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - fontFamily: 'monospace'), + fontSize: 11, + color: cs.onSurfaceVariant, + fontFamily: 'monospace', + ), ), ], ), - - // Email (Show only if Super Admin) if (isSuperAdmin && user['email'] != null) ...[ - const SizedBox(height: 2), + const SizedBox(height: 3), Text( user['email'], - style: - TextStyle(fontSize: 10, color: Colors.grey[400]), + style: TextStyle( + fontSize: 10, color: cs.onSurfaceVariant), maxLines: 1, overflow: TextOverflow.ellipsis, - ) - ] + ), + ], ], ), ), - - // Arrow Icon(Icons.arrow_forward_ios_rounded, - size: 16, color: Colors.grey[300]), + size: 14, color: cs.onSurfaceVariant), ], ), ), @@ -321,63 +481,89 @@ class Passengrs extends StatelessWidget { ); } - // --- دالة تنسيق الرقم (إظهار آخر 4 أرقام لغير الأدمن) --- String _formatPhoneNumber(String phone, bool isSuperAdmin) { - if (isSuperAdmin) return phone; // إظهار الرقم كاملاً للسوبر أدمن - - // لغير الأدمن + if (isSuperAdmin) return phone; if (phone.length <= 4) return phone; String lastFour = phone.substring(phone.length - 4); String masked = '*' * (phone.length - 4); - return '$masked$lastFour'; // النتيجة: *******5678 + return '$masked$lastFour'; } - // --- دالة إضافة الجوائز --- - void _showAddPrizeDialog(PassengerAdminController controller) { - // التحقق من يوم السبت + void _showAddPrizeDialog( + PassengerAdminController controller, ColorScheme cs) { if (DateTime.now().weekday == DateTime.saturday) { Get.defaultDialog( - title: 'Add Prize'.tr, - titleStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + title: 'إضافة جائزة', + titleStyle: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + color: cs.onSurface), + backgroundColor: cs.surface, contentPadding: const EdgeInsets.all(20), - content: Form( - key: controller.formPrizeKey, - child: Column( - children: [ - Text( - 'Add Points to Gold Passengers wallet'.tr, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 14, color: Colors.grey[700]), + content: Column( + children: [ + Text( + 'إضافة نقاط لمحفظة ركاب الذهب', + textAlign: TextAlign.center, + style: + TextStyle(color: cs.onSurfaceVariant, fontSize: 13), + ), + const SizedBox(height: 20), + TextFormField( + controller: controller.passengerPrizeController, + keyboardType: TextInputType.number, + style: TextStyle(color: cs.onSurface), + cursorColor: cs.primary, + decoration: InputDecoration( + labelText: 'مبلغ الجائزة', + hintText: '1000...', + filled: true, + fillColor: cs.surfaceContainerHighest, + labelStyle: TextStyle(color: cs.onSurfaceVariant), + hintStyle: TextStyle(color: cs.onSurfaceVariant), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: cs.outline), + borderRadius: BorderRadius.circular(14), + ), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: cs.primary, width: 1.5), + borderRadius: BorderRadius.circular(14), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 14), ), - const SizedBox(height: 20), - MyTextForm( - controller: controller.passengerPrizeController, - label: 'Prize Amount'.tr, - hint: '1000...', - type: TextInputType.number, - ), - ], - ), + ), + ], ), confirm: SizedBox( width: 120, - child: MyElevatedButton( - title: 'Add', + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: cs.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + elevation: 0, + ), onPressed: () async { if (controller.formPrizeKey.currentState!.validate()) { controller.addPassengerPrizeToWalletSecure(); Get.back(); } }, + child: const Text('إضافة', + style: TextStyle(fontWeight: FontWeight.bold)), ), ), cancel: TextButton( - onPressed: () => Get.back(), - child: - Text('Cancel'.tr, style: const TextStyle(color: Colors.grey))), + onPressed: () => Get.back(), + child: Text('إلغاء', + style: TextStyle(color: cs.onSurfaceVariant)), + ), ); } else { - mySnackbarWarning('Prizes can only be added on Saturdays.'.tr); + mySnackbarWarning('يمكن إضافة الجوائز يوم السبت فقط'); } } } diff --git a/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart b/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart index c5796dad..a3bf79b6 100644 --- a/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart +++ b/siro_admin/lib/views/admin/pricing/kazan_editor_page.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:get/get.dart'; -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; import '../../../controller/admin/kazan_controller.dart'; import '../../../views/widgets/snackbar.dart'; -import '../../widgets/my_scafold.dart'; import '../../widgets/elevated_btn.dart'; class KazanEditorPage extends StatelessWidget { @@ -14,63 +12,72 @@ class KazanEditorPage extends StatelessWidget { @override Widget build(BuildContext context) { - return MyScafolld( - title: 'تعديل أسعار كازان'.tr, - isleading: true, - body: [ - Column( - children: [ - _buildCountryDropdown(), - Expanded( - child: Obx(() => controller.isLoading.value && controller.kazanData.isEmpty - ? const Center(child: CircularProgressIndicator()) - : SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader('⚙️ الإعدادات العامة'), - _buildGeneralSettings(), - const SizedBox(height: 24), - _buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة'), - _buildKmPricesGrid(), - const SizedBox(height: 24), - _buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)'), - _buildMinutePrices(), - const SizedBox(height: 32), - MyElevatedButton( - title: '💾 حفظ جميع التعديلات', - icon: Icons.save_rounded, - onPressed: () => _handleSave(), - ), - const SizedBox(height: 100), - ], - ), - )), - ), - ], + final cs = Theme.of(context).colorScheme; + + return Scaffold( + backgroundColor: cs.surface, + appBar: AppBar( + backgroundColor: cs.surface, + elevation: 0, + centerTitle: true, + title: Text('تعديل أسعار كازان'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), + leading: GestureDetector( + onTap: () => Get.back(), + child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface), ), - ], + ), + body: Column( + children: [ + _buildCountryDropdown(cs), + Expanded( + child: Obx(() => controller.isLoading.value && controller.kazanData.isEmpty + ? Center(child: CircularProgressIndicator(color: cs.primary)) + : SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader('⚙️ الإعدادات العامة', cs), + _buildGeneralSettings(cs), + const SizedBox(height: 24), + _buildSectionHeader('🚗 أسعار الكيلومتر لكل نوع سيارة', cs), + _buildKmPricesGrid(cs), + const SizedBox(height: 24), + _buildSectionHeader('⏱️ أسعار الدقيقة (حسب وقت اليوم)', cs), + _buildMinutePrices(cs), + const SizedBox(height: 32), + MyElevatedButton( + title: '💾 حفظ جميع التعديلات', + icon: Icons.save_rounded, + onPressed: () => _handleSave(), + ), + const SizedBox(height: 100), + ], + ), + )), + ), + ], + ), ); } - Widget _buildCountryDropdown() { + Widget _buildCountryDropdown(ColorScheme cs) { return Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), child: Obx(() => Container( padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(14), - border: Border.all(color: AppColor.divider), + border: Border.all(color: cs.outline), ), child: DropdownButtonHideUnderline( child: DropdownButton( value: controller.selectedCountry.value, isExpanded: true, icon: const Icon(Icons.keyboard_arrow_down_rounded), - style: AppStyle.title.copyWith(fontSize: 16), + style: GoogleFonts.inter(fontWeight: FontWeight.w600, fontSize: 16, color: cs.onSurface), items: controller.countries.map((c) { return DropdownMenuItem( value: c['name'], @@ -92,13 +99,14 @@ class KazanEditorPage extends StatelessWidget { ); } - // ==================================================================== - // 1. الإعدادات العامة - // ==================================================================== - Widget _buildGeneralSettings() { + Widget _buildGeneralSettings(ColorScheme cs) { return Container( padding: const EdgeInsets.all(20), - decoration: AppStyle.cardDecoration, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), child: Column( children: [ _buildSliderItem( @@ -106,31 +114,30 @@ class KazanEditorPage extends StatelessWidget { 'kazanPercent', 'النسبة المئوية التي تقتطعها المنصة من كل رحلة', Icons.percent_rounded, + cs, ), - const Divider(height: 32, color: AppColor.divider), + Divider(height: 32, color: cs.outline), _buildPriceInputRow( 'سعر الوقود المرجعي', 'fuelPrice', 'السعر المستخدم في حسابات تعويض الوقود', Icons.local_gas_station_rounded, + cs, ), - const Divider(height: 32, color: AppColor.divider), + Divider(height: 32, color: cs.outline), _buildPriceInputRow( 'رمز العملة', 'currency', 'مثال: SYP, EGP, JOD, IQD', Icons.currency_exchange_rounded, + cs, ), ], ), ); } - // ==================================================================== - // 2. أسعار الكيلومتر - كل نوع سيارة له حقل خاص به - // ==================================================================== - Widget _buildKmPricesGrid() { - // 🆕 9 أنواع سيارات - كل واحد له عمود سعر مستقل + Widget _buildKmPricesGrid(ColorScheme cs) { final Map> priceFields = { 'speedPrice': { 'label': 'Speed ⚡', @@ -180,13 +187,17 @@ class KazanEditorPage extends StatelessWidget { }; return Container( - decoration: AppStyle.cardDecoration, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), padding: const EdgeInsets.all(12), child: GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, // 3 أعمدة بدلاً من 2 + crossAxisCount: 3, childAspectRatio: 1.8, crossAxisSpacing: 8, mainAxisSpacing: 8, @@ -196,14 +207,14 @@ class KazanEditorPage extends StatelessWidget { String key = priceFields.keys.elementAt(index); var field = priceFields[key]!; return _buildKmPriceCard( - key, field['label'], field['icon'], field['color']); + key, field['label'], field['icon'], field['color'], cs); }, ), ); } Widget _buildKmPriceCard( - String key, String label, IconData icon, Color color) { + String key, String label, IconData icon, Color color, ColorScheme cs) { final TextEditingController textController = TextEditingController(text: _getValue(key)); @@ -225,8 +236,7 @@ class KazanEditorPage extends StatelessWidget { Expanded( child: Text( label, - style: AppStyle.caption - .copyWith(fontSize: 10, fontWeight: FontWeight.bold), + style: GoogleFonts.inter(fontSize: 10, fontWeight: FontWeight.bold, color: cs.onSurfaceVariant), overflow: TextOverflow.ellipsis, ), ), @@ -240,7 +250,7 @@ class KazanEditorPage extends StatelessWidget { controller: textController, keyboardType: TextInputType.number, textAlign: TextAlign.center, - style: AppStyle.number.copyWith(fontSize: 13, color: color), + style: GoogleFonts.jetBrainsMono(fontSize: 13, fontWeight: FontWeight.bold, color: color), decoration: const InputDecoration( border: InputBorder.none, isDense: true, @@ -256,10 +266,7 @@ class KazanEditorPage extends StatelessWidget { ); } - // ==================================================================== - // 3. أسعار الدقيقة (حسب وقت اليوم) - // ==================================================================== - Widget _buildMinutePrices() { + Widget _buildMinutePrices(ColorScheme cs) { final Map> minuteFields = { 'normalMinPrice': { 'label': 'Normal (سعر الدقيقة العادي)', @@ -282,7 +289,11 @@ class KazanEditorPage extends StatelessWidget { }; return Container( - decoration: AppStyle.cardDecoration, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), padding: const EdgeInsets.all(16), child: Column( children: minuteFields.entries.map((entry) { @@ -293,6 +304,7 @@ class KazanEditorPage extends StatelessWidget { entry.key, entry.value['desc'], entry.value['icon'], + cs, ), ); }).toList(), @@ -300,15 +312,12 @@ class KazanEditorPage extends StatelessWidget { ); } - // ==================================================================== - // دوال مساعدة للـ UI - // ==================================================================== - Widget _buildSectionHeader(String title) { + Widget _buildSectionHeader(String title, ColorScheme cs) { return Padding( padding: const EdgeInsets.only(bottom: 12, left: 4), child: Text( title, - style: AppStyle.title.copyWith(color: AppColor.accent), + style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.primary), ), ); } @@ -322,21 +331,21 @@ class KazanEditorPage extends StatelessWidget { } Widget _buildPriceInputRow( - String title, String key, String desc, IconData icon) { + String title, String key, String desc, IconData icon, ColorScheme cs) { final TextEditingController textController = TextEditingController(text: _getValue(key)); return Row( children: [ - Icon(icon, size: 20, color: AppColor.accent), + Icon(icon, size: 20, color: cs.primary), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, - style: AppStyle.body.copyWith(fontWeight: FontWeight.bold)), - Text(desc, style: AppStyle.caption.copyWith(fontSize: 10)), + style: GoogleFonts.inter(fontSize: 14, color: cs.onSurface, fontWeight: FontWeight.bold)), + Text(desc, style: GoogleFonts.inter(fontSize: 10, color: cs.onSurfaceVariant)), ], ), ), @@ -344,16 +353,15 @@ class KazanEditorPage extends StatelessWidget { width: 100, height: 40, decoration: BoxDecoration( - color: AppColor.surfaceElevated, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColor.divider), + border: Border.all(color: cs.outline), ), child: TextField( controller: textController, keyboardType: TextInputType.number, textAlign: TextAlign.center, - style: - AppStyle.number.copyWith(fontSize: 16, color: AppColor.accent), + style: GoogleFonts.jetBrainsMono(fontSize: 16, fontWeight: FontWeight.bold, color: cs.primary), decoration: const InputDecoration( border: InputBorder.none, isDense: true, @@ -367,7 +375,7 @@ class KazanEditorPage extends StatelessWidget { } Widget _buildSliderItem( - String title, String key, String desc, IconData icon) { + String title, String key, String desc, IconData icon, ColorScheme cs) { double value = double.tryParse(_getValue(key)) ?? 0; return Column( @@ -378,25 +386,25 @@ class KazanEditorPage extends StatelessWidget { children: [ Row( children: [ - Icon(icon, size: 18, color: AppColor.accent), + Icon(icon, size: 18, color: cs.primary), const SizedBox(width: 8), - Text(title, style: AppStyle.title), + Text(title, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), ], ), Text( '${value.toInt()}%', - style: AppStyle.number.copyWith(fontSize: 18), + style: GoogleFonts.jetBrainsMono(fontSize: 18, fontWeight: FontWeight.bold, color: cs.onSurface), ), ], ), const SizedBox(height: 4), - Text(desc, style: AppStyle.caption), + Text(desc, style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)), Slider( value: value.clamp(0, 100), min: 0, max: 100, - activeColor: AppColor.accent, - inactiveColor: AppColor.divider, + activeColor: cs.primary, + inactiveColor: cs.outline, onChanged: (val) { _setValue(key, val.toInt().toString()); }, @@ -405,14 +413,10 @@ class KazanEditorPage extends StatelessWidget { ); } - // ==================================================================== - // حفظ البيانات - // ==================================================================== void _handleSave() async { final data = Map.from(controller.kazanData); data['adminId'] = 'admin1'; - // التأكد من وجود country if (!data.containsKey('country') || data['country'] == null || data['country'].toString().isEmpty) { diff --git a/siro_admin/lib/views/admin/promo/promo_management_page.dart b/siro_admin/lib/views/admin/promo/promo_management_page.dart index 2e583bca..5ebca494 100644 --- a/siro_admin/lib/views/admin/promo/promo_management_page.dart +++ b/siro_admin/lib/views/admin/promo/promo_management_page.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:get/get.dart'; -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; import '../../../controller/admin/promo_controller.dart'; -import '../../widgets/my_scafold.dart'; import '../../widgets/elevated_btn.dart'; import '../../widgets/my_textField.dart'; import '../../widgets/mydialoug.dart'; @@ -15,89 +13,110 @@ class PromoManagementPage extends StatelessWidget { @override Widget build(BuildContext context) { - return MyScafolld( - title: 'إدارة أكواد الخصم'.tr, - isleading: true, - action: IconButton( - icon: const Icon(Icons.add_circle_outline_rounded, color: AppColor.accent), - onPressed: () => _showPromoSheet(context), + final cs = Theme.of(context).colorScheme; + + return Scaffold( + backgroundColor: cs.surface, + appBar: AppBar( + backgroundColor: cs.surface, + elevation: 0, + centerTitle: true, + title: Text('إدارة أكواد الخصم'.tr, style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface)), + leading: GestureDetector( + onTap: () => Get.back(), + child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurface), + ), + actions: [ + IconButton( + icon: Icon(Icons.add_circle_outline_rounded, color: cs.primary), + onPressed: () => _showPromoSheet(context), + ), + ], ), - body: [ - Obx(() => controller.isLoading.value && controller.promoList.isEmpty - ? const Center(child: CircularProgressIndicator()) - : controller.promoList.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.confirmation_number_outlined, size: 64, color: AppColor.textMuted), - const SizedBox(height: 16), - Text('لا يوجد أكواد خصم حالياً', style: AppStyle.subtitle), - ], - ), - ) - : RefreshIndicator( - onRefresh: () => controller.getPromos(), - child: ListView.builder( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), - itemCount: controller.promoList.length, - itemBuilder: (context, index) { - final promo = controller.promoList[index]; - return _buildPromoCard(context, promo); - }, - ), - )), - ], + body: Obx(() => controller.isLoading.value && controller.promoList.isEmpty + ? Center(child: CircularProgressIndicator(color: cs.primary)) + : controller.promoList.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration(shape: BoxShape.circle, color: cs.onSurfaceVariant.withValues(alpha: 0.1)), + child: Icon(Icons.confirmation_number_outlined, size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text('لا يوجد أكواد خصم حالياً', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 14)), + ], + ), + ) + : RefreshIndicator( + onRefresh: () => controller.getPromos(), + child: ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 80), + itemCount: controller.promoList.length, + itemBuilder: (context, index) { + final promo = controller.promoList[index]; + return _buildPromoCard(context, promo); + }, + ), + )), ); } Widget _buildPromoCard(BuildContext context, dynamic promo) { + final cs = Theme.of(context).colorScheme; + return Container( margin: const EdgeInsets.only(bottom: 12), - decoration: AppStyle.cardDecoration, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), leading: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: AppColor.accentSoft, + color: cs.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), - child: const Icon(Icons.local_offer_rounded, color: AppColor.accent), + child: Icon(Icons.local_offer_rounded, color: cs.primary), ), title: Text( promo['promo_code']?.toString() ?? 'N/A', - style: AppStyle.title, + style: GoogleFonts.inter(fontWeight: FontWeight.w600, color: cs.onSurface), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), - Text(promo['description']?.toString() ?? '', style: AppStyle.caption), + Text(promo['description']?.toString() ?? '', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(height: 4), Row( children: [ - Icon(Icons.money_rounded, size: 14, color: AppColor.success), + Icon(Icons.money_rounded, size: 14, color: const Color(0xFF10B981)), const SizedBox(width: 4), - Text('% ${promo['amount']}', style: AppStyle.number.copyWith(color: AppColor.success)), + Text('% ${promo['amount']}', style: GoogleFonts.jetBrainsMono(fontWeight: FontWeight.bold, color: const Color(0xFF10B981))), const SizedBox(width: 12), - Icon(Icons.person_rounded, size: 14, color: AppColor.info), + Icon(Icons.person_rounded, size: 14, color: cs.tertiary), const SizedBox(width: 4), - Text(promo['passengerID'] == 'none' ? 'عام' : 'مخصص', style: AppStyle.caption), + Text(promo['passengerID'] == 'none' ? 'عام' : 'مخصص', style: GoogleFonts.inter(color: cs.onSurfaceVariant, fontSize: 12)), ], ), ], ), trailing: PopupMenuButton( - icon: const Icon(Icons.more_vert_rounded, color: AppColor.textSecondary), - color: AppColor.surfaceElevated, + icon: Icon(Icons.more_vert_rounded, color: cs.onSurfaceVariant), + color: cs.surfaceContainerHighest, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), itemBuilder: (context) => [ PopupMenuItem( value: 'edit', child: Row( children: [ - const Icon(Icons.edit_rounded, size: 18, color: AppColor.info), + Icon(Icons.edit_rounded, size: 18, color: cs.tertiary), const SizedBox(width: 8), Text('تعديل'.tr), ], @@ -107,9 +126,9 @@ class PromoManagementPage extends StatelessWidget { value: 'delete', child: Row( children: [ - const Icon(Icons.delete_outline_rounded, size: 18, color: AppColor.danger), + Icon(Icons.delete_outline_rounded, size: 18, color: cs.error), const SizedBox(width: 8), - Text('حذف'.tr, style: const TextStyle(color: AppColor.danger)), + Text('حذف'.tr, style: TextStyle(color: cs.error)), ], ), ), @@ -131,6 +150,7 @@ class PromoManagementPage extends StatelessWidget { } void _showPromoSheet(BuildContext context, {dynamic promo}) { + final cs = Theme.of(context).colorScheme; final TextEditingController codeController = TextEditingController(text: promo?['promo_code']); final TextEditingController amountController = TextEditingController(text: promo?['amount']?.toString()); final TextEditingController descController = TextEditingController(text: promo?['description']); @@ -141,9 +161,9 @@ class PromoManagementPage extends StatelessWidget { Get.bottomSheet( Container( padding: const EdgeInsets.all(24), - decoration: const BoxDecoration( - color: AppColor.surfaceElevated, - borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: SingleChildScrollView( child: Column( @@ -152,10 +172,10 @@ class PromoManagementPage extends StatelessWidget { Container( width: 40, height: 4, - decoration: BoxDecoration(color: AppColor.divider, borderRadius: BorderRadius.circular(2)), + decoration: BoxDecoration(color: cs.outline, borderRadius: BorderRadius.circular(2)), ), const SizedBox(height: 24), - Text(promo == null ? 'إضافة كود خصم جديد' : 'تعديل كود الخصم', style: AppStyle.headTitle), + Text(promo == null ? 'إضافة كود خصم جديد' : 'تعديل كود الخصم', style: GoogleFonts.cairo(fontWeight: FontWeight.bold, color: cs.onSurface, fontSize: 18)), const SizedBox(height: 24), MyTextForm( controller: codeController, diff --git a/siro_admin/lib/views/admin/quality/blacklist_page.dart b/siro_admin/lib/views/admin/quality/blacklist_page.dart index b8c0cce0..712c5ccf 100644 --- a/siro_admin/lib/views/admin/quality/blacklist_page.dart +++ b/siro_admin/lib/views/admin/quality/blacklist_page.dart @@ -7,143 +7,324 @@ class BlacklistPage extends StatelessWidget { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; Get.put(QualityController()).fetchBlacklist(); - return DefaultTabController( - length: 2, - child: Scaffold( - appBar: AppBar( - title: const Text('إدارة القائمة السوداء (Blacklist)', - style: TextStyle(fontWeight: FontWeight.bold)), - backgroundColor: Colors.red[800], - bottom: const TabBar( - indicatorColor: Colors.white, - tabs: [ - Tab(icon: Icon(Icons.drive_eta), text: 'السائقين المحظورين'), - Tab(icon: Icon(Icons.person), text: 'الركاب المحظورين'), - ], - ), - ), - body: GetBuilder( - builder: (controller) { - if (controller.isLoading) { - return const Center(child: CircularProgressIndicator()); - } + return Scaffold( + backgroundColor: cs.surface, + body: Column( + children: [ + _buildAppBar(context, cs), + _buildTabBar(context, cs), + Expanded( + child: GetBuilder( + builder: (controller) { + if (controller.isLoading) { + return _buildLoadingState(cs); + } - return TabBarView( - children: [ - _buildDriverList(controller), - _buildPassengerList(controller), - ], - ); - }, + return TabBarView( + controller: DefaultTabController.of(context), + children: [ + _buildDriverList(controller, cs), + _buildPassengerList(controller, cs), + ], + ); + }, + ), + ), + ], + ), + ); + } + + Widget _buildAppBar(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.error.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.error.withValues(alpha: 0.25)), + ), + child: Icon(Icons.block_rounded, color: cs.error, size: 18), + ), + const SizedBox(width: 10), + Text( + 'القائمة السوداء', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } + + Widget _buildTabBar(BuildContext context, ColorScheme cs) { + return Container( + margin: const EdgeInsets.fromLTRB(16, 12, 16, 0), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: cs.outline), + ), + child: TabBar( + controller: DefaultTabController.of(context), + indicator: BoxDecoration( + color: cs.primary.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.primary.withValues(alpha: 0.3)), + ), + indicatorSize: TabBarIndicatorSize.tab, + dividerColor: Colors.transparent, + labelColor: cs.primary, + unselectedLabelColor: cs.onSurfaceVariant, + labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13), + unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.w500, fontSize: 13), + padding: const EdgeInsets.all(4), + labelPadding: EdgeInsets.zero, + tabs: const [ + Tab(text: 'السائقين', icon: Icon(Icons.drive_eta_rounded, size: 16)), + Tab(text: 'الركاب', icon: Icon(Icons.person_rounded, size: 16)), + ], + ), + ); + } + + Widget _buildLoadingState(ColorScheme cs) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: cs.primary, + strokeWidth: 2, + backgroundColor: cs.primary.withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text('جاري التحميل...', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)), + ], + ), + ); + } + + Widget _buildDriverList(QualityController controller, ColorScheme cs) { + if (controller.driversBlacklist.isEmpty) { + return _buildEmptyState('لا يوجد سائقين محظورين', cs); + } + return ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 80), + physics: const BouncingScrollPhysics(), + itemCount: controller.driversBlacklist.length, + itemBuilder: (context, index) { + var driver = controller.driversBlacklist[index]; + return _buildBlacklistItem( + phone: driver['phone'], + reason: driver['reason'] ?? 'غير محدد', + date: driver['created_at'], + type: 'سائق', + icon: Icons.drive_eta_rounded, + onUnblock: () => controller.unblockDriver(driver['phone'].toString()), + cs: cs, + ); + }, + ); + } + + Widget _buildPassengerList(QualityController controller, ColorScheme cs) { + if (controller.passengersBlacklist.isEmpty) { + return _buildEmptyState('لا يوجد ركاب محظورين', cs); + } + return ListView.builder( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 80), + physics: const BouncingScrollPhysics(), + itemCount: controller.passengersBlacklist.length, + itemBuilder: (context, index) { + var passenger = controller.passengersBlacklist[index]; + return _buildBlacklistItem( + phone: passenger['phone'] ?? passenger['phone_normalized'], + reason: passenger['reason'] ?? 'غير محدد', + date: passenger['created_at'], + type: 'راكب', + icon: Icons.person_rounded, + onUnblock: () => controller.unblockPassenger( + passenger['phone_normalized'].toString()), + cs: cs, + ); + }, + ); + } + + Widget _buildEmptyState(String message, ColorScheme cs) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon(Icons.block_rounded, size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text(message, + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)), + ], + ), + ); + } + + Widget _buildBlacklistItem({ + required String phone, + required String reason, + required String date, + required String type, + required IconData icon, + required VoidCallback onUnblock, + required ColorScheme cs, + }) { + return Container( + margin: const EdgeInsets.only(bottom: 10), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.error.withValues(alpha: 0.15)), + ), + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: cs.error.withValues(alpha: 0.12), + shape: BoxShape.circle, + border: Border.all(color: cs.error.withValues(alpha: 0.2)), + ), + child: Icon(icon, color: cs.error, size: 22), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + phone, + style: TextStyle( + color: cs.onSurface, + fontWeight: FontWeight.w700, + fontSize: 15, + fontFamily: 'monospace', + ), + ), + const SizedBox(height: 4), + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: cs.error.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + type, + style: TextStyle( + color: cs.error, + fontSize: 10, + fontWeight: FontWeight.w700, + ), + ), + ), + const SizedBox(width: 6), + Expanded( + child: Text( + 'السبب: $reason', + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 11, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 3), + Text( + date, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 10, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () => _showUnblockDialog(Get.context!, type, phone, onUnblock, cs), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: const Color(0xFF10B981).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.2)), + ), + child: Icon(Icons.settings_backup_restore_rounded, + color: const Color(0xFF10B981), size: 18), + ), + ), + ], ), ), ); } - Widget _buildDriverList(QualityController controller) { - if (controller.driversBlacklist.isEmpty) { - return const Center(child: Text('لا يوجد سائقين محظورين حالياً')); - } - return ListView.builder( - itemCount: controller.driversBlacklist.length, - padding: const EdgeInsets.all(12), - itemBuilder: (context, index) { - var driver = controller.driversBlacklist[index]; - return Card( - elevation: 3, - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - child: ListTile( - leading: const CircleAvatar( - backgroundColor: Colors.redAccent, - child: Icon(Icons.block, color: Colors.white), - ), - title: Text('هاتف: ${driver['phone']}', - style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('السبب: ${driver['reason'] ?? "غير محدد"}'), - Text('تاريخ الحظر: ${driver['created_at']}'), - ], - ), - trailing: IconButton( - icon: const Icon(Icons.settings_backup_restore, - color: Colors.green), - onPressed: () { - _showUnblockDialog( - Get.context!, - 'سائق', - driver['phone'], - () => controller.unblockDriver(driver['phone'].toString()), - ); - }, - ), - ), - ); - }, - ); - } - - Widget _buildPassengerList(QualityController controller) { - if (controller.passengersBlacklist.isEmpty) { - return const Center(child: Text('لا يوجد ركاب محظورين حالياً')); - } - return ListView.builder( - itemCount: controller.passengersBlacklist.length, - padding: const EdgeInsets.all(12), - itemBuilder: (context, index) { - var passenger = controller.passengersBlacklist[index]; - return Card( - elevation: 3, - shape: - RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - child: ListTile( - leading: const CircleAvatar( - backgroundColor: Colors.orangeAccent, - child: Icon(Icons.person_off, color: Colors.white), - ), - title: Text( - 'هاتف: ${passenger['phone'] ?? passenger['phone_normalized']}', - style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('السبب: ${passenger['reason'] ?? "غير محدد"}'), - Text('تاريخ الحظر: ${passenger['created_at']}'), - ], - ), - trailing: IconButton( - icon: const Icon(Icons.settings_backup_restore, - color: Colors.green), - onPressed: () { - _showUnblockDialog( - Get.context!, - 'راكب', - passenger['phone_normalized'], - () => controller.unblockPassenger( - passenger['phone_normalized'].toString()), - ); - }, - ), - ), - ); - }, - ); - } - - void _showUnblockDialog(BuildContext context, String type, String identifier, - VoidCallback onConfirm) { + void _showUnblockDialog( + BuildContext context, String type, String identifier, VoidCallback onConfirm, ColorScheme cs) { Get.defaultDialog( title: "تأكيد فك الحظر", + titleStyle: TextStyle(color: cs.onSurface, fontWeight: FontWeight.bold), + backgroundColor: cs.surface, middleText: "هل أنت متأكد من فك الحظر عن هذا ال$type ($identifier)؟\nسيتم تسجيل هذه العملية في الـ Audit Log.", textConfirm: "نعم، فك الحظر", textCancel: "تراجع", confirmTextColor: Colors.white, - buttonColor: Colors.green, + buttonColor: const Color(0xFF10B981), + cancelTextColor: cs.onSurfaceVariant, onConfirm: () { Get.back(); onConfirm(); diff --git a/siro_admin/lib/views/admin/rides/rides.dart b/siro_admin/lib/views/admin/rides/rides.dart index b0947271..24376237 100644 --- a/siro_admin/lib/views/admin/rides/rides.dart +++ b/siro_admin/lib/views/admin/rides/rides.dart @@ -1,236 +1,435 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../../constant/colors.dart'; -import '../../../constant/style.dart'; import '../../../controller/admin/ride_admin_controller.dart'; -import '../../widgets/my_scafold.dart'; -import '../../widgets/mycircular.dart'; class Rides extends StatelessWidget { Rides({super.key}); final RideAdminController rideAdminController = Get.put(RideAdminController()); + @override Widget build(BuildContext context) { - return MyScafolld(title: 'Rides'.tr, isleading: true, body: [ - GetBuilder( - builder: (rideAdminController) => rideAdminController.isLoading - ? const Center(child: MyCircularProgressIndicator()) - : Column( - children: [ - SizedBox( - height: Get.height * .4, - child: LineChart( - duration: const Duration(milliseconds: 150), - curve: Curves.ease, - LineChartData( - lineBarsData: [ - LineChartBarData( - spots: rideAdminController.chartData, - isCurved: true, - color: Colors.deepPurpleAccent, // Custom color - barWidth: 3, // Thinner line - dotData: const FlDotData( - show: true), // Show dots on each point - belowBarData: BarAreaData( - // Add gradient fill below the line - show: true, - color: AppColor.deepPurpleAccent, - ), - isStrokeJoinRound: true, - shadow: const BoxShadow( - color: AppColor.yellowColor, - blurRadius: 4, - offset: Offset(2, 2), - ), - ), - ], - showingTooltipIndicators: const [], - titlesData: FlTitlesData( - show: true, - topTitles: AxisTitles( - axisNameWidget: Text( - 'Days', - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, showTitles: true)), - bottomTitles: AxisTitles( - axisNameWidget: Text( - 'Total Trips on month'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, showTitles: true)), - leftTitles: AxisTitles( - axisNameWidget: Text( - 'Counts of Trips on month'.tr, - style: AppStyle.title, - ), - axisNameSize: 30, - sideTitles: const SideTitles( - reservedSize: 30, showTitles: true)), - ), - gridData: const FlGridData( - show: true, - ), - borderData: FlBorderData( - show: true, - border: const Border( - bottom: BorderSide(color: AppColor.accentColor), - left: BorderSide(color: AppColor.accentColor), - ), - ), - ), - ), - ), - // SizedBox( - // height: Get.height * .4, - // child: PieChart( - // PieChartData( - // sectionsSpace: 4, // Adjust spacing between sections - // centerSpaceRadius: - // 40, // Adjust radius of center space - // sections: [ - // for (final rideData in rideAdminController.rideData) - // PieChartSectionData( - // value: rideData.ridesCount.toDouble(), - // title: '${rideData.day}', showTitle: true, - // titleStyle: - // AppStyle.subtitle, // Display day as title - // radius: 60, // Adjust radius of each section - // color: - // AppColor.deepPurpleAccent, // Custom color - // ), - // ], - // ), - // ), - // ), + final cs = Theme.of(context).colorScheme; - // SizedBox( - // // height: 400, - // child: SfCartesianChart( - // legend: const Legend( - // isVisible: true, - // position: LegendPosition.bottom, - // overflowMode: LegendItemOverflowMode.wrap, - // textStyle: TextStyle( - // color: Colors.white, - // fontSize: 12, - // fontWeight: FontWeight.bold, - // ), - // ), - // borderWidth: 2, - // borderColor: AppColor.blueColor, - // plotAreaBorderColor: AppColor.deepPurpleAccent, - // enableAxisAnimation: true, - // primaryXAxis: CategoryAxis( - // borderColor: AppColor.accentColor, borderWidth: 2, - // title: AxisTitle( - // text: 'Total Trips on month'.tr, - // textStyle: AppStyle.title, - // ), - // // labelRotation: 45, - // majorGridLines: const MajorGridLines(width: 0), - // ), - // primaryYAxis: const NumericAxis(isVisible: false), - // series: >[ - // LineSeries( - // dataSource: rideAdminController.chartDatasync, - // xValueMapper: (ChartDataS data, _) => '${data.day}', - // yValueMapper: (ChartDataS data, _) => - // data.ridesCount, - // dataLabelSettings: - // const DataLabelSettings(isVisible: true), - // ), - // ], - // ), - // ), + return Scaffold( + backgroundColor: cs.surface, + body: Column( + children: [ + _buildAppBar(context, cs), + Expanded( + child: GetBuilder( + builder: (controller) { + if (controller.isLoading) { + return _buildLoadingState(cs); + } - const SizedBox( - height: 20, - ), - Card( - elevation: 4, - color: AppColor.deepPurpleAccent, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Total Trips on this Month is ${rideAdminController.jsonResponse['message'][0]['current_month_rides_count']}', - style: AppStyle.title, - ), + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildChartSection(controller, cs), + const SizedBox(height: 16), + _buildMonthlyTotal(controller, cs), + const SizedBox(height: 16), + _buildRideDetails(controller, cs), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } + + Widget _buildAppBar(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.map_rounded, color: cs.primary, size: 18), + ), + const SizedBox(width: 10), + Text( + 'شاشة الرحلات', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); + } + + Widget _buildLoadingState(ColorScheme cs) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: cs.primary, + strokeWidth: 2, + backgroundColor: cs.primary.withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text('جاري تحميل الرحلات...', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)), + ], + ), + ); + } + + Widget _buildChartSection(RideAdminController controller, ColorScheme cs) { + return Container( + height: 280, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.primary.withValues(alpha: 0.15)), + boxShadow: [ + BoxShadow( + color: cs.primary.withValues(alpha: 0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.trending_up_rounded, + color: cs.primary, size: 16), + ), + const SizedBox(width: 10), + Text( + 'رحلات الشهر', + style: TextStyle( + color: cs.onSurface, + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + const SizedBox(height: 16), + Expanded( + child: Directionality( + textDirection: TextDirection.ltr, + child: LineChart( + LineChartData( + lineBarsData: [ + LineChartBarData( + spots: controller.chartData, + isCurved: true, + color: cs.primary, + barWidth: 3, + dotData: const FlDotData(show: true), + belowBarData: BarAreaData( + show: true, + color: cs.primary.withValues(alpha: 0.08), ), + isStrokeJoinRound: true, ), - const SizedBox( - height: 20, - ), - Card( - elevation: 4, - color: AppColor.yellowColor, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - Text( - 'Driver Average Duration: ${rideAdminController.ridesDetails[0]['driver_avg_duration']}', - style: AppStyle.subtitle, - ), - Text( - 'Number of Drivers: ${rideAdminController.ridesDetails[0]['num_Driver']}', - style: AppStyle.subtitle, - ), - Text( - 'Total Rides: ${rideAdminController.ridesDetails[0]['total_rides']}', - style: AppStyle.subtitle, - ), - Text( - 'Ongoing Rides: ${rideAdminController.ridesDetails[0]['ongoing_rides']}', - style: AppStyle.subtitle, - ), - Text( - 'Completed Rides: ${rideAdminController.ridesDetails[0]['completed_rides']}', - style: AppStyle.subtitle, - ), - Text( - 'Cancelled Rides: ${rideAdminController.ridesDetails[0]['cancelled_rides']}', - style: AppStyle.subtitle, - ), - Text( - 'Longest Duration: ${rideAdminController.ridesDetails[0]['longest_duration']}', - style: AppStyle.subtitle, - ), - Text( - 'Total Distance: ${rideAdminController.ridesDetails[0]['total_distance']} km', - style: AppStyle.subtitle, - ), - Text( - 'Average Distance: ${rideAdminController.ridesDetails[0]['average_distance']} km', - style: AppStyle.subtitle, - ), - Text( - 'Longest Distance: ${rideAdminController.ridesDetails[0]['longest_distance']} km', - style: AppStyle.subtitle, - ), - Text( - 'Total Driver Earnings: \$${rideAdminController.ridesDetails[0]['total_driver_earnings']}', - style: AppStyle.subtitle, - ), - Text( - 'Total Company Earnings: \$${rideAdminController.ridesDetails[0]['total_company_earnings']}', - style: AppStyle.subtitle, - ), - Text( - 'Company Percentage: ${rideAdminController.ridesDetails[0]['companyPercent']} %', - style: AppStyle.subtitle, - ), - ], - ), - ), - ) ], - )) - ]); + titlesData: FlTitlesData( + show: true, + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 24, + getTitlesWidget: (v, _) => Text( + '${v.toInt()}', + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 9), + ), + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 36, + getTitlesWidget: (v, _) => Text( + v.toInt().toString(), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 9), + ), + ), + ), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + getDrawingHorizontalLine: (_) => FlLine( + color: cs.outline.withValues(alpha: 0.5), + strokeWidth: 1), + ), + borderData: FlBorderData(show: false), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildMonthlyTotal(RideAdminController controller, ColorScheme cs) { + final count = controller.jsonResponse['message']?[0] + ['current_month_rides_count'] ?? + '0'; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.primary.withValues(alpha: 0.15)), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + cs.primary.withValues(alpha: 0.20), + cs.primary.withValues(alpha: 0.08), + ], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.calendar_today_rounded, + color: cs.primary, size: 24), + ), + const SizedBox(width: 16), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'إجمالي رحلات الشهر', + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 12), + ), + const SizedBox(height: 4), + Text( + '$count', + style: TextStyle( + color: cs.onSurface, + fontSize: 28, + fontWeight: FontWeight.w800, + height: 1, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildRideDetails(RideAdminController controller, ColorScheme cs) { + if (controller.ridesDetails.isEmpty) return const SizedBox.shrink(); + + final details = controller.ridesDetails[0]; + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + boxShadow: [ + BoxShadow( + color: cs.shadow.withValues(alpha: 0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: const Color(0xFFF59E0B).withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: const Color(0xFFF59E0B).withValues(alpha: 0.25)), + ), + child: Icon(Icons.analytics_rounded, + color: const Color(0xFFF59E0B), size: 16), + ), + const SizedBox(width: 10), + Text( + 'تفاصيل الرحلات', + style: TextStyle( + color: cs.onSurface, + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + const SizedBox(height: 16), + _buildDetailGrid(details, cs), + ], + ), + ); + } + + Widget _buildDetailGrid(dynamic details, ColorScheme cs) { + final items = [ + _DetailItem('متوسط مدة السائق', details['driver_avg_duration'] ?? '', + Icons.timer_rounded, cs.tertiary), + _DetailItem('عدد السائقين', details['num_Driver'] ?? '', + Icons.people_rounded, cs.primary), + _DetailItem('إجمالي الرحلات', details['total_rides'] ?? '', + Icons.route_rounded, cs.primary), + _DetailItem('رحلات نشطة', details['ongoing_rides'] ?? '', + Icons.play_circle_rounded, const Color(0xFFF59E0B)), + _DetailItem('مكتملة', details['completed_rides'] ?? '', + Icons.check_circle_rounded, const Color(0xFF10B981)), + _DetailItem('ملغاة', details['cancelled_rides'] ?? '', + Icons.cancel_rounded, cs.error), + _DetailItem('أطول مدة', details['longest_duration'] ?? '', + Icons.hourglass_top_rounded, cs.tertiary), + _DetailItem('إجمالي المسافة', '${details['total_distance'] ?? 0} km', + Icons.straighten_rounded, cs.primary), + _DetailItem('متوسط المسافة', '${details['average_distance'] ?? 0} km', + Icons.speed_rounded, const Color(0xFFF59E0B)), + _DetailItem('أطول مسافة', '${details['longest_distance'] ?? 0} km', + Icons.arrow_forward_rounded, cs.tertiary), + _DetailItem('إجمالي أرباح السائقين', + '\$${details['total_driver_earnings'] ?? 0}', Icons.payments_rounded, + const Color(0xFF10B981)), + _DetailItem('إجمالي أرباح الشركة', + '\$${details['total_company_earnings'] ?? 0}', + Icons.business_center_rounded, cs.primary), + _DetailItem('نسبة الشركة', '${details['companyPercent'] ?? 0}%', + Icons.pie_chart_rounded, const Color(0xFFF59E0B)), + ]; + + return GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: cs.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: item.color.withValues(alpha: 0.15)), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: item.color.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(item.icon, color: item.color, size: 16), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.label, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 10, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + item.value, + style: TextStyle( + color: cs.onSurface, + fontSize: 13, + fontWeight: FontWeight.w700, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ); + }, + ); } } + +class _DetailItem { + final String label, value; + final IconData icon; + final Color color; + _DetailItem(this.label, this.value, this.icon, this.color); +} diff --git a/siro_admin/lib/views/admin/security/audit_logs_page.dart b/siro_admin/lib/views/admin/security/audit_logs_page.dart index 660f5d96..78dbe433 100644 --- a/siro_admin/lib/views/admin/security/audit_logs_page.dart +++ b/siro_admin/lib/views/admin/security/audit_logs_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/colors.dart'; import 'package:siro_admin/controller/admin/security_v2_controller.dart'; class AuditLogsPage extends StatelessWidget { @@ -8,21 +7,24 @@ class AuditLogsPage extends StatelessWidget { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Scaffold( - backgroundColor: AppColor.bg, + backgroundColor: cs.surface, appBar: AppBar( - title: const Text('سجل العمليات (Audit Logs)', - style: TextStyle(fontWeight: FontWeight.bold)), - backgroundColor: AppColor.surface, + title: Text('سجل العمليات (Audit Logs)', + style: TextStyle(fontWeight: FontWeight.bold, color: cs.onSurface)), + backgroundColor: cs.surface, elevation: 0, centerTitle: true, + iconTheme: IconThemeData(color: cs.onSurface), ), body: GetBuilder( init: SecurityV2Controller(), builder: (ctrl) { if (ctrl.isLoading) { - return const Center( - child: CircularProgressIndicator(color: AppColor.accent)); + return Center( + child: CircularProgressIndicator(color: cs.primary)); } if (ctrl.auditLogs.isEmpty) { @@ -31,15 +33,15 @@ class AuditLogsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.security_rounded, - size: 64, color: AppColor.textSecondary.withOpacity(0.3)), + size: 64, color: cs.onSurfaceVariant.withValues(alpha: 0.3)), const SizedBox(height: 16), - const Text('لا توجد عمليات مسجلة حالياً', - style: TextStyle(color: AppColor.textSecondary)), + Text('لا توجد عمليات مسجلة حالياً', + style: TextStyle(color: cs.onSurfaceVariant)), const SizedBox(height: 8), - const Text( + Text( 'تأكد من إنشاء جدول سجل العمليات في قاعدة البيانات', style: TextStyle( - color: AppColor.textSecondary, fontSize: 10)), + color: cs.onSurfaceVariant, fontSize: 10)), ], ), ); @@ -50,7 +52,7 @@ class AuditLogsPage extends StatelessWidget { itemCount: ctrl.auditLogs.length, itemBuilder: (ctx, i) { final log = ctrl.auditLogs[i]; - return _buildLogItem(log); + return _buildLogItem(log, cs); }, ); }, @@ -58,14 +60,14 @@ class AuditLogsPage extends StatelessWidget { ); } - Widget _buildLogItem(Map log) { + Widget _buildLogItem(Map log, ColorScheme cs) { return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppColor.divider), + border: Border.all(color: cs.outline), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -74,22 +76,22 @@ class AuditLogsPage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(log['admin_name'] ?? 'أدمن غير معروف', - style: const TextStyle( - color: AppColor.accent, fontWeight: FontWeight.bold)), + style: TextStyle( + color: cs.primary, fontWeight: FontWeight.bold)), Text(log['created_at'] ?? '', - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 11)), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 11)), ], ), const SizedBox(height: 8), Text(log['action'] ?? '', - style: const TextStyle( - color: AppColor.textPrimary, fontWeight: FontWeight.w600)), + style: TextStyle( + color: cs.onSurface, fontWeight: FontWeight.w600)), if (log['details'] != null) ...[ const SizedBox(height: 4), Text(log['details'], - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 12)), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 12)), ], const SizedBox(height: 8), Row( @@ -97,19 +99,19 @@ class AuditLogsPage extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: AppColor.info.withOpacity(0.1), + color: cs.tertiary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), child: Text(log['table_name'] ?? '', - style: const TextStyle( - color: AppColor.info, + style: TextStyle( + color: cs.tertiary, fontSize: 10, fontWeight: FontWeight.bold)), ), const SizedBox(width: 8), Text('ID: ${log['record_id']}', - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 10)), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 10)), ], ), ], diff --git a/siro_admin/lib/views/admin/staff/add_staff_page.dart b/siro_admin/lib/views/admin/staff/add_staff_page.dart index 5cbb81d5..e387db73 100644 --- a/siro_admin/lib/views/admin/staff/add_staff_page.dart +++ b/siro_admin/lib/views/admin/staff/add_staff_page.dart @@ -3,23 +3,20 @@ import 'package:get/get.dart'; import '../../../controller/admin/staff_controller.dart'; class AddStaffPage extends StatelessWidget { - final String role; // 'admin' or 'service' + final String role; const AddStaffPage({super.key, required this.role}); @override Widget build(BuildContext context) { final controller = Get.put(StaffController()); controller.selectedRole = role; - - const Color bgColor = Color(0xFF0D1117); - const Color inputColor = Color(0xFF161B22); - const Color accentColor = Color(0xFF00D4AA); + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: bgColor, + backgroundColor: cs.surface, appBar: AppBar( title: Text(role == 'admin' ? "إضافة مدير جديد" : "إضافة موظف خدمة عملاء"), - backgroundColor: bgColor, + backgroundColor: cs.surface, elevation: 0, ), body: SingleChildScrollView( @@ -29,20 +26,20 @@ class AddStaffPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildSectionTitle("المعلومات الأساسية"), + _buildSectionTitle("المعلومات الأساسية", cs), const SizedBox(height: 16), _buildTextField( controller: controller.nameController, label: "الاسم الكامل", icon: Icons.person_outline, - fillColor: inputColor, + cs: cs, ), const SizedBox(height: 16), _buildTextField( controller: controller.phoneController, label: "رقم الهاتف", icon: Icons.phone_android_outlined, - fillColor: inputColor, + cs: cs, keyboardType: TextInputType.phone, ), const SizedBox(height: 16), @@ -50,7 +47,7 @@ class AddStaffPage extends StatelessWidget { controller: controller.emailController, label: "البريد الإلكتروني", icon: Icons.email_outlined, - fillColor: inputColor, + cs: cs, keyboardType: TextInputType.emailAddress, ), const SizedBox(height: 16), @@ -58,11 +55,11 @@ class AddStaffPage extends StatelessWidget { controller: controller.passwordController, label: "كلمة المرور", icon: Icons.lock_outline, - fillColor: inputColor, + cs: cs, obscureText: true, ), const SizedBox(height: 24), - _buildSectionTitle("معلومات إضافية"), + _buildSectionTitle("معلومات إضافية", cs), const SizedBox(height: 16), Row( children: [ @@ -72,7 +69,7 @@ class AddStaffPage extends StatelessWidget { value: controller.selectedGender, items: ['Male', 'Female'], onChanged: (val) => controller.selectedGender = val!, - fillColor: inputColor, + cs: cs, ), ), const SizedBox(width: 16), @@ -81,7 +78,7 @@ class AddStaffPage extends StatelessWidget { controller: controller.birthdateController, label: "تاريخ الميلاد", icon: Icons.calendar_today_outlined, - fillColor: inputColor, + cs: cs, hint: "YYYY-MM-DD", ), ), @@ -93,7 +90,7 @@ class AddStaffPage extends StatelessWidget { value: controller.selectedCountry, items: const ['Jordan', 'Syria', 'Egypt'], onChanged: (val) => controller.selectedCountry = val!, - fillColor: inputColor, + cs: cs, ), const SizedBox(height: 40), GetBuilder( @@ -103,17 +100,17 @@ class AddStaffPage extends StatelessWidget { child: ElevatedButton( onPressed: controller.isLoading ? null : () => controller.registerStaff(), style: ElevatedButton.styleFrom( - backgroundColor: accentColor, + backgroundColor: cs.primary, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), child: controller.isLoading - ? const CircularProgressIndicator(color: Colors.white) + ? CircularProgressIndicator(color: cs.onPrimary) : Text( "حفظ البيانات", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: bgColor, + color: cs.onPrimary, ), ), ), @@ -126,11 +123,11 @@ class AddStaffPage extends StatelessWidget { ); } - Widget _buildSectionTitle(String title) { + Widget _buildSectionTitle(String title, ColorScheme cs) { return Text( title, - style: const TextStyle( - color: Color(0xFF7D8590), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.2, @@ -142,28 +139,28 @@ class AddStaffPage extends StatelessWidget { required TextEditingController controller, required String label, required IconData icon, - required Color fillColor, + required ColorScheme cs, String? hint, bool obscureText = false, TextInputType keyboardType = TextInputType.text, }) { return Container( decoration: BoxDecoration( - color: fillColor, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white.withOpacity(0.05)), + border: Border.all(color: cs.outline.withValues(alpha: 0.1)), ), child: TextFormField( controller: controller, obscureText: obscureText, keyboardType: keyboardType, - style: const TextStyle(color: Colors.white), + style: TextStyle(color: cs.onSurface), decoration: InputDecoration( labelText: label, hintText: hint, - hintStyle: const TextStyle(color: Colors.white24), - labelStyle: const TextStyle(color: Colors.white54), - prefixIcon: Icon(icon, color: Colors.white38), + hintStyle: TextStyle(color: cs.onSurface.withValues(alpha: 0.24)), + labelStyle: TextStyle(color: cs.onSurfaceVariant), + prefixIcon: Icon(icon, color: cs.onSurfaceVariant), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), @@ -177,22 +174,22 @@ class AddStaffPage extends StatelessWidget { required String value, required List items, required Function(String?) onChanged, - required Color fillColor, + required ColorScheme cs, }) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( - color: fillColor, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white.withOpacity(0.05)), + border: Border.all(color: cs.outline.withValues(alpha: 0.1)), ), child: DropdownButtonFormField( initialValue: value, - dropdownColor: fillColor, - style: const TextStyle(color: Colors.white), + dropdownColor: cs.surfaceContainerHighest, + style: TextStyle(color: cs.onSurface), decoration: InputDecoration( labelText: label, - labelStyle: const TextStyle(color: Colors.white54), + labelStyle: TextStyle(color: cs.onSurfaceVariant), border: InputBorder.none, ), items: items.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(), diff --git a/siro_admin/lib/views/admin/staff/pending_admins_page.dart b/siro_admin/lib/views/admin/staff/pending_admins_page.dart index 23167df9..08e20901 100644 --- a/siro_admin/lib/views/admin/staff/pending_admins_page.dart +++ b/siro_admin/lib/views/admin/staff/pending_admins_page.dart @@ -50,7 +50,7 @@ class _PendingAdminsPageState extends State { ); if (response != 'failure') { mySnackbarSuccess('تم تنفيذ الإجراء بنجاح'); - _fetchPendingAdmins(); // تحديث القائمة + _fetchPendingAdmins(); } } catch (e) { mySnackbarError('حدث خطأ: $e'); @@ -59,35 +59,37 @@ class _PendingAdminsPageState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Scaffold( - backgroundColor: const Color(0xFF0A0D14), + backgroundColor: cs.surface, appBar: AppBar( title: const Text('طلبات الانضمام المعلقة', style: TextStyle(fontWeight: FontWeight.bold)), - backgroundColor: const Color(0xFF161D2E), + backgroundColor: cs.surfaceContainerHighest, elevation: 0, ), body: _isLoading - ? const Center(child: CircularProgressIndicator(color: Color(0xFF00E5FF))) + ? Center(child: CircularProgressIndicator(color: cs.primary)) : _pendingAdmins.isEmpty - ? _buildEmptyState() - : _buildList(), + ? _buildEmptyState(cs) + : _buildList(cs), ); } - Widget _buildEmptyState() { + Widget _buildEmptyState(ColorScheme cs) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.person_search_rounded, size: 80, color: Colors.grey[800]), + Icon(Icons.person_search_rounded, size: 80, color: cs.outline), const SizedBox(height: 16), - const Text('لا توجد طلبات معلقة حالياً', style: TextStyle(color: Colors.grey)), + Text('لا توجد طلبات معلقة حالياً', style: TextStyle(color: cs.onSurfaceVariant)), ], ), ); } - Widget _buildList() { + Widget _buildList(ColorScheme cs) { return ListView.builder( padding: const EdgeInsets.all(16), itemCount: _pendingAdmins.length, @@ -97,40 +99,40 @@ class _PendingAdminsPageState extends State { margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: const Color(0xFF161D2E), + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xFF1F2D4A)), + border: Border.all(color: cs.outline), ), child: Column( children: [ ListTile( contentPadding: EdgeInsets.zero, - leading: const CircleAvatar( - backgroundColor: Color(0xFF1F2D4A), - child: Icon(Icons.person, color: Color(0xFF00E5FF)), + leading: CircleAvatar( + backgroundColor: cs.outline, + child: Icon(Icons.person, color: cs.primary), ), - title: Text(admin['name'] ?? 'بدون اسم', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), - subtitle: Text(admin['phone'] ?? 'بدون رقم', style: const TextStyle(color: Colors.grey)), + title: Text(admin['name'] ?? 'بدون اسم', style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.bold)), + subtitle: Text(admin['phone'] ?? 'بدون رقم', style: TextStyle(color: cs.onSurfaceVariant)), trailing: Text( admin['created_at']?.split(' ')[0] ?? '', - style: const TextStyle(color: Colors.grey, fontSize: 12), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12), ), ), - const Divider(color: Color(0xFF1F2D4A), height: 24), + Divider(color: cs.outline, height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => _handleAction(admin['id'], 'rejected'), - style: TextButton.styleFrom(foregroundColor: Colors.redAccent), + style: TextButton.styleFrom(foregroundColor: cs.error), child: const Text('رفض'), ), const SizedBox(width: 8), ElevatedButton( onPressed: () => _handleAction(admin['id'], 'approved'), style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF00E5FF), - foregroundColor: Colors.black, + backgroundColor: cs.primary, + foregroundColor: cs.onPrimary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), diff --git a/siro_admin/lib/views/admin/static/advanced_analytics_page.dart b/siro_admin/lib/views/admin/static/advanced_analytics_page.dart index 8f2a5bfb..8c37fdc3 100644 --- a/siro_admin/lib/views/admin/static/advanced_analytics_page.dart +++ b/siro_admin/lib/views/admin/static/advanced_analytics_page.dart @@ -1,7 +1,6 @@ import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/colors.dart'; import 'package:siro_admin/controller/admin/analytics_v2_controller.dart'; class AdvancedAnalyticsPage extends StatelessWidget { @@ -10,108 +9,250 @@ class AdvancedAnalyticsPage extends StatelessWidget { @override Widget build(BuildContext context) { final controller = Get.put(AnalyticsV2Controller()); + final cs = Theme.of(context).colorScheme; return Scaffold( - backgroundColor: AppColor.bg, - appBar: AppBar( - title: const Text('التحليلات المتقدمة', - style: TextStyle(fontWeight: FontWeight.bold)), - backgroundColor: AppColor.surface, - elevation: 0, - centerTitle: true, - actions: [ - IconButton( - icon: const Icon(Icons.refresh_rounded), - onPressed: () => controller.fetchAllAnalytics(), - ) + backgroundColor: cs.surface, + body: Column( + children: [ + _buildAppBar(context, cs), + Expanded( + child: GetBuilder( + builder: (ctrl) { + if (ctrl.isLoading) { + return _buildLoadingState(cs); + } + + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSummarySection(ctrl.revenueData['summary'], cs), + const SizedBox(height: 24), + _buildSectionTitle('إيرادات آخر 30 يوم', cs), + _buildRevenueChart(ctrl.revenueData['daily'] ?? [], cs), + const SizedBox(height: 32), + _buildSectionTitle('نمو المستخدمين (آخر 30 يوم)', cs), + _buildGrowthChart(ctrl.growthData, cs), + const SizedBox(height: 32), + _buildSectionTitle('أفضل 10 سائقين (حسب الرحلات)', cs), + _buildTopDriversList(ctrl.topDrivers, cs), + const SizedBox(height: 40), + ], + ), + ); + }, + ), + ), ], ), - body: GetBuilder( - builder: (ctrl) { - if (ctrl.isLoading) { - return const Center( - child: CircularProgressIndicator(color: AppColor.accent)); - } + ); + } - return SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSummarySection(ctrl.revenueData['summary']), - const SizedBox(height: 24), - _buildSectionTitle('إيرادات آخر 30 يوم'), - _buildRevenueChart(ctrl.revenueData['daily'] ?? []), - const SizedBox(height: 32), - _buildSectionTitle('نمو المستخدمين (آخر 30 يوم)'), - _buildGrowthChart(ctrl.growthData), - const SizedBox(height: 32), - _buildSectionTitle('أفضل 10 سائقين (حسب الرحلات)'), - _buildTopDriversList(ctrl.topDrivers), - const SizedBox(height: 40), - ], + Widget _buildAppBar(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), ), - ); - }, + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.analytics_rounded, + color: cs.primary, size: 18), + ), + const SizedBox(width: 10), + Text( + 'التحليلات المتقدمة', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + const Spacer(), + GestureDetector( + onTap: () => Get.find().fetchAllAnalytics(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.refresh_rounded, + color: cs.onSurfaceVariant, size: 18), + ), + ), + ], ), ); } - Widget _buildSectionTitle(String title) { + Widget _buildLoadingState(ColorScheme cs) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: cs.primary, + strokeWidth: 2, + backgroundColor: cs.primary.withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text('جاري تحميل التحليلات...', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)), + ], + ), + ); + } + + Widget _buildSectionTitle(String title, ColorScheme cs) { return Padding( padding: const EdgeInsets.only(bottom: 16), - child: Text( - title, - style: const TextStyle( - color: AppColor.textPrimary, - fontSize: 16, - fontWeight: FontWeight.bold, - ), + child: Row( + children: [ + Container( + width: 3, + height: 16, + decoration: BoxDecoration( + color: cs.primary, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 10), + Text( + title, + style: TextStyle( + color: cs.onSurface, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + ], ), ); } - Widget _buildSummarySection(Map? summary) { + Widget _buildSummarySection( + Map? summary, ColorScheme cs) { if (summary == null) return const SizedBox(); return Row( children: [ - _buildSummaryCard('إجمالي الإيرادات', - '${summary['total_revenue_all'] ?? 0}', AppColor.info), + _buildSummaryCard( + 'إجمالي الإيرادات', + '${summary['total_revenue_all'] ?? 0}', + cs.tertiary, + Icons.attach_money_rounded, + cs, + ), const SizedBox(width: 12), - _buildSummaryCard('صافي الربح', '${summary['total_profit_all'] ?? 0}', - AppColor.success), + _buildSummaryCard( + 'صافي الربح', + '${summary['total_profit_all'] ?? 0}', + const Color(0xFF10B981), + Icons.trending_up_rounded, + cs, + ), ], ); } - Widget _buildSummaryCard(String title, String value, Color color) { + Widget _buildSummaryCard( + String title, String value, Color color, IconData icon, ColorScheme cs) { return Expanded( child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withOpacity(0.3)), + border: Border.all(color: color.withValues(alpha: 0.2)), + boxShadow: [ + BoxShadow( + color: color.withValues(alpha: 0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 12)), - const SizedBox(height: 8), - Text(value, - style: TextStyle( - color: color, fontSize: 22, fontWeight: FontWeight.bold)), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: color.withValues(alpha: 0.25)), + ), + child: Icon(icon, color: color, size: 18), + ), + const SizedBox(height: 12), + Text( + title, + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 12), + ), + const SizedBox(height: 6), + Text( + value, + style: TextStyle( + color: color, + fontSize: 22, + fontWeight: FontWeight.w800, + height: 1, + ), + ), ], ), ), ); } - Widget _buildRevenueChart(List daily) { - if (daily.isEmpty) return const Center(child: Text('لا توجد بيانات')); + Widget _buildRevenueChart(List daily, ColorScheme cs) { + if (daily.isEmpty) { + return Container( + height: 200, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + ), + child: Center( + child: Text('لا توجد بيانات', + style: TextStyle(color: cs.onSurfaceVariant)), + ), + ); + } List revenueSpots = []; List profitSpots = []; @@ -127,71 +268,131 @@ class AdvancedAnalyticsPage extends StatelessWidget { height: 300, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColor.surface, - borderRadius: BorderRadius.circular(24), + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + boxShadow: [ + BoxShadow( + color: cs.shadow.withValues(alpha: 0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], ), - child: LineChart( - LineChartData( - gridData: FlGridData( - show: true, - drawVerticalLine: false, - getDrawingHorizontalLine: (v) => - FlLine(color: AppColor.divider, strokeWidth: 1)), - titlesData: FlTitlesData( - show: true, - rightTitles: - const AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: - const AxisTitles(sideTitles: SideTitles(showTitles: false)), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (val, meta) { - if (val.toInt() % 7 == 0 && val.toInt() < daily.length) { - return Text( - daily[val.toInt()]['date'].toString().substring(8), - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 10)); - } - return const SizedBox(); - }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + _LegendDot(color: cs.tertiary, label: 'الإيرادات'), + const SizedBox(width: 16), + _LegendDot(color: const Color(0xFF10B981), label: 'الربح'), + ], + ), + const SizedBox(height: 16), + Expanded( + child: Directionality( + textDirection: TextDirection.ltr, + child: LineChart( + LineChartData( + gridData: FlGridData( + show: true, + drawVerticalLine: false, + getDrawingHorizontalLine: (v) => FlLine( + color: cs.outline.withValues(alpha: 0.5), + strokeWidth: 1), + ), + titlesData: FlTitlesData( + show: true, + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (v, _) => Text( + v >= 1000 ? '${(v / 1000).toStringAsFixed(0)}k' : v.toInt().toString(), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 9), + ), + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 24, + getTitlesWidget: (val, meta) { + if (val.toInt() % 7 == 0 && + val.toInt() < daily.length) { + return Text( + daily[val.toInt()]['date'] + .toString() + .substring(8), + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 9), + ); + } + return const SizedBox(); + }, + ), + ), + ), + borderData: FlBorderData(show: false), + lineBarsData: [ + LineChartBarData( + spots: revenueSpots, + isCurved: true, + color: cs.tertiary, + barWidth: 3, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + color: cs.tertiary.withValues(alpha: 0.08), + ), + ), + LineChartBarData( + spots: profitSpots, + isCurved: true, + color: const Color(0xFF10B981), + barWidth: 3, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + color: const Color(0xFF10B981).withValues(alpha: 0.08), + ), + ), + ], + ), ), ), ), - borderData: FlBorderData(show: false), - lineBarsData: [ - LineChartBarData( - spots: revenueSpots, - isCurved: true, - color: AppColor.info, - barWidth: 3, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData( - show: true, color: AppColor.info.withOpacity(0.1)), - ), - LineChartBarData( - spots: profitSpots, - isCurved: true, - color: AppColor.success, - barWidth: 3, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData( - show: true, color: AppColor.success.withOpacity(0.1)), - ), - ], - ), + ], ), ); } - Widget _buildGrowthChart(Map data) { + Widget _buildGrowthChart(Map data, ColorScheme cs) { final passengers = data['passenger_daily'] as List? ?? []; final drivers = data['driver_daily'] as List? ?? []; if (passengers.isEmpty && drivers.isEmpty) { - return const Center(child: Text('لا توجد بيانات')); + return Container( + height: 200, + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + ), + child: Center( + child: Text('لا توجد بيانات', + style: TextStyle(color: cs.onSurfaceVariant)), + ), + ); } List barGroups = []; @@ -211,59 +412,118 @@ class AdvancedAnalyticsPage extends StatelessWidget { x: i, barRods: [ BarChartRodData( - toY: pCount, - color: AppColor.info, - width: 8, - borderRadius: BorderRadius.circular(4)), + toY: pCount, + color: cs.tertiary.withValues(alpha: 0.7), + width: 8, + borderRadius: BorderRadius.circular(4), + ), BarChartRodData( - toY: dCount, - color: AppColor.warning, - width: 8, - borderRadius: BorderRadius.circular(4)), + toY: dCount, + color: const Color(0xFFF59E0B).withValues(alpha: 0.7), + width: 8, + borderRadius: BorderRadius.circular(4), + ), ], ), ); } return Container( - height: 250, + height: 280, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColor.surface, - borderRadius: BorderRadius.circular(24), + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + boxShadow: [ + BoxShadow( + color: cs.shadow.withValues(alpha: 0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], ), - child: BarChart( - BarChartData( - barGroups: barGroups, - borderData: FlBorderData(show: false), - titlesData: FlTitlesData( - show: true, - rightTitles: - const AxisTitles(sideTitles: SideTitles(showTitles: false)), - topTitles: - const AxisTitles(sideTitles: SideTitles(showTitles: false)), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - getTitlesWidget: (val, meta) { - if (val.toInt() % 7 == 0 && val.toInt() < passengers.length) { - return Text( - passengers[val.toInt()]['date'].toString().substring(8), - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 10)); - } - return const SizedBox(); - }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + _LegendDot(color: cs.tertiary, label: 'ركاب جدد'), + const SizedBox(width: 16), + _LegendDot( + color: const Color(0xFFF59E0B), label: 'سائقين جدد'), + ], + ), + const SizedBox(height: 16), + Expanded( + child: Directionality( + textDirection: TextDirection.ltr, + child: BarChart( + BarChartData( + barGroups: barGroups, + borderData: FlBorderData(show: false), + titlesData: FlTitlesData( + show: true, + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false)), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: (v, _) => Text( + v.toInt().toString(), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 9), + ), + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 24, + getTitlesWidget: (val, meta) { + if (val.toInt() % 7 == 0 && + val.toInt() < passengers.length) { + return Text( + passengers[val.toInt()]['date'] + .toString() + .substring(8), + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 9), + ); + } + return const SizedBox(); + }, + ), + ), + ), + ), ), ), ), - ), + ], ), ); } - Widget _buildTopDriversList(List drivers) { - if (drivers.isEmpty) return const Center(child: Text('لا توجد بيانات')); + Widget _buildTopDriversList(List drivers, ColorScheme cs) { + if (drivers.isEmpty) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), + ), + child: Center( + child: Text('لا توجد بيانات', + style: TextStyle(color: cs.onSurfaceVariant)), + ), + ); + } return ListView.builder( shrinkWrap: true, @@ -271,47 +531,105 @@ class AdvancedAnalyticsPage extends StatelessWidget { itemCount: drivers.length, itemBuilder: (ctx, i) { final d = drivers[i]; + final isTop3 = i < 3; + final rankColors = [ + const Color(0xFFF59E0B), + cs.onSurfaceVariant, + const Color(0xFFCD7F32), + ]; + return Container( - margin: const EdgeInsets.only(bottom: 12), + margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: AppColor.surface, + color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isTop3 + ? rankColors[i].withValues(alpha: 0.2) + : cs.outline, + ), ), child: Row( children: [ - CircleAvatar( - backgroundColor: AppColor.accent.withOpacity(0.1), - child: Text('${i + 1}', - style: const TextStyle( - color: AppColor.accent, fontWeight: FontWeight.bold)), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: isTop3 + ? rankColors[i].withValues(alpha: 0.12) + : cs.outline, + shape: BoxShape.circle, + border: Border.all( + color: isTop3 + ? rankColors[i].withValues(alpha: 0.3) + : Colors.transparent, + ), + ), + child: Center( + child: Text( + '${i + 1}', + style: TextStyle( + color: isTop3 ? rankColors[i] : cs.onSurfaceVariant, + fontWeight: FontWeight.w800, + fontSize: 14, + ), + ), + ), ), - const SizedBox(width: 16), + const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('${d['first_name']} ${d['last_name']}', - style: const TextStyle( - color: AppColor.textPrimary, - fontWeight: FontWeight.bold)), - Text(d['phone'] ?? '', - style: const TextStyle( - color: AppColor.textSecondary, fontSize: 12)), + Text( + '${d['first_name']} ${d['last_name']}', + style: TextStyle( + color: cs.onSurface, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + const SizedBox(height: 3), + Text( + d['phone'] ?? '', + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 11, + fontFamily: 'monospace', + ), + ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text('${d['completed_rides']} رحلة', - style: const TextStyle( - color: AppColor.info, fontWeight: FontWeight.bold)), - Text('${d['total_revenue']} ل.س', - style: const TextStyle( - color: AppColor.success, - fontSize: 12, - fontWeight: FontWeight.bold)), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: cs.tertiary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + '${d['completed_rides']} رحلة', + style: TextStyle( + color: cs.tertiary, + fontWeight: FontWeight.w700, + fontSize: 12, + ), + ), + ), + const SizedBox(height: 4), + Text( + '${d['total_revenue']} ل.س', + style: TextStyle( + color: const Color(0xFF10B981), + fontSize: 11, + fontWeight: FontWeight.w600, + ), + ), ], ), ], @@ -321,3 +639,36 @@ class AdvancedAnalyticsPage extends StatelessWidget { ); } } + +class _LegendDot extends StatelessWidget { + final Color color; + final String label; + + const _LegendDot({required this.color, required this.label}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 6), + Text( + label, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontSize: 11, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } +} diff --git a/siro_admin/lib/views/admin/static/static.dart b/siro_admin/lib/views/admin/static/static.dart index 0c770b2e..52b41174 100644 --- a/siro_admin/lib/views/admin/static/static.dart +++ b/siro_admin/lib/views/admin/static/static.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart' hide TextDirection; -import 'package:siro_admin/constant/theme.dart'; import 'package:siro_admin/controller/admin/static_controller.dart'; import 'notes_driver_page.dart'; diff --git a/siro_admin/lib/views/admin/wallet/wallet.dart b/siro_admin/lib/views/admin/wallet/wallet.dart index 3b646551..dafb77eb 100644 --- a/siro_admin/lib/views/admin/wallet/wallet.dart +++ b/siro_admin/lib/views/admin/wallet/wallet.dart @@ -1,81 +1,255 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/style.dart'; -import 'package:siro_admin/views/widgets/elevated_btn.dart'; -import 'package:siro_admin/views/widgets/mycircular.dart'; - import '../../../controller/admin/wallet_admin_controller.dart'; -import '../../widgets/my_scafold.dart'; +import '../../widgets/mycircular.dart'; class Wallet extends StatelessWidget { Wallet({super.key}); final WalletAdminController walletAdminController = Get.put(WalletAdminController()); + @override Widget build(BuildContext context) { - return MyScafolld( - title: 'Wallet'.tr, - body: [ - GetBuilder(builder: (walletAdminController) { - return Center( - child: walletAdminController.isLoading - ? const MyCircularProgressIndicator() - : Column( - children: [ - MyElevatedButton( - title: 'Pay to them to banks'.tr, - onPressed: () async { - await walletAdminController.payToBankDriverAll(); - }), - SizedBox( - height: Get.height * .8, - child: ListView.builder( - itemCount: - walletAdminController.driversWalletPoints.length, - itemBuilder: (BuildContext context, int index) { - var res = walletAdminController - .driversWalletPoints[index]; + final cs = Theme.of(context).colorScheme; - if (res != null && res['name_arabic'] != null) { - return Padding( - padding: const EdgeInsets.all(4.0), - child: Container( - decoration: AppStyle.boxDecoration1, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - 'driver name: ${res['name_arabic'].toString()}'), - Text( - 'Amount: ${res['total_amount'].toString()}'), - ], - ), - ), - ), - ); - } else { - return Container(); // Return an empty container if the data is null - } - }, - ), - ) - ], + return Scaffold( + backgroundColor: cs.surface, + body: Column( + children: [ + _buildAppBar(context, cs), + Expanded( + child: GetBuilder( + builder: (controller) { + if (controller.isLoading) { + return _buildLoadingState(cs); + } + + if (controller.driversWalletPoints.isEmpty) { + return _buildEmptyState(cs); + } + + return Column( + children: [ + _buildPayAllButton(context, controller, cs), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 80), + physics: const BouncingScrollPhysics(), + itemCount: controller.driversWalletPoints.length, + separatorBuilder: (_, __) => const SizedBox(height: 10), + itemBuilder: (context, index) { + var res = controller.driversWalletPoints[index]; + if (res != null && res['name_arabic'] != null) { + return _buildWalletCard(res, cs); + } + return const SizedBox.shrink(); + }, + ), + ), + ], + ); + }, + ), + ), + ], + ), + ); + } + + Widget _buildAppBar(BuildContext context, ColorScheme cs) { + return Container( + padding: const EdgeInsets.fromLTRB(16, 50, 16, 16), + decoration: BoxDecoration( + color: cs.surface, + border: Border(bottom: BorderSide(color: cs.outline)), + ), + child: Row( + children: [ + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), + ), + ), + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.primary.withValues(alpha: 0.25)), + ), + child: Icon(Icons.account_balance_wallet_rounded, + color: cs.primary, size: 18), + ), + const SizedBox(width: 10), + Text( + 'المحافظ المالية', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + const Spacer(), + GestureDetector( + onTap: () async { + walletAdminController.getWalletForEachDriverToPay(); + }, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(10), + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.refresh_rounded, + color: cs.onSurfaceVariant, size: 18), + ), + ), + ], + ), + ); + } + + Widget _buildLoadingState(ColorScheme cs) { + return const Center(child: MyCircularProgressIndicator()); + } + + Widget _buildEmptyState(ColorScheme cs) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon(Icons.account_balance_wallet_rounded, + size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text('لا توجد محافظ مالية', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)), + ], + ), + ); + } + + Widget _buildPayAllButton( + BuildContext context, WalletAdminController controller, ColorScheme cs) { + return Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), + child: SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton.icon( + icon: const Icon(Icons.send_rounded, color: Colors.white, size: 18), + label: Text( + 'دفع الجميع للبنوك', + style: const TextStyle( + color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14), + ), + style: ElevatedButton.styleFrom( + backgroundColor: cs.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14)), + elevation: 0, + ), + onPressed: () async { + await controller.payToBankDriverAll(); + }, + ), + ), + ); + } + + Widget _buildWalletCard(dynamic res, ColorScheme cs) { + final String name = res['name_arabic']?.toString() ?? 'بدون اسم'; + final String amount = res['total_amount']?.toString() ?? '0'; + + return Container( + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), + ), + child: Padding( + padding: const EdgeInsets.all(14), + child: Row( + children: [ + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFF10B981).withValues(alpha: 0.20), + const Color(0xFF10B981).withValues(alpha: 0.08), + ], + ), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.2)), + ), + child: Icon(Icons.person_rounded, color: const Color(0xFF10B981), size: 22), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: TextStyle( + color: cs.onSurface, + fontWeight: FontWeight.w600, + fontSize: 14, + ), ), - ); - }) - ], - isleading: true, - action: IconButton( - onPressed: () async { - walletAdminController.getWalletForEachDriverToPay(); - }, - icon: const Icon( - Icons.refresh, - color: Colors.black, + const SizedBox(height: 3), + Text( + 'سائق', + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 11, + ), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + amount, + style: TextStyle( + color: const Color(0xFF10B981), + fontWeight: FontWeight.w800, + fontSize: 18, + ), + ), + Text( + 'ل.س', + style: TextStyle( + color: const Color(0xFF10B981).withValues(alpha: 0.6), + fontSize: 10, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], ), ), ); diff --git a/siro_admin/lib/views/invoice/invoice_list_page.dart b/siro_admin/lib/views/invoice/invoice_list_page.dart index 1405bed3..71c5e0ff 100644 --- a/siro_admin/lib/views/invoice/invoice_list_page.dart +++ b/siro_admin/lib/views/invoice/invoice_list_page.dart @@ -6,9 +6,6 @@ import '../../print.dart'; import '../widgets/snackbar.dart'; import 'add_invoice_page.dart'; -// نفترض أن هذا الموديل موجود في مشروعك، إذا لم يكن موجوداً يرجى إضافته أو تعديل الاستيراد -// import '../../model/invoice_model.dart'; - class InvoiceListPage extends StatefulWidget { const InvoiceListPage({super.key}); @@ -17,17 +14,11 @@ class InvoiceListPage extends StatefulWidget { } class _InvoiceListPageState extends State { - List invoices = []; // استخدام dynamic لتجنب مشاكل الموديل إذا اختلف + List invoices = []; int totalCount = 0; double totalAmount = 0.0; bool isLoading = true; - // الألوان "الإيجابية" للتصميم الجديد - final Color primaryColor = const Color(0xFF4F46E5); // Indigo - final Color secondaryColor = const Color(0xFF818CF8); // Lighter Indigo - final Color moneyColor = const Color(0xFF059669); // Emerald Green - final Color bgColor = const Color(0xFFF3F4F6); // Light Gray Background - @override void initState() { super.initState(); @@ -46,7 +37,7 @@ class _InvoiceListPageState extends State { final data = response; if (mounted) { setState(() { - invoices = data['data']; // استخدام البيانات مباشرة + invoices = data['data']; totalCount = int.tryParse(data['summary']['count'].toString()) ?? 0; totalAmount = double.tryParse(data['summary']['total'].toString()) ?? 0.0; @@ -66,6 +57,7 @@ class _InvoiceListPageState extends State { } void _showImageDialog(BuildContext context, String imageUrl) { + final cs = Theme.of(context).colorScheme; showDialog( context: context, builder: (_) => Dialog( @@ -75,8 +67,9 @@ class _InvoiceListPageState extends State { children: [ Container( decoration: BoxDecoration( - color: Colors.white, + color: cs.surface, borderRadius: BorderRadius.circular(20), + border: Border.all(color: cs.outline), ), padding: const EdgeInsets.all(5), child: ClipRRect( @@ -93,16 +86,16 @@ class _InvoiceListPageState extends State { height: 200, width: 200, child: Center( - child: CircularProgressIndicator(color: primaryColor), + child: CircularProgressIndicator(color: cs.primary), ), ); }, errorBuilder: (context, error, stackTrace) { - return const SizedBox( + return SizedBox( height: 150, width: 150, - child: Icon(Icons.broken_image, - size: 60, color: Colors.grey), + child: Icon(Icons.broken_image_rounded, + size: 60, color: cs.onSurfaceVariant), ); }, ), @@ -112,11 +105,17 @@ class _InvoiceListPageState extends State { Positioned( top: 0, right: 0, - child: CircleAvatar( - backgroundColor: Colors.white, - child: IconButton( - icon: const Icon(Icons.close, color: Colors.black), - onPressed: () => Navigator.pop(context), + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + border: Border.all(color: cs.outline), + ), + child: Icon(Icons.close_rounded, + color: cs.onSurfaceVariant, size: 18), ), ), ), @@ -128,17 +127,18 @@ class _InvoiceListPageState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Scaffold( - backgroundColor: bgColor, - // زر عائم بتصميم متدرج + backgroundColor: cs.surface, floatingActionButton: Container( decoration: BoxDecoration( - gradient: LinearGradient(colors: [primaryColor, secondaryColor]), - borderRadius: BorderRadius.circular(30), + gradient: LinearGradient(colors: [cs.primary, cs.primary.withValues(alpha: 0.7)]), + borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: primaryColor.withOpacity(0.4), - blurRadius: 10, + color: cs.primary.withValues(alpha: 0.3), + blurRadius: 12, offset: const Offset(0, 4)) ], ), @@ -146,32 +146,29 @@ class _InvoiceListPageState extends State { onPressed: () => Get.to(() => AddInvoicePage()), label: const Text('إضافة فاتورة', style: TextStyle(fontWeight: FontWeight.bold)), - icon: const Icon(Icons.add), + icon: const Icon(Icons.add_rounded), backgroundColor: Colors.transparent, elevation: 0, ), ), body: Column( children: [ - // 1. رأس الصفحة (Header & Summary) - _buildHeader(), - - // 2. قائمة الفواتير + _buildHeader(cs), Expanded( child: isLoading - ? Center(child: CircularProgressIndicator(color: primaryColor)) + ? _buildLoadingState(cs) : invoices.isEmpty - ? _buildEmptyState() + ? _buildEmptyState(cs) : RefreshIndicator( onRefresh: fetchInvoices, - color: primaryColor, + color: cs.primary, child: ListView.builder( padding: const EdgeInsets.fromLTRB(16, 0, 16, 80), itemCount: invoices.length, physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { final invoice = invoices[index]; - return _buildInvoiceCard(invoice); + return _buildInvoiceCard(invoice, cs); }, ), ), @@ -181,13 +178,12 @@ class _InvoiceListPageState extends State { ); } - // === تصميم الهيدر (رأس الصفحة) === - Widget _buildHeader() { + Widget _buildHeader(ColorScheme cs) { return Container( width: double.infinity, padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 20, - bottom: 30, + bottom: 24, left: 20, right: 20, ), @@ -195,15 +191,15 @@ class _InvoiceListPageState extends State { gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [primaryColor, const Color(0xFF6366F1)], + colors: [cs.primary, cs.primary.withValues(alpha: 0.7)], ), borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(30), - bottomRight: Radius.circular(30), + bottomLeft: Radius.circular(24), + bottomRight: Radius.circular(24), ), boxShadow: [ BoxShadow( - color: primaryColor.withOpacity(0.3), + color: cs.primary.withValues(alpha: 0.2), blurRadius: 20, offset: const Offset(0, 10), ), @@ -211,38 +207,41 @@ class _InvoiceListPageState extends State { ), child: Column( children: [ - // العنوان وزر الرجوع Row( children: [ - IconButton( - icon: const Icon(Icons.arrow_back_ios, - color: Colors.white, size: 20), - onPressed: () => Get.back(), - ), - const Expanded( - child: Text( - "سجل الفواتير", - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold, + GestureDetector( + onTap: () => Get.back(), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(10), ), + child: const Icon(Icons.arrow_back_ios_new_rounded, + color: Colors.white, size: 16), ), ), - const SizedBox(width: 40), // للمحاذاة + const Spacer(), + const Text( + "سجل الفواتير", + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.w700, + ), + ), + const Spacer(), + const SizedBox(width: 34), ], ), - const SizedBox(height: 25), - - // بطاقات الملخص + const SizedBox(height: 20), Row( children: [ Expanded( child: _buildSummaryItem( title: "الإجمالي", value: "${totalAmount.toStringAsFixed(1)} د.أ", - icon: Icons.attach_money, + icon: Icons.attach_money_rounded, isMoney: true, ), ), @@ -251,7 +250,7 @@ class _InvoiceListPageState extends State { child: _buildSummaryItem( title: "عدد الفواتير", value: "$totalCount", - icon: Icons.receipt_long, + icon: Icons.receipt_long_rounded, isMoney: false, ), ), @@ -262,17 +261,18 @@ class _InvoiceListPageState extends State { ); } - Widget _buildSummaryItem( - {required String title, - required String value, - required IconData icon, - required bool isMoney}) { + Widget _buildSummaryItem({ + required String title, + required String value, + required IconData icon, + required bool isMoney, + }) { return Column( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), shape: BoxShape.circle, ), child: Icon(icon, color: Colors.white, size: 20), @@ -282,21 +282,70 @@ class _InvoiceListPageState extends State { value, style: TextStyle( color: isMoney ? const Color(0xFFD1FAE5) : Colors.white, - fontSize: 22, - fontWeight: FontWeight.bold, + fontSize: 20, + fontWeight: FontWeight.w800, ), ), Text( title, - style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 12), + style: const TextStyle( + color: Colors.white, fontSize: 11, fontWeight: FontWeight.w500), ), ], ); } - // === تصميم بطاقة الفاتورة === - Widget _buildInvoiceCard(dynamic invoice) { - // استخراج البيانات بأمان + Widget _buildLoadingState(ColorScheme cs) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 40, + height: 40, + child: CircularProgressIndicator( + color: cs.primary, + strokeWidth: 2, + backgroundColor: cs.primary.withValues(alpha: 0.1), + ), + ), + const SizedBox(height: 16), + Text('جاري تحميل الفواتير...', + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)), + ], + ), + ); + } + + Widget _buildEmptyState(ColorScheme cs) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surfaceContainerHighest, + shape: BoxShape.circle, + ), + child: Icon(Icons.receipt_long_rounded, + size: 48, color: cs.onSurfaceVariant), + ), + const SizedBox(height: 16), + Text("لا توجد فواتير حالياً", + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14)), + const SizedBox(height: 8), + TextButton.icon( + onPressed: fetchInvoices, + icon: const Icon(Icons.refresh_rounded), + label: const Text("تحديث"), + ), + ], + ), + ); + } + + Widget _buildInvoiceCard(dynamic invoice, ColorScheme cs) { String name = invoice['name'] ?? 'بدون اسم'; String amount = invoice['amount']?.toString() ?? '0'; String date = invoice['date'] ?? ''; @@ -304,22 +353,16 @@ class _InvoiceListPageState extends State { String? imageUrl = invoice['imageLink']; return Container( - margin: const EdgeInsets.only(bottom: 16), + margin: const EdgeInsets.only(bottom: 10), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 15, - offset: const Offset(0, 5), - ), - ], + color: cs.surfaceContainerHighest, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline), ), child: Material( color: Colors.transparent, child: InkWell( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(16), onTap: () { if (imageUrl != null && imageUrl.isNotEmpty) { _showImageDialog(context, imageUrl); @@ -328,96 +371,93 @@ class _InvoiceListPageState extends State { } }, child: Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(14), child: Row( children: [ - // 1. أيقونة أو صورة مصغرة Container( - width: 50, - height: 50, + width: 48, + height: 48, decoration: BoxDecoration( - color: primaryColor.withOpacity(0.08), - borderRadius: BorderRadius.circular(15), + color: cs.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: cs.primary.withValues(alpha: 0.15)), ), child: imageUrl != null && imageUrl.isNotEmpty ? ClipRRect( - borderRadius: BorderRadius.circular(15), + borderRadius: BorderRadius.circular(14), child: Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (_, __, ___) => - Icon(Icons.receipt, color: primaryColor), + Icon(Icons.receipt_rounded, color: cs.primary, size: 22), ), ) - : Icon(Icons.receipt_outlined, color: primaryColor), + : Icon(Icons.receipt_outlined, color: cs.primary, size: 22), ), - - const SizedBox(width: 16), - - // 2. التفاصيل + const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - name, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Color(0xFF1F2937), + Expanded( + child: Text( + name, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 15, + color: cs.onSurface, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), - maxLines: 1, - overflow: TextOverflow.ellipsis, ), Container( padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 4), + horizontal: 8, vertical: 3), decoration: BoxDecoration( - color: Colors.grey[100], + color: cs.surface, borderRadius: BorderRadius.circular(8), + border: Border.all(color: cs.outline), ), child: Text( "#$invNumber", style: TextStyle( fontSize: 10, - color: Colors.grey[600], - fontWeight: FontWeight.bold), + color: cs.onSurfaceVariant, + fontWeight: FontWeight.w600), ), ), ], ), - const SizedBox(height: 6), + const SizedBox(height: 4), Text( date, - style: TextStyle(color: Colors.grey[500], fontSize: 12), + style: TextStyle( + color: cs.onSurfaceVariant, fontSize: 11), ), ], ), ), - const SizedBox(width: 12), - - // 3. المبلغ Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( amount, style: TextStyle( - color: moneyColor, - fontWeight: FontWeight.w900, + color: const Color(0xFF10B981), + fontWeight: FontWeight.w800, fontSize: 18, ), ), Text( "د.أ", style: TextStyle( - color: moneyColor.withOpacity(0.7), + color: const Color(0xFF10B981).withValues(alpha: 0.6), fontWeight: FontWeight.w500, - fontSize: 12, + fontSize: 10, ), ), ], @@ -429,26 +469,4 @@ class _InvoiceListPageState extends State { ), ); } - - Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.receipt_long_rounded, size: 80, color: Colors.grey[300]), - const SizedBox(height: 16), - Text( - "لا توجد فواتير حالياً", - style: TextStyle(color: Colors.grey[500], fontSize: 16), - ), - const SizedBox(height: 8), - TextButton.icon( - onPressed: fetchInvoices, - icon: const Icon(Icons.refresh), - label: const Text("تحديث"), - ), - ], - ), - ); - } } diff --git a/siro_admin/lib/views/transit/org_list_page.dart b/siro_admin/lib/views/transit/org_list_page.dart index 2a8dc9d1..d94e8ecf 100644 --- a/siro_admin/lib/views/transit/org_list_page.dart +++ b/siro_admin/lib/views/transit/org_list_page.dart @@ -1,8 +1,6 @@ -// org_list_page.dart — قائمة مؤسسات مواصلاتي (لوحة إدارة سيرو) import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../constant/colors.dart'; import '../../controller/transit/transit_admin_controller.dart'; import '../../controller/transit/transit_admin_models.dart'; import 'org_create_page.dart'; @@ -27,22 +25,25 @@ class _TransitOrgListPageState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return GetBuilder( builder: (c) => Scaffold( - backgroundColor: AppColor.bg, + backgroundColor: cs.surface, appBar: AppBar( - backgroundColor: AppColor.bg, + backgroundColor: cs.surface, elevation: 0, - title: const Text('مواصلاتي — المؤسسات', style: TextStyle(color: AppColor.textPrimary)), + title: Text('مواصلاتي — المؤسسات', + style: TextStyle(color: cs.onSurface)), + iconTheme: IconThemeData(color: cs.onSurface), actions: [ - // زر اعتماد الخطوط IconButton( - icon: const Icon(Icons.route_rounded, color: AppColor.warning), + icon: Icon(Icons.route_rounded, color: cs.tertiary), tooltip: 'اعتماد الخطوط', onPressed: () => Get.to(() => const RouteApprovalPage()), ), IconButton( - icon: const Icon(Icons.add, color: AppColor.accent), + icon: Icon(Icons.add, color: cs.primary), onPressed: () async { final created = await Get.to(() => const TransitOrgCreatePage()); if (created == true) c.fetchOrgs(); @@ -56,17 +57,17 @@ class _TransitOrgListPageState extends State { padding: const EdgeInsets.all(12), child: TextField( controller: _searchCtrl, - style: const TextStyle(color: AppColor.textPrimary), + style: TextStyle(color: cs.onSurface), onSubmitted: (v) { c.searchQuery = v; c.fetchOrgs(); }, decoration: InputDecoration( hintText: 'ابحث عن مؤسسة...', - hintStyle: const TextStyle(color: AppColor.textSecondary), - prefixIcon: const Icon(Icons.search, color: AppColor.textSecondary), + hintStyle: TextStyle(color: cs.onSurfaceVariant), + prefixIcon: Icon(Icons.search, color: cs.onSurfaceVariant), filled: true, - fillColor: AppColor.surface, + fillColor: cs.surfaceContainerHighest, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -76,17 +77,18 @@ class _TransitOrgListPageState extends State { ), Expanded( child: c.isLoadingList - ? const Center(child: CircularProgressIndicator(color: AppColor.accent)) + ? Center(child: CircularProgressIndicator(color: cs.primary)) : c.orgs.isEmpty - ? const Center( - child: Text('لا توجد مؤسسات', style: TextStyle(color: AppColor.textSecondary)), + ? Center( + child: Text('لا توجد مؤسسات', + style: TextStyle(color: cs.onSurfaceVariant)), ) : RefreshIndicator( onRefresh: c.fetchOrgs, child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 12), itemCount: c.orgs.length, - itemBuilder: (_, i) => _orgCard(c.orgs[i]), + itemBuilder: (_, i) => _orgCard(c.orgs[i], cs), ), ), ), @@ -96,30 +98,30 @@ class _TransitOrgListPageState extends State { ); } - Widget _orgCard(TransitOrgSummary org) { + Widget _orgCard(TransitOrgSummary org, ColorScheme cs) { Color statusColor; switch (org.contractStatus) { case 'active': - statusColor = AppColor.success; + statusColor = const Color(0xFF10B981); break; case 'trial': - statusColor = AppColor.info; + statusColor = cs.tertiary; break; case 'suspended': - statusColor = AppColor.warning; + statusColor = cs.tertiary; break; default: - statusColor = AppColor.danger; + statusColor = cs.error; } return Card( - color: AppColor.surface, + color: cs.surfaceContainerHighest, margin: const EdgeInsets.only(bottom: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), title: Text(org.nameAr, - style: const TextStyle(color: AppColor.textPrimary, fontWeight: FontWeight.bold)), + style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.bold)), subtitle: Padding( padding: const EdgeInsets.only(top: 6), child: Row( @@ -127,7 +129,7 @@ class _TransitOrgListPageState extends State { Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( - color: statusColor.withOpacity(0.15), + color: statusColor.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Text(org.contractStatus, @@ -135,21 +137,21 @@ class _TransitOrgListPageState extends State { ), const SizedBox(width: 8), Text('${org.city} · ${org.country}', - style: const TextStyle(color: AppColor.textSecondary, fontSize: 12)), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), const Spacer(), - Icon(Icons.directions_bus, size: 14, color: AppColor.textSecondary), + Icon(Icons.directions_bus, size: 14, color: cs.onSurfaceVariant), const SizedBox(width: 2), Text('${org.vehiclesCount}', - style: const TextStyle(color: AppColor.textSecondary, fontSize: 12)), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(width: 10), - Icon(Icons.people, size: 14, color: AppColor.textSecondary), + Icon(Icons.people, size: 14, color: cs.onSurfaceVariant), const SizedBox(width: 2), Text('${org.activeEnrollments}', - style: const TextStyle(color: AppColor.textSecondary, fontSize: 12)), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), ], ), ), - trailing: const Icon(Icons.arrow_forward_ios, size: 14, color: AppColor.textSecondary), + trailing: Icon(Icons.arrow_forward_ios, size: 14, color: cs.onSurfaceVariant), onTap: () => Get.to(() => TransitOrgDetailsPage(orgId: org.id, orgName: org.nameAr)), ), ); diff --git a/siro_admin/lib/views/widgets/web_sidebar.dart b/siro_admin/lib/views/widgets/web_sidebar.dart index 310b5272..8dc83611 100644 --- a/siro_admin/lib/views/widgets/web_sidebar.dart +++ b/siro_admin/lib/views/widgets/web_sidebar.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:siro_admin/constant/theme.dart'; import 'package:siro_admin/main.dart'; import 'package:siro_admin/constant/box_name.dart'; import 'package:siro_admin/views/admin/captain/captain.dart'; import 'package:siro_admin/views/admin/passenger/passenger.dart'; import 'package:siro_admin/views/admin/drivers/driver_tracker_screen.dart'; import 'package:siro_admin/views/transit/org_list_page.dart'; -import 'package:siro_admin/views/admin/rides/rides.dart'; import 'package:siro_admin/views/admin/static/static.dart'; import 'package:siro_admin/controller/admin/static_controller.dart'; import 'package:siro_admin/views/admin/drivers/monitor_ride.dart';