import 'package:google_navigation_flutter/google_navigation_flutter.dart'; class NavigationSessionService { static final NavigationSessionService _instance = NavigationSessionService._internal(); factory NavigationSessionService() { return _instance; } NavigationSessionService._internal(); bool _isSessionInitialized = false; GoogleNavigationViewController? _controller; bool get isSessionInitialized => _isSessionInitialized; Future initializeSession() async { if (_isSessionInitialized) { return; } try { await GoogleMapsNavigator.initializeNavigationSession(); _isSessionInitialized = true; } catch (e) { throw NavigationSessionException('Failed to initialize session: $e'); } } Future setController( GoogleNavigationViewController controller, ) async { _controller = controller; } Future calculateRoute({ required double startLatitude, required double startLongitude, required double destinationLatitude, required double destinationLongitude, }) async { if (!_isSessionInitialized) { throw NavigationSessionException('Session not initialized'); } if (_controller == null) { throw NavigationSessionException('Controller not set'); } try { final origin = LatLng( latitude: startLatitude, longitude: startLongitude, ); final destination = LatLng( latitude: destinationLatitude, longitude: destinationLongitude, ); final waypoint = NavigationWaypoint.withLatLngTarget( title: 'Destination', target: destination, ); final destinations = Destinations( waypoints: [waypoint], displayOptions: NavigationDisplayOptions(showDestinationMarkers: true), ); // Set destinations will trigger route calculation await GoogleMapsNavigator.setDestinations(destinations); return NavigationRoute( startLocation: origin, endLocation: destination, isCalculated: true, ); } catch (e) { throw NavigationSessionException('Failed to calculate route: $e'); } } Future startNavigation() async { if (!_isSessionInitialized) { throw NavigationSessionException('Navigation not properly initialized'); } try { await GoogleMapsNavigator.startGuidance(); } catch (e) { throw NavigationSessionException('Failed to start navigation: $e'); } } Future stopNavigation() async { if (!_isSessionInitialized) { return; } try { await GoogleMapsNavigator.stopGuidance(); } catch (e) { throw NavigationSessionException('Failed to stop navigation: $e'); } } void addLocationListener( Function(RoadSnappedLocationUpdatedEvent event) onLocationUpdate, ) { if (!_isSessionInitialized) { throw NavigationSessionException('Navigation not initialized'); } GoogleMapsNavigator.setRoadSnappedLocationUpdatedListener((event) { onLocationUpdate(event); }); } void addArrivalListener(Function(OnArrivalEvent event) onArrival) { if (!_isSessionInitialized) { throw NavigationSessionException('Navigation not initialized'); } GoogleMapsNavigator.setOnArrivalListener((event) { onArrival(event); }); } // Note: Remaining distance listener API may vary by version // This is a placeholder for future implementation void addRemainingDistanceListener( Function(dynamic event) onDistanceChange, ) { if (!_isSessionInitialized) { throw NavigationSessionException('Navigation not initialized'); } // TODO: Implement when correct API is available // GoogleMapsNavigator does not expose a public remaining distance listener } void clearAllListeners() { if (!_isSessionInitialized) { return; } // Clear listeners by setting them to empty callbacks // Note: The API doesn't support null, so we use no-op callbacks GoogleMapsNavigator.setRoadSnappedLocationUpdatedListener((_) {}); GoogleMapsNavigator.setOnArrivalListener((_) {}); } Future cleanup() async { try { if (_isSessionInitialized) { await stopNavigation(); clearAllListeners(); await GoogleMapsNavigator.cleanup(); } _isSessionInitialized = false; _controller = null; } catch (e) { throw NavigationSessionException('Failed to cleanup: $e'); } } } class NavigationRoute { final LatLng startLocation; final LatLng endLocation; final bool isCalculated; final int? distanceMeters; final Duration? estimatedTime; NavigationRoute({ required this.startLocation, required this.endLocation, required this.isCalculated, this.distanceMeters, this.estimatedTime, }); } class NavigationSessionException implements Exception { final String message; NavigationSessionException(this.message); @override String toString() => 'NavigationSessionException: $message'; }