import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_navigation_flutter/google_navigation_flutter.dart'; import 'package:planb_logistic/l10n/app_localizations.dart'; import '../models/delivery.dart'; import '../services/location_permission_service.dart'; import '../components/navigation_tc_dialog.dart'; class NavigationPage extends ConsumerStatefulWidget { final Delivery delivery; final double destinationLatitude; final double destinationLongitude; final VoidCallback? onNavigationComplete; final VoidCallback? onNavigationCancelled; const NavigationPage({ super.key, required this.delivery, required this.destinationLatitude, required this.destinationLongitude, this.onNavigationComplete, this.onNavigationCancelled, }); @override ConsumerState createState() => _NavigationPageState(); } class _NavigationPageState extends ConsumerState { GoogleNavigationViewController? _navigationViewController; late LocationPermissionService _permissionService; bool _isNavigationInitialized = false; bool _hasLocationPermission = false; bool _isControllerReady = false; Brightness? _lastBrightness; @override void initState() { super.initState(); _permissionService = LocationPermissionService(); _initializeNavigation(); } @override void didChangeDependencies() { super.didChangeDependencies(); // Detect theme changes and reapply map style final currentBrightness = Theme.of(context).brightness; if (_lastBrightness != null && _lastBrightness != currentBrightness && _isControllerReady) { _applyDarkModeStyle(); } _lastBrightness = currentBrightness; } Future _initializeNavigation() async { try { final hasPermission = await _permissionService.hasLocationPermission(); if (!hasPermission) { if (mounted) { await _requestLocationPermission(); } return; } setState(() { _hasLocationPermission = true; _isNavigationInitialized = true; }); } catch (e) { if (mounted) { _showErrorDialog('Initialization error: ${e.toString()}'); } } } Future _initializeNavigationSession() async { try { await GoogleMapsNavigator.initializeNavigationSession(); } catch (e) { debugPrint('Navigation session initialization error: $e'); // Don't show error dialog, just log it // The session might already be initialized } } Future _setDestination() async { try { final waypoint = NavigationWaypoint.withLatLngTarget( title: widget.delivery.name, target: LatLng( latitude: widget.destinationLatitude, longitude: widget.destinationLongitude, ), ); final destinations = Destinations( waypoints: [waypoint], displayOptions: NavigationDisplayOptions(showDestinationMarkers: true), ); await GoogleMapsNavigator.setDestinations(destinations); // Start guidance automatically await GoogleMapsNavigator.startGuidance(); // Reapply dark mode style after navigation starts if (mounted) { await _applyDarkModeStyle(); } // Listen for arrival events GoogleMapsNavigator.setOnArrivalListener((event) { if (mounted) { _handleArrival(event); } }); } catch (e) { if (mounted) { _showErrorDialog('Failed to set destination: ${e.toString()}'); } } } void _handleArrival(OnArrivalEvent event) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('You have arrived at the destination'), duration: Duration(seconds: 3), ), ); // Call completion callback widget.onNavigationComplete?.call(); } } void _showPermissionDialog() { final l10n = AppLocalizations.of(context); showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: Text(l10n?.locationPermissionRequired ?? 'Location Permission'), content: Text( l10n?.locationPermissionMessage ?? 'This app requires location permission to navigate to deliveries.', ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); widget.onNavigationCancelled?.call(); }, child: Text(l10n?.cancel ?? 'Cancel'), ), TextButton( onPressed: () { Navigator.of(context).pop(); _requestLocationPermission(); }, child: Text(l10n?.requestPermission ?? 'Request Permission'), ), ], ), ); } Future _requestLocationPermission() async { final result = await _permissionService.requestLocationPermission(); if (!mounted) return; result.when( granted: () { setState(() { _hasLocationPermission = true; _isNavigationInitialized = true; }); _initializeNavigationSession(); }, denied: () { _showErrorDialog( AppLocalizations.of(context).locationPermissionDenied, ); widget.onNavigationCancelled?.call(); }, permanentlyDenied: () { _showPermissionSettingsDialog(); }, error: (message) { _showErrorDialog(message); widget.onNavigationCancelled?.call(); }, ); } void _showPermissionSettingsDialog() { final l10n = AppLocalizations.of(context); showDialog( context: context, builder: (context) => AlertDialog( title: Text(l10n?.permissionPermanentlyDenied ?? 'Permission Required'), content: Text( l10n?.openSettingsMessage ?? 'Location permission is permanently denied. Please enable it in app settings.', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text(l10n?.cancel ?? 'Cancel'), ), TextButton( onPressed: () { _permissionService.openAppSettings(); Navigator.of(context).pop(); }, child: Text(l10n?.openSettings ?? 'Open Settings'), ), ], ), ); } void _showErrorDialog(String message) { showDialog( context: context, builder: (context) => AlertDialog( title: Text(AppLocalizations.of(context).errorTitle), content: Text(message), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); widget.onNavigationCancelled?.call(); }, child: Text(AppLocalizations.of(context).ok), ), ], ), ); } Future _applyDarkModeStyle() async { if (_navigationViewController == null || !_isControllerReady) return; try { final isDarkMode = Theme.of(context).brightness == Brightness.dark; if (isDarkMode) { await _navigationViewController!.setMapStyle(_getDarkMapStyle()); } else { await _navigationViewController!.setMapStyle(null); } } catch (e) { debugPrint('Error applying map style: $e'); } } String _getDarkMapStyle() { // Google Maps style JSON for dark mode with warm accents return '''[ { "elementType": "geometry", "stylers": [{"color": "#212121"}] }, { "elementType": "labels.icon", "stylers": [{"visibility": "off"}] }, { "elementType": "labels.text.fill", "stylers": [{"color": "#757575"}] }, { "elementType": "labels.text.stroke", "stylers": [{"color": "#212121"}] }, { "featureType": "administrative", "elementType": "geometry", "stylers": [{"color": "#757575"}] }, { "featureType": "administrative.country", "elementType": "labels.text.fill", "stylers": [{"color": "#9e9e9e"}] }, { "featureType": "administrative.land_parcel", "stylers": [{"visibility": "off"}] }, { "featureType": "administrative.locality", "elementType": "labels.text.fill", "stylers": [{"color": "#bdbdbd"}] }, { "featureType": "administrative.neighborhood", "stylers": [{"visibility": "off"}] }, { "featureType": "administrative.province", "elementType": "labels.text.fill", "stylers": [{"color": "#9e9e9e"}] }, { "featureType": "landscape", "elementType": "geometry", "stylers": [{"color": "#000000"}] }, { "featureType": "poi", "elementType": "geometry", "stylers": [{"color": "#383838"}] }, { "featureType": "poi", "elementType": "labels.text.fill", "stylers": [{"color": "#9e9e9e"}] }, { "featureType": "poi.park", "elementType": "geometry", "stylers": [{"color": "#181818"}] }, { "featureType": "poi.park", "elementType": "labels.text.fill", "stylers": [{"color": "#616161"}] }, { "featureType": "road", "elementType": "geometry.fill", "stylers": [{"color": "#2c2c2c"}] }, { "featureType": "road", "elementType": "labels.text.fill", "stylers": [{"color": "#8a8a8a"}] }, { "featureType": "road.arterial", "elementType": "geometry", "stylers": [{"color": "#373737"}] }, { "featureType": "road.highway", "elementType": "geometry", "stylers": [{"color": "#3c3c3c"}] }, { "featureType": "road.highway.controlled_access", "elementType": "geometry", "stylers": [{"color": "#4e4e4e"}] }, { "featureType": "road.local", "elementType": "labels.text.fill", "stylers": [{"color": "#616161"}] }, { "featureType": "transit", "elementType": "labels.text.fill", "stylers": [{"color": "#757575"}] }, { "featureType": "water", "elementType": "geometry", "stylers": [{"color": "#0c1221"}] }, { "featureType": "water", "elementType": "labels.text.fill", "stylers": [{"color": "#3d3d3d"}] } ]'''; } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); return Scaffold( appBar: AppBar( title: Text( '${l10n?.navigatingTo ?? 'Navigating to'}: ${widget.delivery.name}', ), elevation: 0, ), body: _hasLocationPermission && _isNavigationInitialized ? GoogleMapsNavigationView( onViewCreated: (controller) async { _navigationViewController = controller; _isControllerReady = true; await _initializeNavigationSession(); await Future.delayed(const Duration(milliseconds: 500)); await _applyDarkModeStyle(); await _setDestination(); }, initialCameraPosition: CameraPosition( target: LatLng( latitude: widget.destinationLatitude, longitude: widget.destinationLongitude, ), zoom: 15, ), ) : Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(), const SizedBox(height: 16), Text( l10n?.initializingNavigation ?? 'Initializing navigation...', ), ], ), ), floatingActionButton: _hasLocationPermission && _isNavigationInitialized ? FloatingActionButton( onPressed: () { widget.onNavigationCancelled?.call(); Navigator.of(context).pop(); }, child: const Icon(Icons.close), ) : null, ); } @override void dispose() { super.dispose(); } }