إصلاح أخطاء شاشة الكباتن من تحديث الذكاء الاصطناعي
This commit is contained in:
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"6c0baaebf70e0148f485f27d5616b3d3382da7
|
|||||||
|
|
||||||
_flutter.loader.load({
|
_flutter.loader.load({
|
||||||
serviceWorkerSettings: {
|
serviceWorkerSettings: {
|
||||||
serviceWorkerVersion: "578408601" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
serviceWorkerVersion: "4149845318" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,466 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"route": "/admin/home",
|
||||||
|
"file": "lib/views/admin/admin_home_page.dart",
|
||||||
|
"title": "Admin Home Page (Dashboard)",
|
||||||
|
"appBarPattern": "SliverAppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "charts|grid|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom AppBar with FlexibleSpaceBar; complex chart implementations; uses DashboardV2Widget"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/analytics/live",
|
||||||
|
"file": "lib/views/admin/analytics/live_analytics_page.dart",
|
||||||
|
"title": "Live Analytics Page",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "charts|map|tables",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Standard AppBar with TabBar; flutter_map integration; shared widget classes for charts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/analytics/advanced",
|
||||||
|
"file": "lib/views/admin/static/advanced_analytics_page.dart",
|
||||||
|
"title": "Advanced Analytics Page",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "charts|lists|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header; Column+SingleChildScrollView instead of CustomScrollView; inline _LegendDot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/static",
|
||||||
|
"file": "lib/views/admin/static/static.dart",
|
||||||
|
"title": "System Statistics (StaticDash)",
|
||||||
|
"appBarPattern": "SliverAppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "charts|lists",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Complex SliverAppBar; extensive custom chart widgets; many inline helper classes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/passenger",
|
||||||
|
"file": "lib/views/admin/passenger/passenger.dart",
|
||||||
|
"title": "Passenger Management",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header; Column+Expanded+GetBuilder pattern; consistent cs usage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/captain",
|
||||||
|
"file": "lib/views/admin/captain/captain.dart",
|
||||||
|
"title": "Captain List",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header; search section separate; ListView.separated with cards"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/drivers/pending",
|
||||||
|
"file": "lib/views/admin/captain/syrian_driver_not_active.dart",
|
||||||
|
"title": "Pending Captain Requests",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Misleading filename; Custom Container header; RefreshIndicator; status chips"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/driver-docs",
|
||||||
|
"file": "lib/views/admin/drivers/driver_documents_review_page.dart",
|
||||||
|
"title": "Driver Documents Review",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards|images",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Standard AppBar; pagination with loadMore; ExpansionTile for doc details with images"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/drivers/gifts",
|
||||||
|
"file": "lib/views/admin/drivers/driver_gift_check_page.dart",
|
||||||
|
"title": "Driver Gifts & Offers",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": true,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "form|log",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Custom Container header; hardcoded Color(0xFFF59E0B); terminal log with hardcoded green Color(0xFF4ADE80)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/quality/blacklist",
|
||||||
|
"file": "lib/views/admin/quality/blacklist_page.dart",
|
||||||
|
"title": "Blacklist Page",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "tabs|lists",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header with TabBar; TabBar in Container not AppBar.bottom; Dialog for unblock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/rides/dashboard",
|
||||||
|
"file": "lib/views/admin/rides/rides.dart",
|
||||||
|
"title": "Rides Dashboard",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "charts|grid|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Custom Container header; SingleChildScrollView+Column; LineChart; GridView for metrics"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/rides/lookup",
|
||||||
|
"file": "lib/views/admin/rides/ride_lookup_page.dart",
|
||||||
|
"title": "Ride Lookup / Management",
|
||||||
|
"appBarPattern": "SliverAppBar",
|
||||||
|
"usesCsColors": false,
|
||||||
|
"hasHardcodedColors": true,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards|map",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "HARDCODED colors (bgColor, primaryColor, textPrimary); no Theme.of(context).colorScheme; complex SliverAppBar; custom ride cards; separate map screen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/drivers/monitor",
|
||||||
|
"file": "lib/views/admin/drivers/monitor_ride.dart",
|
||||||
|
"title": "Live Ride Monitoring",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "map|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom header for search; two views (search + map); loading/error overlays on map; bottom info panel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/drivers/tracker",
|
||||||
|
"file": "lib/views/admin/drivers/driver_tracker_screen.dart",
|
||||||
|
"title": "Captain Tracker (Siro Tracker)",
|
||||||
|
"appBarPattern": "Container-based (Positioned)",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": true,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "map|dashboard",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "HARDCODED dark theme (Color(0xFF0A0E27)); Stack with Positioned overlays; custom dashboard panel; no standard Scaffold/AppBar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/transit/orgs",
|
||||||
|
"file": "lib/views/transit/org_list_page.dart",
|
||||||
|
"title": "Transit Organizations",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Standard AppBar; simple ListView with Cards; RefreshIndicator; clean minimal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/financial/v2",
|
||||||
|
"file": "lib/views/admin/financial/financial_v2_page.dart",
|
||||||
|
"title": "Financial Report V2",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "cards|charts|lists",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Standard AppBar with refresh; SingleChildScrollView+Column; LinearProgressIndicator; ListView.builder shrinkWrap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/wallet",
|
||||||
|
"file": "lib/views/admin/wallet/wallet.dart",
|
||||||
|
"title": "Financial Wallets",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header; Pay All button; custom MyCircularProgressIndicator; consistent cs usage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/packages",
|
||||||
|
"file": "lib/views/admin/packages.dart",
|
||||||
|
"title": "Packages & Subscriptions",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header; Get.dialog for edit; platform icons; controller in same file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/invoice",
|
||||||
|
"file": "lib/views/invoice/invoice_list_page.dart",
|
||||||
|
"title": "Invoice List",
|
||||||
|
"appBarPattern": "Container-based (gradient)",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": true,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards|images",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Gradient Container header (not AppBar); FAB; InteractiveViewer for images; hardcoded Colors.white in header"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/kazan",
|
||||||
|
"file": "lib/views/admin/pricing/kazan_editor_page.dart",
|
||||||
|
"title": "Kazan Pricing Editor",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": true,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "form|grid",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Custom Container header; hardcoded car type colors; GoogleFonts; GridView for km prices; Slider for percentages"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/promo",
|
||||||
|
"file": "lib/views/admin/promo/promo_management_page.dart",
|
||||||
|
"title": "Promo Code Management",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Standard AppBar; BottomSheet forms; PopupMenuButton; date pickers; GoogleFonts; custom widgets MyTextForm/MyElevatedButton"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/marketing",
|
||||||
|
"file": "lib/views/admin/marketing/marketing_page.dart",
|
||||||
|
"title": "Marketing & AI Automation",
|
||||||
|
"appBarPattern": "AppBar (complex bottom)",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "tabs|charts|lists|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "AppBar with PreferredSize bottom (country selector + TabBar); 3 tabs; FlChart charts; inline Card widgets; AI prompt editor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/heatmap",
|
||||||
|
"file": "lib/views/admin/marketing/heatmap_page.dart",
|
||||||
|
"title": "Heatmap Page",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "map|filters",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Custom Container header; FlutterMap with CircleLayer; filter chips; Dio HTTP; legend overlay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/social-intelligence",
|
||||||
|
"file": "lib/views/admin/marketing/social_intelligence_screen.dart",
|
||||||
|
"title": "Social Intelligence",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|html",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header; http package directly; HtmlWidget for HTML; ExpansionTile reports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/complaints",
|
||||||
|
"file": "lib/views/admin/complaints/complaint_list_page.dart",
|
||||||
|
"title": "Complaints Management",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Custom Container header; delayed complaints banner; ExpansionTile; BottomSheet with dropdown; GoogleFonts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/server",
|
||||||
|
"file": "lib/views/admin/server/monitor_server_page.dart",
|
||||||
|
"title": "Server Monitor",
|
||||||
|
"appBarPattern": "SliverAppBar",
|
||||||
|
"usesCsColors": false,
|
||||||
|
"hasHardcodedColors": true,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "cards|charts|lists",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "HARDCODED dark theme (Color(0xFF0A0E27)); no Theme.of(context).colorScheme; SliverAppBar with gradient; LayoutBuilder responsive; all colors hardcoded"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/security/audit-logs",
|
||||||
|
"file": "lib/views/admin/security/audit_logs_page.dart",
|
||||||
|
"title": "Audit Logs",
|
||||||
|
"appBarPattern": "Container-based",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Custom Container header; GetBuilder with SecurityV2Controller; simple ListView; empty state with DB hint"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/employee",
|
||||||
|
"file": "lib/views/admin/employee/employee_page.dart",
|
||||||
|
"title": "Employee Management",
|
||||||
|
"appBarPattern": "SliverAppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards|images",
|
||||||
|
"hasLoadingState": false,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "SliverAppBar with FlexibleSpaceBar; CustomScrollView with Slivers; FAB; image viewers for IDs; form inline in same file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/staff/add",
|
||||||
|
"file": "lib/views/admin/staff/add_staff_page.dart",
|
||||||
|
"title": "Add Staff Page",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "form",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Standard AppBar; form with helper methods; Dropdown for gender/country; role via constructor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/staff/pending",
|
||||||
|
"file": "lib/views/admin/staff/pending_admins_page.dart",
|
||||||
|
"title": "Pending Admins",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": true,
|
||||||
|
"issues": "Standard AppBar with surfaceContainerHighest bg; StatefulWidget with setState; CRUD in widget; action buttons per item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": "/admin/error",
|
||||||
|
"file": "lib/views/admin/error/error/error_page.dart",
|
||||||
|
"title": "Error Logs",
|
||||||
|
"appBarPattern": "AppBar",
|
||||||
|
"usesCsColors": true,
|
||||||
|
"hasHardcodedColors": false,
|
||||||
|
"importsColorsDart": false,
|
||||||
|
"importsThemeDart": false,
|
||||||
|
"dataDisplay": "list|cards",
|
||||||
|
"hasLoadingState": true,
|
||||||
|
"hasEmptyState": false,
|
||||||
|
"issues": "Standard AppBar; search with phone filter; ExpansionTile for details; multiple _buildInfoBadge chips; StatefulWidget; ErrorLog model in same file"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": {
|
||||||
|
"totalPages": 31,
|
||||||
|
"appBarPatterns": {
|
||||||
|
"AppBar": 8,
|
||||||
|
"SliverAppBar": 4,
|
||||||
|
"Container-based (custom)": 19
|
||||||
|
},
|
||||||
|
"colorSystemUsage": {
|
||||||
|
"usesColorScheme": 27,
|
||||||
|
"hasHardcodedColors": 4
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"importsColorsDart": 0,
|
||||||
|
"importsThemeDart": 0
|
||||||
|
},
|
||||||
|
"dataDisplayTypes": {
|
||||||
|
"list": 22,
|
||||||
|
"cards": 20,
|
||||||
|
"charts": 9,
|
||||||
|
"map": 4,
|
||||||
|
"tables": 3,
|
||||||
|
"forms": 5,
|
||||||
|
"images": 3,
|
||||||
|
"html": 1
|
||||||
|
},
|
||||||
|
"loadingStates": 29,
|
||||||
|
"emptyStates": 22
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,15 +40,14 @@ class RideAdminController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future getRidesDetails() async {
|
Future getRidesDetails() async {
|
||||||
// isLoading = true;
|
isLoading = true;
|
||||||
// update();
|
update();
|
||||||
var res = await CRUD().get(link: AppLink.getRidesDetails, payload: {});
|
var res = await CRUD().get(link: AppLink.getRidesDetails, payload: {});
|
||||||
|
|
||||||
var d = jsonDecode(res);
|
var d = jsonDecode(res);
|
||||||
ridesDetails = d['message'];
|
ridesDetails = d['message'];
|
||||||
|
isLoading = false;
|
||||||
// isLoading = false;
|
update();
|
||||||
// update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -176,19 +176,21 @@ class WalletAdminController extends GetxController {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future getWalletForEachDriverToPay() async {
|
Future getWalletForEachDriverToPay() async {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
update();
|
update();
|
||||||
var res = await CRUD()
|
var res = await CRUD()
|
||||||
.postWallet(link: AppLink.getVisaForEachDriver, payload: {});
|
.postWallet(link: AppLink.getVisaForEachDriver, payload: {});
|
||||||
var d = (res);
|
var d = (res);
|
||||||
if (d != 'failure') {
|
if (d != 'failure') {
|
||||||
driversWalletPoints = d['message'];
|
driversWalletPoints = d['message'] ?? [];
|
||||||
|
isLoading = false;
|
||||||
|
update();
|
||||||
|
} else {
|
||||||
|
driversWalletPoints = [];
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
driversWalletPoints = [];
|
}
|
||||||
isLoading = false;
|
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,29 @@ import '../../views/widgets/snackbar.dart';
|
|||||||
import '../functions/crud.dart';
|
import '../functions/crud.dart';
|
||||||
|
|
||||||
class DriverController extends GetxController {
|
class DriverController extends GetxController {
|
||||||
|
bool isLoading = false;
|
||||||
List drivers = [];
|
List drivers = [];
|
||||||
Map driverDetails = {};
|
Map driverDetails = {};
|
||||||
|
|
||||||
// جلب السائقين pending
|
// جلب السائقين pending
|
||||||
getDriversPending() async {
|
getDriversPending() async {
|
||||||
|
isLoading = true;
|
||||||
|
update();
|
||||||
var res = await CRUD().post(
|
var res = await CRUD().post(
|
||||||
link: AppLink.getDriversPending, // رابط drivers_pending_list.php
|
link: AppLink.getDriversPending,
|
||||||
payload: {},
|
payload: {},
|
||||||
);
|
);
|
||||||
if (res != 'failure') {
|
if (res != 'failure') {
|
||||||
drivers = (res)['message'];
|
drivers = (res)['message'];
|
||||||
update(['drivers']); // تحديث الـ UI
|
isLoading = false;
|
||||||
|
update(['drivers']);
|
||||||
} else {
|
} else {
|
||||||
mySnackbarError('Failed to load drivers');
|
mySnackbarError('Failed to load drivers');
|
||||||
|
isLoading = false;
|
||||||
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// جلب تفاصيل سائق واحد
|
// جلب تفاصيل سائق واحد
|
||||||
getDriverDetails(String driverId) async {
|
getDriverDetails(String driverId) async {
|
||||||
|
|||||||
@@ -23,13 +23,16 @@ class DriversPendingPage extends StatelessWidget {
|
|||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildAppBar(context, cs),
|
_buildAppBar(context, cs),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GetBuilder<DriverController>(
|
child: GetBuilder<DriverController>(
|
||||||
id: 'drivers',
|
id: 'drivers',
|
||||||
builder: (c) { if (c.drivers.isEmpty) {
|
builder: (c) {
|
||||||
|
if (c.isLoading) {
|
||||||
|
return _buildLoadingState(cs);
|
||||||
|
}
|
||||||
|
if (c.drivers.isEmpty) {
|
||||||
return _buildEmptyState(cs);
|
return _buildEmptyState(cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () => controller.getDriversPending(),
|
onRefresh: () => controller.getDriversPending(),
|
||||||
color: cs.primary,
|
color: cs.primary,
|
||||||
|
|||||||
@@ -147,13 +147,16 @@ class BlacklistPage extends StatelessWidget {
|
|||||||
itemCount: controller.driversBlacklist.length,
|
itemCount: controller.driversBlacklist.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var driver = controller.driversBlacklist[index];
|
var driver = controller.driversBlacklist[index];
|
||||||
|
final String phone = (driver['phone'] ?? driver['phone_normalized'] ?? driver['mobile'] ?? driver['number'] ?? '').toString();
|
||||||
|
final String reason = (driver['reason'] ?? driver['block_reason'] ?? driver['note'] ?? 'غير محدد').toString();
|
||||||
|
final String date = (driver['created_at'] ?? driver['createdAt'] ?? driver['date'] ?? driver['blocked_at'] ?? '').toString();
|
||||||
return _buildBlacklistItem(
|
return _buildBlacklistItem(
|
||||||
phone: driver['phone'],
|
phone: phone,
|
||||||
reason: driver['reason'] ?? 'غير محدد',
|
reason: reason,
|
||||||
date: driver['created_at'],
|
date: date,
|
||||||
type: 'سائق',
|
type: 'سائق',
|
||||||
icon: Icons.drive_eta_rounded,
|
icon: Icons.drive_eta_rounded,
|
||||||
onUnblock: () => controller.unblockDriver(driver['phone'].toString()),
|
onUnblock: () => controller.unblockDriver(phone),
|
||||||
cs: cs,
|
cs: cs,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -170,14 +173,17 @@ class BlacklistPage extends StatelessWidget {
|
|||||||
itemCount: controller.passengersBlacklist.length,
|
itemCount: controller.passengersBlacklist.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var passenger = controller.passengersBlacklist[index];
|
var passenger = controller.passengersBlacklist[index];
|
||||||
|
final String phone = (passenger['phone'] ?? passenger['phone_normalized'] ?? passenger['mobile'] ?? passenger['number'] ?? '').toString();
|
||||||
|
final String reason = (passenger['reason'] ?? passenger['block_reason'] ?? passenger['note'] ?? 'غير محدد').toString();
|
||||||
|
final String date = (passenger['created_at'] ?? passenger['createdAt'] ?? passenger['date'] ?? passenger['blocked_at'] ?? '').toString();
|
||||||
|
final String phoneForUnblock = (passenger['phone_normalized'] ?? passenger['phone'] ?? passenger['mobile'] ?? passenger['number'] ?? '').toString();
|
||||||
return _buildBlacklistItem(
|
return _buildBlacklistItem(
|
||||||
phone: passenger['phone'] ?? passenger['phone_normalized'],
|
phone: phone,
|
||||||
reason: passenger['reason'] ?? 'غير محدد',
|
reason: reason,
|
||||||
date: passenger['created_at'],
|
date: date,
|
||||||
type: 'راكب',
|
type: 'راكب',
|
||||||
icon: Icons.person_rounded,
|
icon: Icons.person_rounded,
|
||||||
onUnblock: () => controller.unblockPassenger(
|
onUnblock: () => controller.unblockPassenger(phoneForUnblock),
|
||||||
passenger['phone_normalized'].toString()),
|
|
||||||
cs: cs,
|
cs: cs,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -204,11 +204,6 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
late final RidesListController controller;
|
late final RidesListController controller;
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
|
|
||||||
// 🎨 الألوان العصرية
|
|
||||||
final Color bgColor = const Color(0xFFF4F7FE);
|
|
||||||
final Color primaryColor = const Color(0xFF4318FF);
|
|
||||||
final Color textPrimary = const Color(0xFF2B3674);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -232,10 +227,11 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: bgColor,
|
backgroundColor: cs.surface,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false, // نسمح للـ AppBar بالتمدد لأعلى الشاشة
|
top: false,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
@@ -243,16 +239,16 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: false,
|
floating: false,
|
||||||
expandedHeight: 310.0, // ارتفاع الجزء العلوي بالكامل
|
expandedHeight: 310.0,
|
||||||
backgroundColor: primaryColor,
|
backgroundColor: cs.primary,
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
shadowColor: primaryColor.withOpacity(0.4),
|
shadowColor: cs.primary.withValues(alpha: 0.4),
|
||||||
iconTheme: const IconThemeData(color: Colors.white),
|
iconTheme: IconThemeData(color: cs.onPrimary),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: const Text(
|
title: Text(
|
||||||
'إدارة الرحلات',
|
'إدارة الرحلات',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: cs.onPrimary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
),
|
),
|
||||||
@@ -263,16 +259,16 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.2),
|
color: cs.onPrimary.withValues(alpha: 0.2),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: const Text(
|
child: Text(
|
||||||
'اليوم',
|
'اليوم',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.white,
|
color: cs.onPrimary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -281,7 +277,10 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
background: Container(
|
background: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [primaryColor, primaryColor.withOpacity(0.8)],
|
colors: [
|
||||||
|
cs.primary,
|
||||||
|
cs.primary.withValues(alpha: 0.8),
|
||||||
|
],
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
),
|
),
|
||||||
@@ -290,12 +289,12 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
// الإحصائيات تختفي عند التمرير لأعلى
|
// الإحصائيات تختفي عند التمرير لأعلى
|
||||||
_buildStatisticsSection(),
|
_buildStatisticsSection(cs),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// شريط البحث يختفي عند التمرير لأعلى
|
// شريط البحث يختفي عند التمرير لأعلى
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: _buildSearchBar(),
|
child: _buildSearchBar(cs),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 60), // مساحة للـ TabBar بالأسفل
|
const SizedBox(height: 60), // مساحة للـ TabBar بالأسفل
|
||||||
],
|
],
|
||||||
@@ -306,18 +305,17 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size.fromHeight(54),
|
preferredSize: const Size.fromHeight(54),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: cs.surface,
|
||||||
Color(0xFFF4F7FE), // لون خلفية التطبيق ليظهر بشكل مدمج
|
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.vertical(top: Radius.circular(24)),
|
const BorderRadius.vertical(top: Radius.circular(24)),
|
||||||
),
|
),
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
labelColor: primaryColor,
|
labelColor: cs.primary,
|
||||||
unselectedLabelColor: Colors.grey,
|
unselectedLabelColor: cs.onSurfaceVariant,
|
||||||
indicatorColor: primaryColor,
|
indicatorColor: cs.primary,
|
||||||
indicatorWeight: 3,
|
indicatorWeight: 3,
|
||||||
labelStyle: const TextStyle(
|
labelStyle: const TextStyle(
|
||||||
fontWeight: FontWeight.bold, fontSize: 14),
|
fontWeight: FontWeight.bold, fontSize: 14),
|
||||||
@@ -359,13 +357,13 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.inbox_rounded,
|
Icon(Icons.inbox_rounded,
|
||||||
size: 80, color: Colors.grey[400]),
|
size: 80, color: cs.onSurfaceVariant),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'لا توجد رحلات في هذا القسم',
|
'لا توجد رحلات في هذا القسم',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: Colors.grey[600],
|
color: cs.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -380,7 +378,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
(context, index) {
|
(context, index) {
|
||||||
final ride = controller.displayedRides[index];
|
final ride = controller.displayedRides[index];
|
||||||
return _buildRideCardCompact(
|
return _buildRideCardCompact(
|
||||||
ride, controller.isSuperAdmin);
|
ride, controller.isSuperAdmin, cs);
|
||||||
},
|
},
|
||||||
childCount: controller.displayedRides.length,
|
childCount: controller.displayedRides.length,
|
||||||
),
|
),
|
||||||
@@ -395,7 +393,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
|
|
||||||
// --- Components ---
|
// --- Components ---
|
||||||
|
|
||||||
Widget _buildStatisticsSection() {
|
Widget _buildStatisticsSection(ColorScheme cs) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -446,17 +444,17 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatCard(
|
Widget _buildStatCard(
|
||||||
String label, String value, IconData icon, Color iconColor) {
|
String label, String value, IconData icon, Color iconColor, ColorScheme cs) {
|
||||||
return Container(
|
return Container(
|
||||||
width: 105,
|
width: 105,
|
||||||
margin: const EdgeInsets.only(left: 10),
|
margin: const EdgeInsets.only(left: 10),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: cs.surface,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withOpacity(0.1),
|
color: cs.shadow.withValues(alpha: 0.1),
|
||||||
blurRadius: 10,
|
blurRadius: 10,
|
||||||
offset: const Offset(0, 4),
|
offset: const Offset(0, 4),
|
||||||
)
|
)
|
||||||
@@ -470,7 +468,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18, fontWeight: FontWeight.bold, color: textPrimary),
|
fontSize: 18, fontWeight: FontWeight.bold, color: cs.onSurface),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -479,7 +477,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
label,
|
label,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: Colors.grey[600],
|
color: cs.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -487,7 +485,7 @@ class _RidesDashboardScreenState extends State<RidesDashboardScreen>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSearchBar() {
|
Widget _buildSearchBar(ColorScheme cs) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@@ -0,0 +1,483 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class UnifiedAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final String title;
|
||||||
|
final Widget? leading;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final Color? foregroundColor;
|
||||||
|
final double elevation;
|
||||||
|
final bool centerTitle;
|
||||||
|
final PreferredSizeWidget? bottom;
|
||||||
|
final double toolbarHeight;
|
||||||
|
|
||||||
|
const UnifiedAppBar({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
this.leading,
|
||||||
|
this.actions,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.foregroundColor,
|
||||||
|
this.elevation = 0,
|
||||||
|
this.centerTitle = false,
|
||||||
|
this.bottom,
|
||||||
|
this.toolbarHeight = kToolbarHeight + 34,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
final bgColor = backgroundColor ?? cs.surface;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: toolbarHeight,
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bgColor,
|
||||||
|
border: Border(bottom: BorderSide(color: cs.outline, width: 0.5)),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
leading ??
|
||||||
|
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),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: foregroundColor ?? cs.onSurface,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (actions != null) ...actions!,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => Size.fromHeight(toolbarHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnifiedAppBarWithTabs extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final String title;
|
||||||
|
final List<Widget> tabs;
|
||||||
|
final TabController? tabController;
|
||||||
|
final Widget? leading;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final Color? foregroundColor;
|
||||||
|
final double elevation;
|
||||||
|
final EdgeInsetsGeometry? tabPadding;
|
||||||
|
|
||||||
|
const UnifiedAppBarWithTabs({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.tabs,
|
||||||
|
this.tabController,
|
||||||
|
this.leading,
|
||||||
|
this.actions,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.foregroundColor,
|
||||||
|
this.elevation = 0,
|
||||||
|
this.tabPadding,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
final bgColor = backgroundColor ?? cs.surface;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: kToolbarHeight + 34 + 48,
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 50, 16, 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bgColor,
|
||||||
|
border: Border(bottom: BorderSide(color: cs.outline, width: 0.5)),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
leading ??
|
||||||
|
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),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: foregroundColor ?? cs.onSurface,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (actions != null) ...actions!,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
padding: tabPadding ?? const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: cs.surfaceContainerHighest,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(color: cs.outline),
|
||||||
|
),
|
||||||
|
child: TabBar(
|
||||||
|
controller: tabController,
|
||||||
|
isScrollable: true,
|
||||||
|
indicator: BoxDecoration(
|
||||||
|
color: cs.primary.withValues(alpha: 0.15),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
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),
|
||||||
|
tabs: tabs,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight + 34 + 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnifiedScaffold extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final Widget body;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
final Widget? leading;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final bool extendBody;
|
||||||
|
final FloatingActionButton? floatingActionButton;
|
||||||
|
final FloatingActionButtonLocation? floatingActionButtonLocation;
|
||||||
|
final PreferredSizeWidget? bottomNavigationBar;
|
||||||
|
|
||||||
|
const UnifiedScaffold({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.body,
|
||||||
|
this.actions,
|
||||||
|
this.leading,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.extendBody = false,
|
||||||
|
this.floatingActionButton,
|
||||||
|
this.floatingActionButtonLocation,
|
||||||
|
this.bottomNavigationBar,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: backgroundColor ?? cs.surface,
|
||||||
|
extendBody: extendBody,
|
||||||
|
floatingActionButton: floatingActionButton,
|
||||||
|
floatingActionButtonLocation: floatingActionButtonLocation,
|
||||||
|
bottomNavigationBar: bottomNavigationBar,
|
||||||
|
appBar: UnifiedAppBar(
|
||||||
|
title: title,
|
||||||
|
actions: actions,
|
||||||
|
leading: leading,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
),
|
||||||
|
body: body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnifiedLoadingState extends StatelessWidget {
|
||||||
|
final String? message;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
const UnifiedLoadingState({super.key, this.message, this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: color ?? cs.primary,
|
||||||
|
strokeWidth: 2,
|
||||||
|
backgroundColor: (color ?? cs.primary).withValues(alpha: 0.1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
message ?? 'جاري التحميل...',
|
||||||
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnifiedEmptyState extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final String title;
|
||||||
|
final String? subtitle;
|
||||||
|
final Widget? action;
|
||||||
|
|
||||||
|
const UnifiedEmptyState({
|
||||||
|
super.key,
|
||||||
|
required this.icon,
|
||||||
|
required this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.action,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: cs.surfaceContainerHighest,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, size: 48, color: cs.onSurfaceVariant),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
if (subtitle != null) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
subtitle!,
|
||||||
|
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (action != null) ...[
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
action!,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnifiedStatCard extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
final IconData icon;
|
||||||
|
final Color color;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
final String? subtitle;
|
||||||
|
|
||||||
|
const UnifiedStatCard({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.icon,
|
||||||
|
required this.color,
|
||||||
|
this.onTap,
|
||||||
|
this.subtitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
splashColor: color.withValues(alpha: 0.1),
|
||||||
|
highlightColor: color.withValues(alpha: 0.05),
|
||||||
|
child: Container(
|
||||||
|
width: 155,
|
||||||
|
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(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(7),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withValues(alpha: 0.12),
|
||||||
|
borderRadius: BorderRadius.circular(9),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: color, size: 16),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: color.withValues(alpha: 0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurface,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
height: 1.1,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
if (subtitle != null) ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
subtitle!,
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnifiedSectionHeader extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final IconData? icon;
|
||||||
|
final Color? iconColor;
|
||||||
|
final Widget? trailing;
|
||||||
|
|
||||||
|
const UnifiedSectionHeader({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
this.icon,
|
||||||
|
this.iconColor,
|
||||||
|
this.trailing,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cs = Theme.of(context).colorScheme;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 3,
|
||||||
|
height: 14,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: iconColor ?? cs.primary,
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
if (icon != null) ...[
|
||||||
|
Icon(icon!, size: 16, color: iconColor ?? cs.primary),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (trailing != null) ...[
|
||||||
|
const Spacer(),
|
||||||
|
trailing!,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user