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>
This commit is contained in:
Jean-Philippe Brule
2025-11-15 20:49:20 -05:00
parent 46af8f55a2
commit 9cb5b51f6d
11 changed files with 558 additions and 192 deletions
+229
View File
@@ -8,12 +8,14 @@ class DarkModeMapComponent extends StatefulWidget {
final List<Delivery> deliveries;
final Delivery? selectedDelivery;
final ValueChanged<Delivery?>? onDeliverySelected;
final Function(String)? onAction;
const DarkModeMapComponent({
super.key,
required this.deliveries,
this.selectedDelivery,
this.onDeliverySelected,
this.onAction,
});
@override
@@ -256,6 +258,47 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
}
}
Future<void> _zoomIn() async {
if (_navigationController == null) return;
try {
final currentCamera = await _navigationController!.getCameraPosition();
await _navigationController!.animateCamera(
CameraUpdate.newLatLngZoom(
currentCamera.target,
currentCamera.zoom + 1,
),
);
} catch (e) {
debugPrint('Zoom in error: $e');
}
}
Future<void> _zoomOut() async {
if (_navigationController == null) return;
try {
final currentCamera = await _navigationController!.getCameraPosition();
await _navigationController!.animateCamera(
CameraUpdate.newLatLngZoom(
currentCamera.target,
currentCamera.zoom - 1,
),
);
} catch (e) {
debugPrint('Zoom out error: $e');
}
}
Future<void> _recenterMap() async {
if (_navigationController == null || _destinationLocation == null) return;
try {
await _navigationController!.animateCamera(
CameraUpdate.newLatLngZoom(_destinationLocation!, 15),
);
} catch (e) {
debugPrint('Recenter map error: $e');
}
}
@override
Widget build(BuildContext context) {
final initialPosition = widget.selectedDelivery?.deliveryAddress != null &&
@@ -283,6 +326,27 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
),
),
// Custom dark-themed controls overlay
Positioned(
top: 16,
right: 16,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Zoom in button
_buildIconButton(
icon: Icons.add,
onPressed: _zoomIn,
),
const SizedBox(height: 8),
// Zoom out button
_buildIconButton(
icon: Icons.remove,
onPressed: _zoomOut,
),
],
),
),
// Navigation and action buttons
Positioned(
bottom: 16,
right: 16,
@@ -388,10 +452,175 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
),
),
),
// Bottom action button bar
if (widget.selectedDelivery != null)
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
child: Row(
children: [
// Recenter button
Expanded(
child: _buildBottomActionButton(
label: 'Recenter',
icon: Icons.location_on,
onPressed: _recenterMap,
),
),
const SizedBox(width: 12),
// Mark Complete button (if not already delivered)
if (!widget.selectedDelivery!.delivered)
Expanded(
child: _buildBottomActionButton(
label: 'Mark Complete',
icon: Icons.check_circle,
onPressed: () => widget.onAction?.call('complete'),
isPrimary: true,
),
),
if (widget.selectedDelivery!.delivered)
Expanded(
child: _buildBottomActionButton(
label: 'Start Navigation',
icon: Icons.directions,
onPressed: _startNavigation,
isPrimary: true,
),
),
if (!_isNavigating && !widget.selectedDelivery!.delivered)
const SizedBox(width: 12),
if (!_isNavigating && !widget.selectedDelivery!.delivered)
Expanded(
child: _buildBottomActionButton(
label: 'Navigate',
icon: Icons.directions,
onPressed: _startNavigation,
),
),
if (_isNavigating)
const SizedBox(width: 12),
if (_isNavigating)
Expanded(
child: _buildBottomActionButton(
label: 'Stop',
icon: Icons.stop,
onPressed: _stopNavigation,
isDanger: true,
),
),
],
),
),
),
],
);
}
Widget _buildIconButton({
required IconData icon,
required VoidCallback onPressed,
}) {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Theme.of(context).colorScheme.surface,
shape: const CircleBorder(),
child: InkWell(
onTap: onPressed,
customBorder: const CircleBorder(),
child: Padding(
padding: const EdgeInsets.all(12),
child: Icon(
icon,
color: Theme.of(context).colorScheme.onSurface,
size: 24,
),
),
),
),
);
}
Widget _buildBottomActionButton({
required String label,
required IconData icon,
required VoidCallback onPressed,
bool isPrimary = false,
bool isDanger = false,
}) {
Color backgroundColor;
Color textColor = Colors.white;
if (isDanger) {
backgroundColor = Colors.red.shade600;
} else if (isPrimary) {
backgroundColor = SvrntyColors.crimsonRed;
} else {
backgroundColor = Theme.of(context).colorScheme.surfaceContainerHighest;
textColor = Theme.of(context).colorScheme.onSurface;
}
return Material(
color: backgroundColor,
borderRadius: BorderRadius.circular(8),
child: InkWell(
onTap: onPressed,
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: textColor,
size: 18,
),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
],
),
),
),
);
}
Widget _buildActionButton({
required String label,
required IconData icon,
+8 -4
View File
@@ -8,6 +8,7 @@ class DeliveryListItem extends StatefulWidget {
final bool isSelected;
final VoidCallback onTap;
final VoidCallback? onCall;
final Function(String)? onAction;
final int? animationIndex;
const DeliveryListItem({
@@ -16,6 +17,7 @@ class DeliveryListItem extends StatefulWidget {
required this.isSelected,
required this.onTap,
this.onCall,
this.onAction,
this.animationIndex,
});
@@ -112,10 +114,12 @@ class _DeliveryListItemState extends State<DeliveryListItem>
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: _isHovered || widget.isSelected
? Theme.of(context).colorScheme.surfaceContainer
: Colors.transparent,
boxShadow: _isHovered || widget.isSelected
color: widget.delivery.delivered
? Colors.green.withOpacity(0.15)
: (_isHovered || widget.isSelected
? Theme.of(context).colorScheme.surfaceContainer
: Colors.transparent),
boxShadow: (_isHovered || widget.isSelected) && !widget.delivery.delivered
? [
BoxShadow(
color: Colors.black.withOpacity(