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:sefer_admin1/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; // 🎨 الألوان العصرية final Color bgColor = const Color(0xFFF4F7FE); final Color primaryColor = const Color(0xFF4318FF); final Color textPrimary = const Color(0xFF2B3674); @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) { return Scaffold( backgroundColor: bgColor, body: SafeArea( top: false, // نسمح للـ AppBar بالتمدد لأعلى الشاشة child: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ // 1. Sliver AppBar (المتحرك الذكي الذي يصغر عند التمرير) SliverAppBar( pinned: true, floating: false, expandedHeight: 310.0, // ارتفاع الجزء العلوي بالكامل backgroundColor: primaryColor, elevation: 4, shadowColor: primaryColor.withOpacity(0.4), iconTheme: const IconThemeData(color: Colors.white), centerTitle: true, title: const Text( 'إدارة الرحلات', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20, ), ), actions: [ Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 14), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), alignment: Alignment.center, child: const Text( 'اليوم', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ], flexibleSpace: FlexibleSpaceBar( background: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [primaryColor, primaryColor.withOpacity(0.8)], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ // الإحصائيات تختفي عند التمرير لأعلى _buildStatisticsSection(), const SizedBox(height: 12), // شريط البحث يختفي عند التمرير لأعلى Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildSearchBar(), ), const SizedBox(height: 60), // مساحة للـ TabBar بالأسفل ], ), ), ), // TabBar يثبت دائماً أسفل الـ AppBar عند التمرير bottom: PreferredSize( preferredSize: const Size.fromHeight(54), child: Container( decoration: const BoxDecoration( color: Color(0xFFF4F7FE), // لون خلفية التطبيق ليظهر بشكل مدمج borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), child: TabBar( controller: _tabController, isScrollable: true, labelColor: primaryColor, unselectedLabelColor: Colors.grey, indicatorColor: primaryColor, indicatorWeight: 3, labelStyle: const TextStyle( fontWeight: FontWeight.bold, 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: Colors.grey[400]), const SizedBox(height: 16), Text( 'لا توجد رحلات في هذا القسم', style: TextStyle( fontSize: 16, color: Colors.grey[600], fontWeight: FontWeight.w600, ), ), ], ), ), ); } return SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final ride = controller.displayedRides[index]; return _buildRideCardCompact( ride, controller.isSuperAdmin); }, childCount: controller.displayedRides.length, ), ); }), ), ], ), ), ); } // --- Components --- Widget _buildStatisticsSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), child: Text( 'نظرة عامة', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white.withOpacity(0.8), ), ), ), 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, const Color(0xFF10B981))), Obx(() => _buildStatCard('جديدة', controller.newCount.toString(), Icons.new_releases_rounded, const Color(0xFF3B82F6))), Obx(() => _buildStatCard( 'مكتملة', controller.completedCount.toString(), Icons.check_circle_rounded, const Color(0xFF14B8A6))), Obx(() => _buildStatCard( 'ملغاة', controller.canceledCount.toString(), Icons.cancel_rounded, const Color(0xFFEF4444))), Obx(() => _buildStatCard( 'الإيرادات', '${controller.totalRevenue.value.toStringAsFixed(0)}', Icons.payments_rounded, const Color(0xFFF59E0B))), ], ), ), ], ); } Widget _buildStatCard( String label, String value, IconData icon, Color iconColor) { return Container( width: 105, margin: const EdgeInsets.only(left: 10), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4), ) ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: iconColor, size: 24), const SizedBox(height: 8), Text( value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: textPrimary), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( label, style: TextStyle( fontSize: 11, color: Colors.grey[600], fontWeight: FontWeight.bold), ), ], ), ); } Widget _buildSearchBar() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 8, offset: const Offset(0, 4), ) ], ), child: TextField( controller: controller.searchController, onChanged: (val) => controller.filterRides(val), style: TextStyle(color: textPrimary, fontWeight: FontWeight.w600), decoration: InputDecoration( hintText: 'ابحث عن رقم الرحلة، السائق، أو الراكب...', hintStyle: TextStyle(color: Colors.grey[400], fontSize: 13), prefixIcon: Icon(Icons.search_rounded, color: primaryColor), suffixIcon: controller.searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.close_rounded, color: Colors.grey), 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) { Color statusColor = _getStatusColor(ride.status); String statusText = _getStatusText(ride.status); return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 10, offset: const Offset(0, 4), ), ], border: Border.all(color: Colors.grey.withOpacity(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: primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.confirmation_number_rounded, color: primaryColor, size: 18), ), const SizedBox(width: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'رحلة #${ride.rideId}', style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: textPrimary), ), Text( '${ride.date} • ${ride.time}', style: TextStyle( fontSize: 11, color: Colors.grey[500], fontWeight: FontWeight.w600), ), ], ), ], ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: statusColor.withOpacity(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: [ const Icon(Icons.my_location_rounded, size: 14, color: Color(0xFF10B981)), Container( width: 2, height: 16, color: Colors.grey.withOpacity(0.3), margin: const EdgeInsets.symmetric(vertical: 2)), const Icon(Icons.location_on_rounded, size: 14, color: Color(0xFFEF4444)), ], ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( ride.startLocation, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.black87), ), const SizedBox(height: 12), Text( ride.endLocation, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.black87), ), ], ), ), ], ), const SizedBox(height: 16), // 3. Driver & Passenger (Slim Rows) Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ _buildSlimUserRow( icon: Icons.local_taxi_rounded, title: 'السائق', name: ride.driverName, phone: ride.driverPhone, color: const Color(0xFF3B82F6), 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} ل.س', const Color(0xFF10B981)), const SizedBox(width: 8), _buildInfoChip( Icons.straighten_rounded, '${double.tryParse(ride.distance)?.toStringAsFixed(1) ?? 0} كم', const Color(0xFFF59E0B)), const SizedBox(width: 8), _buildInfoChip( Icons.access_time_rounded, ride.time.length > 5 ? ride.time.substring(0, 5) : ride.time, const Color(0xFF3B82F6)), ], ), // 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: const Color(0xFFEF4444).withOpacity(0.08), borderRadius: BorderRadius.circular(8), border: Border.all( color: const Color(0xFFEF4444).withOpacity(0.2)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.info_outline_rounded, size: 16, color: Color(0xFFEF4444)), 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, }) { 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: Colors.grey[600], fontWeight: FontWeight.bold), ), const SizedBox(width: 6), Expanded( child: Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87), ), ), Text( displayPhone, style: TextStyle( fontSize: 11, color: Colors.grey[500], 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.withOpacity(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) { if (status == 'Begin' || status == 'Arrived') return const Color(0xFF10B981); if (status == 'Finished') return const Color(0xFF14B8A6); if (status.contains('Cancel') || status == 'TimeOut') return const Color(0xFFEF4444); if (status == 'New') return const Color(0xFF3B82F6); return Colors.grey; } 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) { 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: [ TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.tripz.app', ), // Route Polyline if (startPos != null && endPos != null) PolylineLayer( polylines: [ Polyline( points: [startPos!, endPos!], strokeWidth: 5, color: const Color(0xFF4318FF).withOpacity(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.withOpacity(0.2), blurRadius: 5), ], ), child: const Icon(Icons.flag_rounded, color: Color(0xFF10B981), 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.withOpacity(0.2), blurRadius: 5), ], ), child: const Icon(Icons.location_on_rounded, color: Color(0xFFEF4444), 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: const Color(0xFF3B82F6).withOpacity(0.3), blurRadius: 8), ], ), child: const Icon(Icons.directions_car_rounded, color: Color(0xFF3B82F6), 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.withOpacity(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: const Color(0xFF3B82F6), ), 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, }) { String displayPhone = phone; if (!widget.isAdmin && phone.length > 4) { displayPhone = phone.substring(phone.length - 4).padLeft(phone.length, '*'); } return Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: color.withOpacity(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: Colors.grey[600], 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: Colors.green.withOpacity(0.1), shape: BoxShape.circle, ), child: const Icon(Icons.call_rounded, size: 20, color: Colors.green), ), ), ], ); } }