import 'package:siro_admin/constant/map_tiles.dart'; import 'package:siro_admin/constant/theme.dart'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:siro_admin/constant/links.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../controller/functions/crud.dart'; import '../../../constant/box_name.dart'; import '../../../main.dart'; // ═══════════════════════════════════════════════════════════════════════════ // 1. MODEL // ═══════════════════════════════════════════════════════════════════════════ class RideDashboardModel { final String rideId; final String status; final String startLocation; final String endLocation; final String date; final String time; final String price; final String distance; final String driverId; final String driverName; final String driverPhone; final String driverCompletedCount; final String driverCanceledCount; final String passengerName; final String passengerPhone; final String passengerCompletedCount; final String cancelReason; RideDashboardModel({ required this.rideId, required this.status, required this.startLocation, required this.endLocation, required this.date, required this.time, required this.price, required this.distance, required this.driverId, required this.driverName, required this.driverPhone, required this.driverCompletedCount, required this.driverCanceledCount, required this.passengerName, required this.passengerPhone, required this.passengerCompletedCount, required this.cancelReason, }); factory RideDashboardModel.fromJson(Map json) { return RideDashboardModel( rideId: json['id'].toString(), status: json['status'] ?? '', startLocation: json['start_location'] ?? '', endLocation: json['end_location'] ?? '', date: json['date'] ?? '', time: json['time'] ?? '', price: json['price']?.toString() ?? '0', distance: json['distance']?.toString() ?? '0', driverId: json['driver_id'].toString(), driverName: json['driver_full_name'] ?? 'غير معروف', driverPhone: json['d_phone'] ?? '', driverCompletedCount: (json['d_completed'] ?? 0).toString(), driverCanceledCount: (json['d_canceled'] ?? 0).toString(), passengerName: json['passenger_full_name'] ?? 'غير معروف', passengerPhone: json['p_phone'] ?? '', passengerCompletedCount: (json['p_completed'] ?? 0).toString(), cancelReason: json['cancel_reason'] ?? '', ); } LatLng? getStartLatLng() { try { var parts = startLocation.split(','); return LatLng(double.parse(parts[0]), double.parse(parts[1])); } catch (e) { return null; } } LatLng? getEndLatLng() { try { var parts = endLocation.split(','); return LatLng(double.parse(parts[0]), double.parse(parts[1])); } catch (e) { return null; } } } // ═══════════════════════════════════════════════════════════════════════════ // 2. CONTROLLER // ═══════════════════════════════════════════════════════════════════════════ class RidesListController extends GetxController { var isLoading = false.obs; var allRidesList = []; var displayedRides = [].obs; TextEditingController searchController = TextEditingController(); String currentStatus = 'Begin'; String myPhone = box.read(BoxName.adminPhone)?.toString() ?? ''; bool get isSuperAdmin { return myPhone == '963942542053' || myPhone == '963992952235'; } final String apiUrl = "${AppLink.server}/Admin/rides/get_rides_by_status.php"; // ═══ Statistics ═══ var beginCount = 0.obs; var newCount = 0.obs; var completedCount = 0.obs; var canceledCount = 0.obs; var totalRevenue = 0.0.obs; var totalDistance = 0.0.obs; @override void onInit() { super.onInit(); fetchRides(); } void changeTab(String status) { currentStatus = status; searchController.clear(); fetchRides(); } void filterRides(String query) { if (query.isEmpty) { displayedRides.value = allRidesList; } else { displayedRides.value = allRidesList.where((ride) { return ride.driverPhone.contains(query) || ride.passengerPhone.contains(query) || ride.driverName.toLowerCase().contains(query.toLowerCase()) || ride.passengerName.toLowerCase().contains(query.toLowerCase()) || ride.rideId.contains(query); }).toList(); } } void calculateStatistics() { beginCount.value = allRidesList.where((r) => r.status == 'Begin').length; newCount.value = allRidesList.where((r) => r.status == 'New').length; completedCount.value = allRidesList.where((r) => r.status == 'Finished').length; canceledCount.value = allRidesList.where((r) => r.status.contains('Cancel')).length; totalRevenue.value = allRidesList.fold( 0.0, (sum, ride) => sum + (double.tryParse(ride.price) ?? 0.0)); totalDistance.value = allRidesList.fold( 0.0, (sum, ride) => sum + (double.tryParse(ride.distance) ?? 0.0)); } Future fetchRides() async { isLoading.value = true; allRidesList.clear(); displayedRides.clear(); try { var response = await CRUD().post(link: apiUrl, payload: {"status": currentStatus}); if (response != 'failure' && response['status'] == 'success') { List data = []; if (response['message'] is List) { data = response['message']; } else if (response['data'] is List) { data = response['data']; } allRidesList = data.map((e) => RideDashboardModel.fromJson(e)).toList(); displayedRides.value = allRidesList; calculateStatistics(); } } catch (e) { debugPrint("Error fetching rides: $e"); } finally { isLoading.value = false; } } } // ═══════════════════════════════════════════════════════════════════════════ // 3. MAIN DASHBOARD SCREEN (ADVANCED SLIVER IMPLEMENTATION) // ═══════════════════════════════════════════════════════════════════════════ class RidesDashboardScreen extends StatefulWidget { const RidesDashboardScreen({super.key}); @override State createState() => _RidesDashboardScreenState(); } class _RidesDashboardScreenState extends State with SingleTickerProviderStateMixin { late final RidesListController controller; late TabController _tabController; @override void initState() { super.initState(); controller = Get.put(RidesListController()); _tabController = TabController(length: 4, vsync: this); _tabController.addListener(_handleTabChange); } @override void dispose() { _tabController.removeListener(_handleTabChange); _tabController.dispose(); super.dispose(); } void _handleTabChange() { if (_tabController.indexIsChanging) return; List statuses = ['Begin', 'New', 'Completed', 'Canceled']; controller.changeTab(statuses[_tabController.index]); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Scaffold( backgroundColor: cs.surface, body: SafeArea( top: false, child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ // 1. Sliver AppBar (المتحرك الذكي الذي يصغر عند التمرير) // الرأس: سطح هادئ بدل الكتلة الملوّنة المشبعة. // النسخة السابقة كانت شريحة بلون primary بارتفاع 310 مع تدرّج // وظل — تسحب الانتباه إلى الإطار بدل الأرقام، وهي أول ما يجعل // اللوحة تبدو غير احترافية. اللون الآن يميّز الحالة داخل البطاقات // فقط، حيث يحمل معنى. SliverAppBar( pinned: true, floating: false, expandedHeight: 268.0, backgroundColor: cs.surface, surfaceTintColor: Colors.transparent, elevation: 0, scrolledUnderElevation: 0, iconTheme: IconThemeData(color: cs.onSurface), centerTitle: false, titleSpacing: 20, title: Text( 'إدارة الرحلات', style: TextStyle( color: cs.onSurface, fontWeight: FontWeight.w700, fontSize: 19, ), ), actions: [ Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: cs.primary.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(10), border: Border.all( color: cs.primary.withValues(alpha: 0.22)), ), alignment: Alignment.center, child: Text( 'اليوم', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: cs.primary, ), ), ), ], flexibleSpace: FlexibleSpaceBar( background: Container( color: cs.surface, child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ _buildStatisticsSection(cs), const SizedBox(height: 14), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildSearchBar(cs), ), const SizedBox(height: 62), // مساحة للـ TabBar بالأسفل ], ), ), ), // TabBar يثبت دائماً أسفل الـ AppBar عند التمرير bottom: PreferredSize( preferredSize: const Size.fromHeight(54), child: Container( decoration: BoxDecoration( color: cs.surface, border: Border( bottom: BorderSide(color: cs.outline, width: 1)), ), child: TabBar( controller: _tabController, isScrollable: true, labelColor: cs.primary, unselectedLabelColor: cs.onSurfaceVariant, indicatorColor: cs.primary, indicatorWeight: 3, indicatorSize: TabBarIndicatorSize.label, labelStyle: const TextStyle( fontWeight: FontWeight.w700, fontSize: 14), tabAlignment: TabAlignment.center, dividerColor: Colors.transparent, tabs: const [ Tab( icon: Icon(Icons.directions_car_rounded), text: 'جارية'), Tab( icon: Icon(Icons.new_releases_rounded), text: 'جديدة'), Tab( icon: Icon(Icons.check_circle_rounded), text: 'مكتملة'), Tab(icon: Icon(Icons.cancel_rounded), text: 'ملغاة'), ], ), ), ), ), // 2. قائمة الرحلات (Sliver List) SliverPadding( padding: const EdgeInsets.all(16.0), sliver: Obx(() { if (controller.isLoading.value) { return const SliverFillRemaining( hasScrollBody: false, child: Center(child: CircularProgressIndicator()), ); } if (controller.displayedRides.isEmpty) { return SliverFillRemaining( hasScrollBody: false, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.inbox_rounded, size: 80, color: cs.onSurfaceVariant), const SizedBox(height: 16), Text( 'لا توجد رحلات في هذا القسم', style: TextStyle( fontSize: 16, color: cs.onSurfaceVariant, fontWeight: FontWeight.w600, ), ), ], ), ), ); } return SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final ride = controller.displayedRides[index]; return _buildRideCardCompact( ride, controller.isSuperAdmin, cs); }, childCount: controller.displayedRides.length, ), ); }), ), ], ), ), ); } // --- Components --- Widget _buildStatisticsSection(ColorScheme cs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: Text( 'نظرة عامة', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, // كان Colors.white ثابتاً — يصلح فوق الرأس الملوّن السابق فقط، // ويصبح غير مقروء إطلاقاً على سطح الوضع الفاتح. color: cs.onSurfaceVariant, ), ), ), SingleChildScrollView( physics: const BouncingScrollPhysics(), scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Obx(() => _buildStatCard( 'جارية', controller.beginCount.toString(), Icons.directions_car_rounded, cs.success, cs)), Obx(() => _buildStatCard('جديدة', controller.newCount.toString(), Icons.new_releases_rounded, cs.info, cs)), Obx(() => _buildStatCard( 'مكتملة', controller.completedCount.toString(), Icons.check_circle_rounded, cs.tertiary, cs)), Obx(() => _buildStatCard( 'ملغاة', controller.canceledCount.toString(), Icons.cancel_rounded, cs.danger, cs)), Obx(() => _buildStatCard( 'الإيرادات', controller.totalRevenue.value.toStringAsFixed(0), Icons.payments_rounded, cs.warning, cs)), ], ), ), ], ); } Widget _buildStatCard( String label, String value, IconData icon, Color iconColor, ColorScheme cs) { // البطاقة تُبنى على سطح مرتفع بحدّ رفيع وشارة لونية للأيقونة، بدل // بطاقة بيضاء عائمة بظل ثقيل. اللون يعرّف الحالة ولا يملأ المساحة. return Container( width: 108, margin: const EdgeInsets.only(left: 10), padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 10), decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), border: Border.all(color: iconColor.withValues(alpha: 0.22)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( color: iconColor.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(9), ), child: Icon(icon, color: iconColor, size: 18), ), const SizedBox(height: 8), Text( value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: cs.onSurface), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( label, style: TextStyle( fontSize: 11, color: cs.onSurfaceVariant, fontWeight: FontWeight.bold), ), ], ), ); } Widget _buildSearchBar(ColorScheme cs) { return Container( decoration: BoxDecoration( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(14), border: Border.all(color: cs.outline), ), child: TextField( controller: controller.searchController, onChanged: (val) => controller.filterRides(val), style: TextStyle(color: cs.onSurface, fontWeight: FontWeight.w600), decoration: InputDecoration( hintText: 'ابحث عن رقم الرحلة، السائق، أو الراكب...', hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 13), prefixIcon: Icon(Icons.search_rounded, color: cs.primary), suffixIcon: controller.searchController.text.isNotEmpty ? IconButton( icon: Icon(Icons.close_rounded, color: cs.onSurfaceVariant), onPressed: () { controller.searchController.clear(); controller.filterRides(''); }, ) : null, border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), ), ), ); } // ═══════════════════════════════════════════════════════════════════════════ // تصميم بطاقة الرحلة (المدمج والمنظم - Slim Design) // ═══════════════════════════════════════════════════════════════════════════ Widget _buildRideCardCompact(RideDashboardModel ride, bool isAdmin, ColorScheme cs) { Color statusColor = _getStatusColor(ride.status); String statusText = _getStatusText(ride.status); return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: cs.surface, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: cs.shadow.withValues(alpha: 0.04), blurRadius: 10, offset: const Offset(0, 4), ), ], border: Border.all(color: cs.outline.withValues(alpha: 0.1)), ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(20), onTap: () => Get.to( () => RideMapMonitorScreen(ride: ride, isAdmin: isAdmin), transition: Transition.cupertino, ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 1. Header (ID + Status) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: cs.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.confirmation_number_rounded, color: cs.primary, size: 18), ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'رحلة #${ride.rideId}', style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: cs.onSurface), ), Text( '${ride.date} • ${ride.time}', style: TextStyle( fontSize: 11, color: cs.onSurfaceVariant, fontWeight: FontWeight.w600), ), ], ), ], ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: statusColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Text( statusText, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: statusColor), ), ), ], ), const Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Divider(height: 1), ), // 2. Locations (Timeline style) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( children: [ Icon(Icons.my_location_rounded, size: 14, color: cs.success), Container( width: 2, height: 16, color: cs.outline, margin: const EdgeInsets.symmetric(vertical: 2)), Icon(Icons.location_on_rounded, size: 14, color: cs.danger), ], ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( ride.startLocation, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: cs.onSurface), ), const SizedBox(height: 12), Text( ride.endLocation, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: cs.onSurface), ), ], ), ), ], ), const SizedBox(height: 16), // 3. Driver & Passenger (Slim Rows) Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color:Colors.white, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ _buildSlimUserRow( icon: Icons.local_taxi_rounded, title: 'السائق', name: ride.driverName, phone: ride.driverPhone, color: cs.info, isAdmin: isAdmin, ), const Padding( padding: EdgeInsets.symmetric(vertical: 6), child: Divider(height: 1, thickness: 0.5), ), _buildSlimUserRow( icon: Icons.person_rounded, title: 'الراكب', name: ride.passengerName, phone: ride.passengerPhone, color: const Color(0xFF8B5CF6), isAdmin: isAdmin, ), ], ), ), const SizedBox(height: 14), // 4. Information Chips Row( children: [ _buildInfoChip( Icons.payments_rounded, '${double.tryParse(ride.price)?.toStringAsFixed(0) ?? 0} ل.س', cs.success), const SizedBox(width: 8), _buildInfoChip( Icons.straighten_rounded, '${double.tryParse(ride.distance)?.toStringAsFixed(1) ?? 0} كم', cs.warning), const SizedBox(width: 8), _buildInfoChip( Icons.access_time_rounded, ride.time.length > 5 ? ride.time.substring(0, 5) : ride.time, cs.info), ], ), // 5. Cancel Reason (If any) if ((ride.status.contains('Cancel') || ride.status == 'TimeOut') && ride.cancelReason.isNotEmpty && ride.cancelReason != 'لا يوجد سبب') ...[ const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: cs.danger.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8), border: Border.all( color: cs.danger.withValues(alpha: 0.2)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.info_outline_rounded, size: 16, color: cs.danger), const SizedBox(width: 6), Expanded( child: Text( 'السبب: ${ride.cancelReason}', style: const TextStyle( color: Color(0xFFB91C1C), fontSize: 11, fontWeight: FontWeight.bold), ), ), ], ), ), ], ], ), ), ), ), ); } // تصميم الصف النحيف للمستخدم (لتوفير المساحة) Widget _buildSlimUserRow({ required IconData icon, required String title, required String name, required String phone, required Color color, required bool isAdmin, }) { final cs = Theme.of(context).colorScheme; String displayPhone = phone; if (!isAdmin && phone.length > 4) { displayPhone = phone.substring(phone.length - 4).padLeft(phone.length, '*'); } return Row( children: [ Icon(icon, size: 16, color: color), const SizedBox(width: 6), Text( '$title:', style: TextStyle( fontSize: 11, color: cs.onSurfaceVariant, fontWeight: FontWeight.bold), ), const SizedBox(width: 6), Expanded( child: Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: cs.onSurface), ), ), Text( displayPhone, style: TextStyle( fontSize: 11, color: cs.onSurfaceVariant, letterSpacing: 0.5), ), if (isAdmin && phone.isNotEmpty) ...[ const SizedBox(width: 8), GestureDetector( onTap: () async { String formattedPhone = phone; if (!formattedPhone.startsWith('+')) { formattedPhone = '+$formattedPhone'; } final Uri launchUri = Uri(scheme: 'tel', path: formattedPhone); if (await canLaunchUrl(launchUri)) await launchUrl(launchUri); }, child: Icon(Icons.call_rounded, size: 16, color: color), ), ] ], ); } // تصميم الرقاقة (Chip) للمعلومات السفلية Widget _buildInfoChip(IconData icon, String text, Color color) { return Expanded( child: Container( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 14, color: color), const SizedBox(width: 4), Flexible( child: Text( text, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, color: color), ), ), ], ), ), ); } // Helper Methods for Status Color _getStatusColor(String status) { final cs = Theme.of(context).colorScheme; if (status == 'Begin' || status == 'Arrived') { return cs.success; } if (status == 'Finished') return const Color(0xFF14B8A6); if (status.contains('Cancel') || status == 'TimeOut') { return cs.danger; } if (status == 'New') return cs.info; return cs.onSurfaceVariant; } String _getStatusText(String status) { if (status == 'Begin' || status == 'Arrived') return 'جارية'; if (status == 'Finished') return 'مكتملة'; if (status == 'CancelFromDriver' || status == 'CancelFromDriverAfterApply') { return 'ألغى السائق'; } if (status == 'CancelFromPassenger') return 'ألغى الراكب'; if (status == 'TimeOut') return 'انتهى الوقت'; if (status == 'New') return 'جديدة'; return 'ملغاة'; } } // ═══════════════════════════════════════════════════════════════════════════ // 5. MAP MONITOR SCREEN (Minor UI Polish) // ═══════════════════════════════════════════════════════════════════════════ class RideMapMonitorScreen extends StatefulWidget { final RideDashboardModel ride; final bool isAdmin; const RideMapMonitorScreen({ super.key, required this.ride, required this.isAdmin, }); @override State createState() => _RideMapMonitorScreenState(); } class _RideMapMonitorScreenState extends State { final MapController mapController = MapController(); LatLng? startPos, endPos, driverPos; Timer? _timer; bool isFirstLoad = true; @override void initState() { super.initState(); startPos = widget.ride.getStartLatLng(); endPos = widget.ride.getEndLatLng(); if (widget.ride.status == 'Begin' || widget.ride.status == 'Arrived') { fetchDriverLocation(); _timer = Timer.periodic( const Duration(seconds: 10), (_) => fetchDriverLocation(), ); } WidgetsBinding.instance.addPostFrameCallback((_) => _fitBounds()); } @override void dispose() { _timer?.cancel(); mapController.dispose(); super.dispose(); } void _fitBounds() { List points = []; if (startPos != null) points.add(startPos!); if (endPos != null) points.add(endPos!); if (driverPos != null) points.add(driverPos!); if (points.isNotEmpty) { try { mapController.fitCamera( CameraFit.bounds( bounds: LatLngBounds.fromPoints(points), padding: const EdgeInsets.all(100), ), ); } catch (e) {} } } Future fetchDriverLocation() async { String trackUrl = "${AppLink.server}/Admin/rides/get_driver_live_pos.php"; try { var response = await CRUD().post( link: trackUrl, payload: {"driver_id": widget.ride.driverId}, ); if (response != 'failure') { var d = response['message']; setState(() { driverPos = LatLng( double.parse(d['latitude'].toString()), double.parse(d['longitude'].toString()), ); }); if (isFirstLoad) { _fitBounds(); isFirstLoad = false; } } } catch (e) {} } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; return Scaffold( appBar: AppBar( title: Text( 'تتبع الرحلة #${widget.ride.rideId}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), backgroundColor: Colors.white, foregroundColor: const Color(0xFF2B3674), elevation: 0, centerTitle: true, ), body: Stack( children: [ // Map FlutterMap( mapController: mapController, options: MapOptions( initialCenter: startPos ?? const LatLng(33.513, 36.276), initialZoom: 13, ), children: [ MapTiles.layer(context), // Route Polyline if (startPos != null && endPos != null) PolylineLayer( polylines: [ Polyline( points: [startPos!, endPos!], strokeWidth: 5, color: cs.primary.withValues(alpha: 0.8), ), ], ), // Markers MarkerLayer( markers: [ // Start Point if (startPos != null) Marker( point: startPos!, width: 40, height: 40, child: Container( decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.2), blurRadius: 5), ], ), child: Icon(Icons.flag_rounded, color: cs.success, size: 24), ), alignment: Alignment.topCenter, ), // End Point if (endPos != null) Marker( point: endPos!, width: 40, height: 40, child: Container( decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.2), blurRadius: 5), ], ), child: Icon(Icons.location_on_rounded, color: cs.danger, size: 24), ), alignment: Alignment.topCenter, ), // Driver Current Position if (driverPos != null) Marker( point: driverPos!, width: 50, height: 50, child: Container( decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: cs.info.withValues(alpha: 0.3), blurRadius: 8), ], ), child: Icon(Icons.directions_car_rounded, color: cs.info, size: 28), ), ), ], ), ], ), // Info Panel (Floating) Positioned( bottom: 24, left: 16, right: 16, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 10), ), ], ), padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Driver Info _buildMapUserInfo( icon: Icons.local_taxi_rounded, title: 'السائق', name: widget.ride.driverName, phone: widget.ride.driverPhone, color: cs.info, ), const SizedBox(height: 12), // Passenger Info _buildMapUserInfo( icon: Icons.person_rounded, title: 'الراكب', name: widget.ride.passengerName, phone: widget.ride.passengerPhone, color: const Color(0xFF8B5CF6), ), ], ), ), ), // Fit Button Positioned( top: 16, right: 16, child: FloatingActionButton.small( backgroundColor: Colors.white, foregroundColor: const Color(0xFF2B3674), onPressed: _fitBounds, child: const Icon(Icons.center_focus_strong_rounded), ), ), ], ), ); } Widget _buildMapUserInfo({ required IconData icon, required String title, required String name, required String phone, required Color color, }) { final cs = Theme.of(context).colorScheme; return Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, size: 20, color: color), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 11, color: cs.onSurfaceVariant, fontWeight: FontWeight.bold), ), const SizedBox(height: 2), Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xFF2B3674)), ), ], ), ), if (widget.isAdmin && phone.isNotEmpty) GestureDetector( onTap: () async { String formattedPhone = phone; if (!formattedPhone.startsWith('+')) { formattedPhone = '+$formattedPhone'; } final Uri launchUri = Uri(scheme: 'tel', path: formattedPhone); if (await canLaunchUrl(launchUri)) await launchUrl(launchUri); }, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: cs.success.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Icon(Icons.call_rounded, size: 20, color: cs.success), ), ), ], ); } }