ionic-planb-logistic-app-fl.../lib/pages/navigation_page.dart
Jean-Philippe Brule 9cb5b51f6d Fix Google Navigation initialization timing issues
Restructures navigation session initialization to occur after the view is
created, eliminating race conditions. Session initialization now happens in
onViewCreated callback with proper delay before setting destination.

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 20:49:20 -05:00

303 lines
8.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_navigation_flutter/google_navigation_flutter.dart';
import 'package:flutter_gen/gen_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<NavigationPage> createState() => _NavigationPageState();
}
class _NavigationPageState extends ConsumerState<NavigationPage> {
late GoogleMapsNavigationViewController _navigationViewController;
late LocationPermissionService _permissionService;
bool _isNavigationInitialized = false;
bool _hasLocationPermission = false;
@override
void initState() {
super.initState();
_permissionService = LocationPermissionService();
_initializeNavigation();
}
Future<void> _initializeNavigation() async {
try {
final hasPermission = await _permissionService.hasLocationPermission();
if (!hasPermission) {
if (mounted) {
_showPermissionDialog();
}
return;
}
setState(() {
_hasLocationPermission = true;
_isNavigationInitialized = true;
});
} catch (e) {
if (mounted) {
_showErrorDialog('Initialization error: ${e.toString()}');
}
}
}
Future<void> _initializeNavigationSession() async {
try {
await GoogleMapsNavigationViewController.initializeNavigationSession();
} catch (e) {
debugPrint('Navigation session initialization error: $e');
// Don't show error dialog, just log it
// The session might already be initialized
}
}
Future<void> _setDestination() async {
try {
final destination = NavigationDisplayOptions(
showDestinationMarkers: true,
);
final waypoint = Waypoint(
title: widget.delivery.name,
target: LatLng(
latitude: widget.destinationLatitude,
longitude: widget.destinationLongitude,
),
);
await _navigationViewController.setDestinations(
destinations: [waypoint],
displayOptions: destination,
);
// Listen for location updates
_navigationViewController.addOnLocationUpdatedListener((location) {
debugPrint(
'Location updated: ${location.latitude}, ${location.longitude}',
);
});
// Listen for navigation events
_navigationViewController.addOnNavigationUIEnabledListener((isEnabled) {
debugPrint('Navigation UI enabled: $isEnabled');
});
// Listen for waypoint reached
_navigationViewController.addOnArrivalListener((arrival) {
_handleArrival(arrival);
});
} catch (e) {
if (mounted) {
_showErrorDialog('Failed to set destination: ${e.toString()}');
}
}
}
void _handleArrival(NavInfo navInfo) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)?.navigationArrived ??
'You have arrived at the destination',
),
duration: const 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<void> _requestLocationPermission() async {
final result = await _permissionService.requestLocationPermission();
if (!mounted) return;
result.when(
granted: () {
setState(() {
_hasLocationPermission = true;
});
_initializeNavigationSession();
},
denied: () {
_showErrorDialog(
AppLocalizations.of(context)?.locationPermissionDenied ??
'Location permission denied. Navigation cannot proceed.',
);
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)?.error ?? 'Error'),
content: Text(message),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
widget.onNavigationCancelled?.call();
},
child: Text(AppLocalizations.of(context)?.ok ?? 'OK'),
),
],
),
);
}
@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;
await _initializeNavigationSession();
await Future.delayed(const Duration(milliseconds: 500));
await _setDestination();
},
initialCameraPosition: CameraPosition(
target: LatLng(
latitude: widget.destinationLatitude,
longitude: widget.destinationLongitude,
),
zoom: 15,
),
useMarkerClusteringForDynamicMarkers: true,
zoomGesturesEnabled: true,
scrollGesturesEnabled: true,
navigationUIEnabled: true,
mapToolbarEnabled: true,
)
: 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();
}
}