Adds complete Google Navigation support with: - LocationPermissionService for runtime location permissions - NavigationSessionService for session and route management - NavigationPage for full-screen turn-by-turn navigation UI - NavigationTermsAndConditionsDialog for service acceptance - Comprehensive i18n support (English/French) - Android minSdk=23 with Java NIO desugaring - iOS location permissions in Info.plist - Error handling with user-friendly dialogs - Location update and arrival notifications Includes detailed setup guide and implementation documentation with API key configuration instructions, integration examples, and testing checklist. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
185 lines
4.3 KiB
Dart
185 lines
4.3 KiB
Dart
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;
|
|
GoogleMapsNavigationViewController? _controller;
|
|
|
|
bool get isSessionInitialized => _isSessionInitialized;
|
|
|
|
Future<void> initializeSession() async {
|
|
if (_isSessionInitialized) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await GoogleMapsNavigationViewController.initializeNavigationSession();
|
|
_isSessionInitialized = true;
|
|
} catch (e) {
|
|
throw NavigationSessionException('Failed to initialize session: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> setController(
|
|
GoogleMapsNavigationViewController controller,
|
|
) async {
|
|
_controller = controller;
|
|
}
|
|
|
|
Future<NavigationRoute> 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 = Waypoint(
|
|
target: destination,
|
|
);
|
|
|
|
// Set destinations will trigger route calculation
|
|
await _controller!.setDestinations(
|
|
destinations: [waypoint],
|
|
);
|
|
|
|
return NavigationRoute(
|
|
startLocation: origin,
|
|
endLocation: destination,
|
|
isCalculated: true,
|
|
);
|
|
} catch (e) {
|
|
throw NavigationSessionException('Failed to calculate route: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> startNavigation() async {
|
|
if (!_isSessionInitialized || _controller == null) {
|
|
throw NavigationSessionException('Navigation not properly initialized');
|
|
}
|
|
|
|
try {
|
|
await _controller!.startGuidance();
|
|
} catch (e) {
|
|
throw NavigationSessionException('Failed to start navigation: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> stopNavigation() async {
|
|
if (_controller == null) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await _controller!.stopGuidance();
|
|
} catch (e) {
|
|
throw NavigationSessionException('Failed to stop navigation: $e');
|
|
}
|
|
}
|
|
|
|
void addLocationListener(
|
|
Function(LatLng location) onLocationUpdate,
|
|
) {
|
|
if (_controller == null) {
|
|
throw NavigationSessionException('Controller not set');
|
|
}
|
|
|
|
_controller!.addOnLocationUpdatedListener((location) {
|
|
onLocationUpdate(location);
|
|
});
|
|
}
|
|
|
|
void addArrivalListener(Function(NavInfo info) onArrival) {
|
|
if (_controller == null) {
|
|
throw NavigationSessionException('Controller not set');
|
|
}
|
|
|
|
_controller!.addOnArrivalListener((info) {
|
|
onArrival(info);
|
|
});
|
|
}
|
|
|
|
void addRemainingDistanceListener(
|
|
Function(int distanceMeters) onDistanceChange,
|
|
) {
|
|
if (_controller == null) {
|
|
throw NavigationSessionException('Controller not set');
|
|
}
|
|
|
|
_controller!.addOnRemainingDistanceChangedListener((distance) {
|
|
onDistanceChange(distance);
|
|
});
|
|
}
|
|
|
|
void clearAllListeners() {
|
|
if (_controller == null) {
|
|
return;
|
|
}
|
|
|
|
_controller!.removeAllListeners();
|
|
}
|
|
|
|
Future<void> cleanup() async {
|
|
try {
|
|
if (_controller != null) {
|
|
await stopNavigation();
|
|
clearAllListeners();
|
|
}
|
|
_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';
|
|
}
|