import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/responsive/responsive_layout.dart'; import '../../core/responsive/viewport_size.dart'; import '../../core/theme/theme_extensions.dart'; import '../../services/auth/auth_providers.dart'; import '../../services/map/map_provider_interface.dart'; import '../../services/map/unified_map_view.dart'; import '../../services/routes/navigation_handoff.dart'; import '../../services/routes/route_models.dart'; import '../../services/routes/route_providers.dart'; import 'models/route_curvature_analysis.dart'; import 'widgets/route_curvature_summary_card.dart'; class RouteDetailScreen extends ConsumerStatefulWidget { final String routeId; const RouteDetailScreen({super.key, required this.routeId}); @override ConsumerState createState() => _RouteDetailScreenState(); } class _RouteDetailScreenState extends ConsumerState { bool _isDeleting = false; Future _confirmDelete(MotorSignalRoute route) async { final colors = context.motorColors; final confirm = await showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: colors.surfaceContainer, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: colors.outlineVariant), ), title: const Text( 'Delete Route', style: TextStyle(color: Colors.white), ), content: Text( 'Are you sure you want to delete "${route.title}"? This cannot be undone.', style: const TextStyle(color: Colors.white70), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text( 'CANCEL', style: TextStyle(color: Colors.white54), ), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: colors.raceRed, foregroundColor: Colors.white, ), onPressed: () => Navigator.pop(ctx, true), child: const Text('DELETE'), ), ], ), ); if (confirm != true || !mounted) return; setState(() => _isDeleting = true); try { final user = ref.read(currentUserProvider); if (user == null || user.uid.isEmpty) { throw StateError('Sign-in required to delete route.'); } final service = ref.read(routeServiceProvider); await service.deleteRoute( widget.routeId, callerUid: user.uid, ); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Route deleted.'))); context.pop(); } } catch (e) { if (mounted) { setState(() => _isDeleting = false); ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Failed to delete route: $e'))); } } } IconData _getIconForStopType(RouteStopType type) { switch (type) { case RouteStopType.start: case RouteStopType.origin: return Icons.play_circle_fill; case RouteStopType.meetup: return Icons.people; case RouteStopType.waypoint: return Icons.place; case RouteStopType.fuel: return Icons.local_gas_station; case RouteStopType.rest: return Icons.hotel; case RouteStopType.food: return Icons.restaurant; case RouteStopType.scenic: return Icons.photo_camera; case RouteStopType.regroup: return Icons.group; case RouteStopType.destination: return Icons.flag; case RouteStopType.other: return Icons.location_on; } } @override Widget build(BuildContext context) { final colors = context.motorColors; final routeAsync = ref.watch(routeByIdProvider(widget.routeId)); final currentUser = ref.watch(currentUserProvider); return Scaffold( backgroundColor: colors.surfaceContainer, appBar: AppBar( backgroundColor: colors.surfaceContainerHigh, elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => context.pop(), ), title: const Text( 'ROUTE DETAILS', style: TextStyle( fontWeight: FontWeight.w900, letterSpacing: 1.2, fontSize: 16, ), ), ), body: routeAsync.when( loading: () => Center( child: CircularProgressIndicator(color: colors.cyberCyan), ), error: (err, _) => Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.error_outline, size: 48, color: colors.raceRed, ), const SizedBox(height: 12), Text( 'Could not load route: $err', textAlign: TextAlign.center, style: const TextStyle(color: Colors.white70), ), ], ), ), ), data: (route) { if (route == null) { return const Center( child: Text( 'Route not found or has been deleted.', style: TextStyle(color: Colors.white54), ), ); } final isOwner = currentUser?.uid == route.ownerUid; // Map markers final markers = route.stops.map((stop) { final isFirst = stop.orderIndex == 0; final isLast = stop.orderIndex == route.stops.length - 1 && route.stops.length > 1; return AppMapMarker( id: stop.id, position: AppLatLng(stop.latitude, stop.longitude), label: stop.displayName, iconWidget: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: isFirst ? colors.launchGreen : isLast ? colors.raceRed : colors.cyberCyan, shape: BoxShape.circle, boxShadow: const [ BoxShadow( color: Colors.black54, blurRadius: 4, spreadRadius: 1, ), ], ), child: Text( '${stop.orderIndex + 1}', style: const TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 12, ), ), ), onTap: () { NavigationHandoffService.showHandoffSheet( context: context, latitude: stop.latitude, longitude: stop.longitude, displayName: stop.displayName, ); }, ); }).toList(); // Polylines final polylines = route.stops.length >= 2 ? [ AppPolyline( id: 'route_straight_line', points: route.previewPoints .map((p) => AppLatLng(p.latitude, p.longitude)) .toList(), color: colors.cyberCyan, width: 3.5, ), ] : []; final mapCenter = route.stops.isNotEmpty ? AppLatLng( route.stops.first.latitude, route.stops.first.longitude, ) : const AppLatLng(37.7749, -122.4194); return ResponsiveLayout( builder: (context, snapshot) { final isExpandedLayout = snapshot.sizeClass == WindowSizeClass.expanded || (snapshot.isLandscape && snapshot.width >= 720); if (isExpandedLayout) { return _buildExpandedLayout( context, colors, route, isOwner, mapCenter, markers, polylines, ); } return _buildCompactLayout( context, colors, snapshot, route, isOwner, mapCenter, markers, polylines, ); }, ); }, ), ); } Widget _buildExpandedLayout( BuildContext context, MotorSignalThemeColors colors, MotorSignalRoute route, bool isOwner, AppLatLng mapCenter, List markers, List polylines, ) { return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Left Column: Map Preview + Metrics Expanded( flex: 5, child: Container( decoration: BoxDecoration( border: Border( right: BorderSide(color: colors.outlineVariant), ), ), child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Map Preview Box ClipRRect( borderRadius: BorderRadius.circular(12), child: SizedBox( height: 280, width: double.infinity, child: Stack( children: [ UnifiedMapView( key: ValueKey('route_detail_map_exp_${route.id}'), center: mapCenter, zoom: 12.0, markers: markers, polylines: polylines, ), Positioned( top: 8, left: 8, child: _buildVisibilityBadge(colors, route), ), ], ), ), ), const SizedBox(height: 16), _buildDistanceAndStopsCard(colors, route), const SizedBox(height: 12), RouteCurvatureSummaryCard( report: RouteCurvatureReport.analyze( geometry: polylines.isNotEmpty ? polylines.first.points : const [], stops: route.stops, ), colors: colors, ), const SizedBox(height: 12), _buildDisclaimerText(), ], ), ), ), ), // Right Column: Details, Actions, Stops list Expanded( flex: 6, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTitleAndDescription(colors, route), const SizedBox(height: 16), if (isOwner) ...[ _buildOwnerActions(colors, route), const SizedBox(height: 16), ], if (route.attachedGroupRideId != null) ...[ _buildAttachedGroupRideButton(colors, route), const SizedBox(height: 16), ], _buildStopsHeader(), const SizedBox(height: 10), _buildStopsList(colors, route), const SizedBox(height: 24), if (route.stops.isNotEmpty) _buildNavigateStartButton(colors, route), const SizedBox(height: 32), ], ), ), ), ], ); } Widget _buildCompactLayout( BuildContext context, MotorSignalThemeColors colors, ResponsiveSnapshot snapshot, MotorSignalRoute route, bool isOwner, AppLatLng mapCenter, List markers, List polylines, ) { final double mapHeight = snapshot.height < 360 ? 96 : (snapshot.height < 450 ? 140 : 220); return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Map Preview SizedBox( height: mapHeight, width: double.infinity, child: Stack( children: [ UnifiedMapView( key: ValueKey('route_detail_map_compact_${route.id}'), center: mapCenter, zoom: 12.0, markers: markers, polylines: polylines, ), Positioned( top: 8, left: 8, child: _buildVisibilityBadge(colors, route), ), ], ), ), // Title & Info Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTitleAndDescription(colors, route), const SizedBox(height: 12), _buildDistanceAndStopsCard(colors, route), const SizedBox(height: 12), RouteCurvatureSummaryCard( report: RouteCurvatureReport.analyze( geometry: polylines.isNotEmpty ? polylines.first.points : const [], stops: route.stops, ), colors: colors, ), const SizedBox(height: 8), _buildDisclaimerText(), const SizedBox(height: 16), if (isOwner) ...[ _buildOwnerActions(colors, route), const SizedBox(height: 16), ], if (route.attachedGroupRideId != null) ...[ _buildAttachedGroupRideButton(colors, route), const SizedBox(height: 16), ], _buildStopsHeader(), const SizedBox(height: 10), _buildStopsList(colors, route), const SizedBox(height: 24), if (route.stops.isNotEmpty) _buildNavigateStartButton(colors, route), const SizedBox(height: 32), ], ), ), ], ), ); } Widget _buildVisibilityBadge(MotorSignalThemeColors colors, MotorSignalRoute route) { return Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), decoration: BoxDecoration( color: colors.surfaceContainer.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(6), border: Border.all(color: colors.outlineVariant), ), child: Text( route.visibility.label, style: TextStyle( color: colors.cyberCyan, fontSize: 11, fontWeight: FontWeight.bold, ), ), ); } Widget _buildTitleAndDescription(MotorSignalThemeColors colors, MotorSignalRoute route) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( route.title, style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), if (route.description.isNotEmpty) ...[ const SizedBox(height: 6), Text( route.description, style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ], ], ); } Widget _buildDistanceAndStopsCard(MotorSignalThemeColors colors, MotorSignalRoute route) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), border: Border.all(color: colors.outlineVariant), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'STRAIGHT-LINE DISTANCE', style: TextStyle( color: Colors.white38, fontSize: 10, fontWeight: FontWeight.bold, letterSpacing: 1.0, ), ), const SizedBox(height: 2), Text( '${route.formattedDistanceMiles} (${route.formattedDistanceKm})', style: TextStyle( color: colors.cyberCyan, fontSize: 15, fontWeight: FontWeight.bold, ), ), ], ), ), Container( width: 1, height: 32, color: colors.outlineVariant, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'WAYPOINTS', style: TextStyle( color: Colors.white38, fontSize: 10, fontWeight: FontWeight.bold, letterSpacing: 1.0, ), ), const SizedBox(height: 2), Text( '${route.stops.length} STOPS', style: const TextStyle( color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold, ), ), ], ), ), ], ), ); } Widget _buildDisclaimerText() { return const Text( 'MotorSignal provides straight-line route planning and handoff. Turn-by-turn navigation opens in Google Maps or Waze.', style: TextStyle(color: Colors.white38, fontSize: 11), ); } Widget _buildOwnerActions(MotorSignalThemeColors colors, MotorSignalRoute route) { return Row( children: [ Expanded( child: OutlinedButton.icon( key: const ValueKey('route_detail_edit_button'), onPressed: () { context.push( '/routes/planner?routeId=${route.id}', ); }, icon: Icon( Icons.edit, size: 16, color: colors.cyberCyan, ), label: Text( 'EDIT ROUTE', style: TextStyle(color: colors.cyberCyan), ), style: OutlinedButton.styleFrom( side: BorderSide( color: colors.cyberCyan, ), padding: const EdgeInsets.symmetric( vertical: 10, ), ), ), ), const SizedBox(width: 12), OutlinedButton.icon( key: const ValueKey('route_detail_delete_button'), onPressed: _isDeleting ? null : () => _confirmDelete(route), icon: Icon( Icons.delete_outline, size: 16, color: colors.raceRed, ), label: Text( 'DELETE', style: TextStyle(color: colors.raceRed), ), style: OutlinedButton.styleFrom( side: BorderSide( color: colors.raceRed, ), padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 16, ), ), ), ], ); } Widget _buildAttachedGroupRideButton(MotorSignalThemeColors colors, MotorSignalRoute route) { return OutlinedButton.icon( key: const ValueKey( 'route_detail_view_attached_group_ride_button', ), onPressed: () { context.push( '/group-rides/${route.attachedGroupRideId}', ); }, icon: Icon( Icons.group, size: 18, color: colors.cyberCyan, ), label: Text( 'VIEW ATTACHED GROUP RIDE', style: TextStyle(color: colors.cyberCyan), ), style: OutlinedButton.styleFrom( side: BorderSide(color: colors.outlineVariant), minimumSize: const Size(double.infinity, 44), ), ); } Widget _buildStopsHeader() { return const Text( 'STOPS & WAYPOINTS', style: TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1.1, ), ); } Widget _buildStopsList(MotorSignalThemeColors colors, MotorSignalRoute route) { return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: route.stops.length, itemBuilder: (context, index) { final stop = route.stops[index]; final isFirst = index == 0; final isLast = index == route.stops.length - 1 && route.stops.length > 1; return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: colors.surfaceContainerHigh, borderRadius: BorderRadius.circular(10), border: Border.all( color: colors.outlineVariant, ), ), child: Row( children: [ Container( width: 28, height: 28, decoration: BoxDecoration( color: isFirst ? colors.launchGreen : isLast ? colors.raceRed : colors.cyberCyan, shape: BoxShape.circle, ), alignment: Alignment.center, child: Text( '${index + 1}', style: const TextStyle( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 13, ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( _getIconForStopType(stop.stopType), size: 14, color: colors.cyberCyan, ), const SizedBox(width: 4), Flexible( child: Text( stop.displayName, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ], ), const SizedBox(height: 2), Text( '${stop.latitude.toStringAsFixed(4)}, ${stop.longitude.toStringAsFixed(4)}', style: const TextStyle( color: Colors.white38, fontSize: 11, ), ), if (stop.notes.isNotEmpty) ...[ const SizedBox(height: 2), Text( stop.notes, style: const TextStyle( color: Colors.white70, fontSize: 12, fontStyle: FontStyle.italic, ), ), ], ], ), ), ElevatedButton.icon( onPressed: () { NavigationHandoffService.showHandoffSheet( context: context, latitude: stop.latitude, longitude: stop.longitude, displayName: stop.displayName, ); }, icon: const Icon(Icons.navigation, size: 14), label: const Text('NAV'), style: ElevatedButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), textStyle: const TextStyle( fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ], ), ); }, ); } Widget _buildNavigateStartButton(MotorSignalThemeColors colors, MotorSignalRoute route) { return ElevatedButton.icon( key: const ValueKey( 'route_detail_navigate_start_button', ), onPressed: () { final firstStop = route.stops.first; NavigationHandoffService.showHandoffSheet( context: context, latitude: firstStop.latitude, longitude: firstStop.longitude, displayName: 'Start: ${firstStop.displayName}', ); }, icon: const Icon(Icons.directions, size: 20), label: const Text( 'NAVIGATE TO ROUTE START', style: TextStyle( fontWeight: FontWeight.bold, letterSpacing: 1.1, ), ), style: ElevatedButton.styleFrom( backgroundColor: colors.cyberCyan, foregroundColor: Colors.black, minimumSize: const Size(double.infinity, 50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), ); } }