Implement Google Navigation Flutter integration for turn-by-turn delivery navigation
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>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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';
|
||||
}
|
||||
Reference in New Issue
Block a user