import 'package:flutter/material.dart'; import 'package:google_navigation_flutter/google_navigation_flutter.dart'; import '../models/delivery.dart'; import '../theme/color_system.dart'; /// Enhanced dark-mode aware map component with custom styling class DarkModeMapComponent extends StatefulWidget { final List deliveries; final Delivery? selectedDelivery; final ValueChanged? onDeliverySelected; final Function(String)? onAction; const DarkModeMapComponent({ super.key, required this.deliveries, this.selectedDelivery, this.onDeliverySelected, this.onAction, }); @override State createState() => _DarkModeMapComponentState(); } class _DarkModeMapComponentState extends State { GoogleNavigationViewController? _navigationController; bool _isNavigating = false; LatLng? _destinationLocation; bool _isSessionInitialized = false; bool _isInitializing = false; bool _isStartingNavigation = false; String _loadingMessage = 'Initializing...'; Brightness? _lastBrightness; @override void initState() { super.initState(); _initializeNavigation(); } @override void didChangeDependencies() { super.didChangeDependencies(); // Detect theme changes and reapply map style final currentBrightness = Theme.of(context).brightness; if (_lastBrightness != null && _lastBrightness != currentBrightness && _navigationController != null) { _applyDarkModeStyle(); } _lastBrightness = currentBrightness; } Future _initializeNavigation() async { if (_isInitializing || _isSessionInitialized) return; setState(() { _isInitializing = true; }); try { final termsAccepted = await GoogleMapsNavigator.areTermsAccepted(); if (!termsAccepted) { await GoogleMapsNavigator.showTermsAndConditionsDialog( 'Plan B Logistics', 'com.goutezplanb.planbLogistic', ); } await GoogleMapsNavigator.initializeNavigationSession(); if (mounted) { setState(() { _isSessionInitialized = true; _isInitializing = false; }); } } catch (e) { final errorMessage = _formatErrorMessage(e); debugPrint('Map initialization error: $errorMessage'); if (mounted) { setState(() { _isInitializing = false; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Navigation initialization failed: $errorMessage'), duration: const Duration(seconds: 5), ), ); } } } String _formatErrorMessage(Object error) { final errorString = error.toString(); if (errorString.contains('SessionNotInitializedException')) { return 'Google Maps navigation session could not be initialized'; } else if (errorString.contains('permission')) { return 'Location permission is required for navigation'; } else if (errorString.contains('network')) { return 'Network connection error'; } return errorString; } @override void didUpdateWidget(DarkModeMapComponent oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.selectedDelivery != widget.selectedDelivery) { _updateDestination(); } } void _updateDestination() { if (widget.selectedDelivery != null) { final address = widget.selectedDelivery!.deliveryAddress; if (address?.latitude != null && address?.longitude != null) { setState(() { _destinationLocation = LatLng( latitude: address!.latitude!, longitude: address.longitude!, ); }); // Just store the destination, don't move camera // The navigation will handle camera positioning } } } Future _applyDarkModeStyle() async { // Check if widget is still mounted and controller exists if (!mounted || _navigationController == null) return; try { if (!mounted) return; final isDarkMode = Theme.of(context).brightness == Brightness.dark; if (isDarkMode) { // Dark mode style - Note: Currently only supported on Android const simpleDarkStyle = '''[ { "elementType": "geometry", "stylers": [{"color": "#242424"}] }, { "elementType": "labels.text.fill", "stylers": [{"color": "#746855"}] }, { "elementType": "labels.text.stroke", "stylers": [{"color": "#242424"}] }, { "featureType": "water", "elementType": "geometry", "stylers": [{"color": "#17263c"}] } ]'''; await _navigationController!.setMapStyle(simpleDarkStyle); } else { // Reset to default light style await _navigationController!.setMapStyle(null); } } catch (e) { if (mounted) { debugPrint('Error applying map style: $e'); } } } Future _startNavigation() async { if (_destinationLocation == null) return; // Show loading indicator if (mounted) { setState(() { _isStartingNavigation = true; _loadingMessage = 'Starting navigation...'; }); } try { // Ensure session is initialized before starting navigation if (!_isSessionInitialized && !_isInitializing) { debugPrint('Initializing navigation session...'); await _initializeNavigation(); } // Wait for initialization to complete if it's in progress int retries = 0; while (!_isSessionInitialized && retries < 30) { await Future.delayed(const Duration(milliseconds: 100)); retries++; } if (!_isSessionInitialized) { if (mounted) { setState(() { _isStartingNavigation = false; }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Navigation initialization timeout'), duration: Duration(seconds: 3), ), ); } return; } if (mounted) { setState(() { _loadingMessage = 'Setting destination...'; }); } final waypoint = NavigationWaypoint.withLatLngTarget( title: widget.selectedDelivery?.name ?? 'Destination', target: _destinationLocation!, ); final destinations = Destinations( waypoints: [waypoint], displayOptions: NavigationDisplayOptions(showDestinationMarkers: true), ); if (mounted) { setState(() { _loadingMessage = 'Starting guidance...'; }); } debugPrint('Setting destinations: ${_destinationLocation!.latitude}, ${_destinationLocation!.longitude}'); await GoogleMapsNavigator.setDestinations(destinations); debugPrint('Starting guidance...'); await GoogleMapsNavigator.startGuidance(); debugPrint('Navigation started successfully'); // Reapply dark mode style after navigation starts if (mounted) { await _applyDarkModeStyle(); } // Auto-recenter on driver location when navigation starts await _recenterMap(); debugPrint('Camera recentered on driver location'); if (mounted) { setState(() { _isNavigating = true; _isStartingNavigation = false; }); } } catch (e) { final errorMessage = _formatErrorMessage(e); debugPrint('Navigation start error: $errorMessage'); debugPrint('Full error: $e'); if (mounted) { setState(() { _isStartingNavigation = false; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Navigation error: $errorMessage'), duration: const Duration(seconds: 4), ), ); } } } Future _stopNavigation() async { try { await GoogleMapsNavigator.stopGuidance(); await GoogleMapsNavigator.clearDestinations(); if (mounted) { setState(() { _isNavigating = false; }); } } catch (e) { if (mounted) { debugPrint('Navigation stop error: $e'); } } } Future _recenterMap() async { if (_navigationController == null) return; try { // Use the navigation controller's follow location feature // This tells the navigation to follow the driver's current location await _navigationController!.followMyLocation(CameraPerspective.tilted); debugPrint('Navigation set to follow driver location'); } catch (e) { debugPrint('Recenter map error: $e'); } } @override Widget build(BuildContext context) { // Driver's current location (defaults to Montreal if not available) final initialPosition = const LatLng(latitude: 45.5017, longitude: -73.5673); // Calculate dynamic padding for bottom button bar final topPadding = 0.0; final bottomPadding = widget.selectedDelivery != null ? 110.0 : 0.0; return Stack( children: [ // Map with padding to accommodate overlaid elements Padding( padding: EdgeInsets.only( top: topPadding, bottom: bottomPadding, ), child: GoogleMapsNavigationView( onViewCreated: (controller) async { _navigationController = controller; // Apply dark mode style with a small delay to ensure map is ready await Future.delayed(const Duration(milliseconds: 500)); await _applyDarkModeStyle(); controller.animateCamera( CameraUpdate.newLatLngZoom(initialPosition, 12), ); }, initialCameraPosition: CameraPosition( target: initialPosition, zoom: 12, ), ), ), // Bottom action button bar if (widget.selectedDelivery != null) Positioned( bottom: 0, left: 0, right: 0, child: Container( decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.2), blurRadius: 8, offset: const Offset(0, -2), ), ], ), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), child: Row( children: [ // Recenter button Expanded( child: _buildBottomActionButton( label: 'Recenter', icon: Icons.location_on, onPressed: _recenterMap, ), ), const SizedBox(width: 12), // Mark Complete button (if not already delivered) if (!widget.selectedDelivery!.delivered) Expanded( child: _buildBottomActionButton( label: 'Mark Complete', icon: Icons.check_circle, onPressed: () => widget.onAction?.call('complete'), isPrimary: true, ), ), if (widget.selectedDelivery!.delivered) Expanded( child: _buildBottomActionButton( label: _isInitializing ? 'Initializing...' : 'Start Navigation', icon: Icons.directions, onPressed: _isInitializing ? null : _startNavigation, isPrimary: true, ), ), if (!_isNavigating && !widget.selectedDelivery!.delivered) const SizedBox(width: 12), if (!_isNavigating && !widget.selectedDelivery!.delivered) Expanded( child: _buildBottomActionButton( label: _isStartingNavigation || _isInitializing ? 'Loading...' : 'Navigate', icon: Icons.directions, onPressed: _isStartingNavigation || _isInitializing ? null : _startNavigation, ), ), if (_isNavigating) const SizedBox(width: 12), if (_isNavigating) Expanded( child: _buildBottomActionButton( label: 'Stop', icon: Icons.stop, onPressed: _stopNavigation, isDanger: true, ), ), ], ), ), ), // Loading overlay during navigation initialization and start if (_isStartingNavigation || _isInitializing) Positioned.fill( child: Container( color: Colors.black.withValues(alpha: 0.4), child: Center( child: Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Circular progress indicator SizedBox( width: 60, height: 60, child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.primary, ), strokeWidth: 3, ), ), const SizedBox(height: 16), // Loading message Text( _loadingMessage, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), // Secondary message Text( 'Please wait...', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), textAlign: TextAlign.center, ), ], ), ), ), ), ), ], ); } Widget _buildBottomActionButton({ required String label, required IconData icon, required VoidCallback? onPressed, bool isPrimary = false, bool isDanger = false, }) { Color backgroundColor; Color textColor = Colors.white; if (isDanger) { backgroundColor = Colors.red.shade600; } else if (isPrimary) { backgroundColor = SvrntyColors.crimsonRed; } else { backgroundColor = Theme.of(context).colorScheme.surfaceContainerHighest; textColor = Theme.of(context).colorScheme.onSurface; } // Reduce opacity when disabled if (onPressed == null) { backgroundColor = backgroundColor.withValues(alpha: 0.5); } return Material( color: backgroundColor, borderRadius: BorderRadius.circular(8), child: InkWell( onTap: onPressed, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 12, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ Icon( icon, color: textColor, size: 18, ), const SizedBox(width: 6), Text( label, style: TextStyle( color: textColor, fontWeight: FontWeight.w500, fontSize: 14, ), ), ], ), ), ), ); } Widget _buildActionButton({ required String label, required IconData icon, required VoidCallback? onPressed, required Color color, }) { final isDisabled = onPressed == null; final buttonColor = isDisabled ? color.withValues(alpha: 0.5) : color; return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.3), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Material( color: buttonColor, borderRadius: BorderRadius.circular(8), child: InkWell( onTap: onPressed, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, color: Colors.white, size: 18, ), const SizedBox(width: 6), Text( label, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.w500, fontSize: 14, ), ), ], ), ), ), ), ); } }