Refactor theme system and remove unused platforms

- Overhaul theme system with Svrnty design and WCAG AAA compliance
- Remove android, macos, and web platform files (iOS-only focus)
- Update components with improved dark mode map and UI refinements
- Enhance settings page with additional configuration options
- Add theme system documentation in lib/theme/README.md
- Update CLAUDE.md with comprehensive theme guidelines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 14:47:51 -05:00
parent 554b26cfd1
commit edb106a7fd
102 changed files with 1225 additions and 2785 deletions
+312 -98
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:google_navigation_flutter/google_navigation_flutter.dart';
import '../models/delivery.dart';
import '../theme/color_system.dart';
import '../utils/breakpoints.dart';
import '../utils/toast_helper.dart';
/// Enhanced dark-mode aware map component with custom styling
@@ -36,11 +37,21 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
Brightness? _lastBrightness;
bool _isMapViewReady = false;
bool _isDisposed = false;
bool _isAudioGuidanceEnabled = false; // Audio guidance off by default
bool _pendingNavigationStart = false; // User requested navigation before map was ready
@override
void initState() {
super.initState();
// Set destination if delivery is already selected when widget is created
_updateDestination();
_initializeNavigation();
// Ensure state is synced after a brief delay to allow map to fully load
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted && !_isDisposed) {
_syncNavigationState();
}
});
}
@override
@@ -55,6 +66,8 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
super.didUpdateWidget(oldWidget);
if (oldWidget.selectedDelivery != widget.selectedDelivery) {
_updateDestination();
// Sync navigation state with actual SDK state
_syncNavigationState();
// If navigation was active, restart navigation to new delivery
if (_isNavigating &&
@@ -95,13 +108,48 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
}
}
/// Sync local navigation state with actual SDK navigation state
Future<void> _syncNavigationState() async {
try {
final bool isActuallyNavigating = await GoogleMapsNavigator.isGuidanceRunning();
if (mounted && !_isDisposed) {
if (_isNavigating != isActuallyNavigating) {
debugPrint('State mismatch detected! Local: $_isNavigating, SDK: $isActuallyNavigating');
setState(() {
_isNavigating = isActuallyNavigating;
});
debugPrint('Navigation state synced to: $_isNavigating');
} else {
debugPrint('Navigation state already in sync: $_isNavigating');
}
}
} catch (e) {
debugPrint('Failed to sync navigation state: $e');
}
}
Future<void> _initializeNavigation() async {
if (_isInitializing || _isSessionInitialized) return;
if (_isInitializing || _isSessionInitialized) {
debugPrint('Skipping initialization: initializing=$_isInitializing, initialized=$_isSessionInitialized');
return;
}
debugPrint('Initializing navigation session...');
setState(() {
_isInitializing = true;
});
// Safety timeout to reset flag if initialization takes too long
Future.delayed(const Duration(seconds: 15), () {
if (mounted && _isInitializing && !_isSessionInitialized) {
debugPrint('Initialization timeout - resetting flag');
setState(() {
_isInitializing = false;
});
}
});
try {
final termsAccepted = await GoogleMapsNavigator.areTermsAccepted();
if (!termsAccepted) {
@@ -148,15 +196,25 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
if (widget.selectedDelivery != null) {
final address = widget.selectedDelivery!.deliveryAddress;
if (address?.latitude != null && address?.longitude != null) {
final lat = address!.latitude!;
final lon = address.longitude!;
setState(() {
_destinationLocation = LatLng(
latitude: address!.latitude!,
longitude: address.longitude!,
latitude: lat,
longitude: lon,
);
});
debugPrint('Destination set to: $lat, $lon for delivery: ${widget.selectedDelivery!.name}');
// Just store the destination, don't move camera
// The navigation will handle camera positioning
} else {
debugPrint('Delivery ${widget.selectedDelivery!.name} has no valid address');
}
} else {
debugPrint('No delivery selected, clearing destination');
setState(() {
_destinationLocation = null;
});
}
}
@@ -167,6 +225,15 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
try {
if (!mounted || _isDisposed) return;
// Get current theme brightness
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
// Force night mode based on theme
await _navigationController!.setForceNightMode(
isDarkMode ? NavigationForceNightMode.forceNight : NavigationForceNightMode.forceDay,
);
debugPrint('Night mode set to: ${isDarkMode ? 'night' : 'day'}');
// Force dark mode map style using Google's standard dark theme
const String darkMapStyle = '''
[
@@ -269,8 +336,51 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
}
}
Future<void> _toggleAudioGuidance() async {
if (_isDisposed) return;
try {
final newState = !_isAudioGuidanceEnabled;
await GoogleMapsNavigator.setAudioGuidance(
NavigationAudioGuidanceSettings(
guidanceType: newState
? NavigationAudioGuidanceType.alertsAndGuidance
: NavigationAudioGuidanceType.silent,
),
);
if (mounted) {
setState(() {
_isAudioGuidanceEnabled = newState;
});
debugPrint('Audio guidance ${newState ? 'enabled' : 'disabled'}');
}
} catch (e) {
debugPrint('Error toggling audio guidance: $e');
}
}
Future<void> _startNavigation() async {
if (_destinationLocation == null) return;
if (_destinationLocation == null) {
debugPrint('Cannot start navigation: no destination set');
return;
}
debugPrint('Starting navigation to: $_destinationLocation');
// Check if map is ready before attempting to start
if (!_isMapViewReady) {
debugPrint('Map not ready yet - navigation will start automatically when ready');
if (mounted) {
setState(() {
_pendingNavigationStart = true;
_isStartingNavigation = true;
_loadingMessage = 'Waiting for map to load...';
});
ToastHelper.showInfo(context, 'Map is loading. Navigation will start automatically...');
}
return;
}
// Show loading indicator
if (mounted) {
@@ -280,6 +390,19 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
});
}
// Safety timeout to reset flag if navigation start takes too long
Future.delayed(const Duration(seconds: 10), () {
if (mounted && _isStartingNavigation) {
debugPrint('Navigation start timeout - resetting flag');
setState(() {
_isStartingNavigation = false;
});
ToastHelper.showError(context, 'Navigation start timed out. Please try again.');
}
});
bool navigationStarted = false;
try {
// Ensure session is initialized before starting navigation
if (!_isSessionInitialized && !_isInitializing) {
@@ -296,9 +419,6 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
if (!_isSessionInitialized) {
if (mounted) {
setState(() {
_isStartingNavigation = false;
});
ToastHelper.showError(context, 'Navigation initialization timeout');
}
return;
@@ -364,6 +484,7 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
setState(() {
_isNavigating = true;
_isStartingNavigation = false;
_pendingNavigationStart = false; // Clear pending flag on success
});
}
} catch (e) {
@@ -374,6 +495,7 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
if (mounted) {
setState(() {
_isStartingNavigation = false;
_pendingNavigationStart = false; // Clear pending flag on error
});
ToastHelper.showError(context, 'Navigation error: $errorMessage', duration: const Duration(seconds: 4));
@@ -396,14 +518,28 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
await GoogleMapsNavigator.stopGuidance();
await GoogleMapsNavigator.clearDestinations();
// Wait a moment for the SDK to fully stop
await Future.delayed(const Duration(milliseconds: 200));
// Verify navigation actually stopped by checking SDK state
final bool isStillRunning = await GoogleMapsNavigator.isGuidanceRunning();
if (mounted) {
setState(() {
_isNavigating = false;
_isNavigating = isStillRunning;
_pendingNavigationStart = false; // Clear pending flag when stopping
});
debugPrint('Navigation stopped, state synced: $_isNavigating');
}
} catch (e) {
if (mounted) {
debugPrint('Navigation stop error: $e');
setState(() {
_pendingNavigationStart = false; // Clear pending flag on error
});
// Even on error, try to sync state
await _syncNavigationState();
}
}
}
@@ -429,12 +565,17 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
@override
Widget build(BuildContext context) {
// Driver's current location (defaults to Montreal if not available)
final initialPosition = const LatLng(latitude: 45.5017, longitude: -73.5673);
// Driver's current location (defaults to Trois-Rivières if not available)
final initialPosition = const LatLng(latitude: 46.33857534324389, longitude: -72.60787418369715);
// Get safe area insets to avoid rounded corners and notches
final bottomSafeArea = MediaQuery.of(context).padding.bottom;
// Calculate dynamic padding for bottom button bar
// Must account for full button container height to prevent map showing underneath
// Button container height = top padding (8) + button height (~44) + bottom padding (bottomSafeArea + 8)
final topPadding = 0.0;
final bottomPadding = 60.0;
final bottomPadding = 100.0; // Full height of button container to properly cut off map
return Stack(
children: [
@@ -477,11 +618,34 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
if (!mounted || _isDisposed) return;
await controller.setNavigationFooterEnabled(true);
if (!mounted || _isDisposed) return;
await controller.setNavigationTripProgressBarEnabled(true);
// Disable navigation trip progress bar
await controller.setNavigationTripProgressBarEnabled(false);
if (!mounted || _isDisposed) return;
// Enable recenter button
await controller.setRecenterButtonEnabled(true);
if (!mounted || _isDisposed) return;
// Enable speed limit icon
await controller.setSpeedLimitIconEnabled(true);
if (!mounted || _isDisposed) return;
// Enable traffic incident cards
await controller.setTrafficIncidentCardsEnabled(true);
if (!mounted || _isDisposed) return;
// Disable traffic prompts
await controller.setTrafficPromptsEnabled(false);
if (!mounted || _isDisposed) return;
// Disable report incident button
await controller.setReportIncidentButtonEnabled(false);
debugPrint('Navigation UI elements enabled');
if (!mounted || _isDisposed) return;
// Enable speedometer
await controller.setSpeedometerEnabled(true);
if (!mounted || _isDisposed) return;
// Set audio guidance to silent by default
await GoogleMapsNavigator.setAudioGuidance(
NavigationAudioGuidanceSettings(
guidanceType: NavigationAudioGuidanceType.silent,
),
);
debugPrint('Navigation UI elements configured');
// Configure map settings to reduce GPU load for devices with limited graphics capabilities
if (!mounted || _isDisposed) return;
@@ -500,40 +664,42 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
if (!mounted || _isDisposed) return;
await _applyDarkModeStyle();
// Wrap camera animation in try-catch to handle "No valid view found" errors
// This can happen on Android when the view isn't fully ready
// Immediately follow user location on map initialization
try {
if (mounted && _navigationController != null && _isMapViewReady && !_isDisposed) {
await controller.animateCamera(
CameraUpdate.newLatLngZoom(initialPosition, 12),
);
// Start following user location immediately
await _recenterMap();
debugPrint('Map initialized following user location');
// Sync navigation state to ensure button reflects actual navigation state
await _syncNavigationState();
// Auto-recenter to current location after initial setup
await Future.delayed(const Duration(milliseconds: 500));
if (mounted && _navigationController != null && !_isDisposed) {
await _recenterMap();
debugPrint('Auto-recentered map to current location on initialization');
// Auto-start navigation if user requested it before map was ready
if (_pendingNavigationStart && !_isNavigating && !_isDisposed && mounted) {
debugPrint('Auto-starting navigation as requested by user');
_pendingNavigationStart = false;
await _startNavigation();
}
}
} catch (e) {
debugPrint('Camera animation error (view may not be ready): $e');
debugPrint('Follow location error (view may not be ready): $e');
if (_isDisposed || !mounted) return;
// Retry once after a longer delay
await Future.delayed(const Duration(milliseconds: 1500));
if (mounted && _navigationController != null && _isMapViewReady && !_isDisposed) {
try {
await controller.animateCamera(
CameraUpdate.newLatLngZoom(initialPosition, 12),
);
await _recenterMap();
debugPrint('Map initialized following user location (retry)');
// Sync navigation state to ensure button reflects actual navigation state
await _syncNavigationState();
// Auto-recenter to current location after retry
await Future.delayed(const Duration(milliseconds: 500));
if (mounted && _navigationController != null && !_isDisposed) {
await _recenterMap();
debugPrint('Auto-recentered map to current location on initialization (retry)');
// Auto-start navigation if user requested it before map was ready
if (_pendingNavigationStart && !_isNavigating && !_isDisposed && mounted) {
debugPrint('Auto-starting navigation as requested by user (after retry)');
_pendingNavigationStart = false;
await _startNavigation();
}
} catch (e2) {
debugPrint('Camera animation retry failed: $e2');
debugPrint('Follow location retry failed: $e2');
}
}
}
@@ -560,63 +726,108 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
),
],
),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
padding: EdgeInsets.only(
left: 12,
right: 12,
top: 8,
// Add safe area padding plus extra margin to avoid rounded corners
bottom: bottomSafeArea > 0 ? bottomSafeArea + 8 : 16,
),
child: Row(
children: [
// Start button
Expanded(
child: _buildBottomActionButton(
label: _isNavigating ? 'Stop' : 'Start',
icon: _isNavigating ? Icons.stop : Icons.navigation,
onPressed: _isStartingNavigation || _isInitializing || (widget.selectedDelivery == null && !_isNavigating)
? null
: (_isNavigating ? _stopNavigation : _startNavigation),
isDanger: _isNavigating,
),
),
const SizedBox(width: 8),
// Photo button (disabled when no delivery selected or warehouse delivery)
Expanded(
child: _buildBottomActionButton(
label: 'Photo',
icon: Icons.camera_alt,
onPressed: widget.selectedDelivery != null && !widget.selectedDelivery!.isWarehouseDelivery
? () => widget.onAction?.call('photo')
: null,
),
),
const SizedBox(width: 8),
// Note button (only enabled if delivery has notes and not warehouse)
Expanded(
child: _buildBottomActionButton(
label: 'Note',
icon: Icons.note_add,
onPressed: _hasNotes() && widget.selectedDelivery != null && !widget.selectedDelivery!.isWarehouseDelivery
? () => widget.onAction?.call('note')
: null,
),
),
const SizedBox(width: 8),
// Completed button (disabled for warehouse delivery)
Expanded(
child: _buildBottomActionButton(
label: widget.selectedDelivery?.delivered == true ? 'Undo' : 'Completed',
icon: widget.selectedDelivery?.delivered == true ? Icons.undo : Icons.check_circle,
onPressed: widget.selectedDelivery != null && !widget.selectedDelivery!.isWarehouseDelivery
? () => widget.onAction?.call(
widget.selectedDelivery!.delivered ? 'uncomplete' : 'complete',
)
: null,
isPrimary: widget.selectedDelivery != null && !widget.selectedDelivery!.delivered && !widget.selectedDelivery!.isWarehouseDelivery,
),
),
],
child: Builder(
builder: (context) {
final isMobile = context.isMobile;
final showButtonLabels = !isMobile;
return Row(
children: [
// Start button
Expanded(
child: _buildBottomActionButton(
label: _isNavigating ? 'Stop' : 'Start',
icon: _isNavigating ? Icons.stop : Icons.navigation,
onPressed: _isStartingNavigation || _isInitializing || (!_isMapViewReady && !_isNavigating) || (widget.selectedDelivery == null && !_isNavigating)
? null
: (_isNavigating ? _stopNavigation : _startNavigation),
isDanger: _isNavigating,
showLabel: showButtonLabels,
),
),
const SizedBox(width: 8),
// Photo button (disabled when no delivery selected or warehouse delivery)
Expanded(
child: _buildBottomActionButton(
label: 'Photo',
icon: Icons.camera_alt,
onPressed: widget.selectedDelivery != null && !widget.selectedDelivery!.isWarehouseDelivery
? () => widget.onAction?.call('photo')
: null,
showLabel: showButtonLabels,
),
),
const SizedBox(width: 8),
// Note button (only enabled if delivery has notes and not warehouse)
Expanded(
child: _buildBottomActionButton(
label: 'Note',
icon: Icons.note_add,
onPressed: _hasNotes() && widget.selectedDelivery != null && !widget.selectedDelivery!.isWarehouseDelivery
? () => widget.onAction?.call('note')
: null,
showLabel: showButtonLabels,
),
),
const SizedBox(width: 8),
// Completed button (disabled for warehouse delivery)
Expanded(
child: _buildBottomActionButton(
label: widget.selectedDelivery?.delivered == true ? 'Undo' : 'Completed',
icon: widget.selectedDelivery?.delivered == true ? Icons.undo : Icons.check_circle,
onPressed: widget.selectedDelivery != null && !widget.selectedDelivery!.isWarehouseDelivery
? () => widget.onAction?.call(
widget.selectedDelivery!.delivered ? 'uncomplete' : 'complete',
)
: null,
isPrimary: widget.selectedDelivery != null && !widget.selectedDelivery!.delivered && !widget.selectedDelivery!.isWarehouseDelivery,
showLabel: showButtonLabels,
),
),
],
);
},
),
),
),
// Audio guidance toggle button - positioned below green navigation card
Positioned(
top: topPadding + 48,
right: 16,
child: Material(
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(28),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
icon: Icon(
_isAudioGuidanceEnabled ? Icons.volume_up : Icons.volume_off,
color: _isAudioGuidanceEnabled
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
),
onPressed: _toggleAudioGuidance,
tooltip: _isAudioGuidanceEnabled ? 'Mute navigation' : 'Unmute navigation',
),
),
),
),
// Loading overlay during navigation initialization and start
if (_isStartingNavigation || _isInitializing)
Positioned.fill(
@@ -684,6 +895,7 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
required VoidCallback? onPressed,
bool isPrimary = false,
bool isDanger = false,
bool showLabel = true,
}) {
Color backgroundColor;
Color textColor = Colors.white;
@@ -709,9 +921,9 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
onTap: onPressed,
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 10,
padding: EdgeInsets.symmetric(
horizontal: showLabel ? 8 : 12,
vertical: showLabel ? 10 : 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
@@ -720,17 +932,19 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
Icon(
icon,
color: textColor,
size: 18,
size: showLabel ? 18 : 20,
),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.w600,
fontSize: 14,
if (showLabel) ...[
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
],
],
),
),
+16 -16
View File
@@ -125,7 +125,7 @@ class _DeliveryListItemState extends State<DeliveryListItem>
borderRadius: BorderRadius.circular(10),
border: widget.isSelected
? Border.all(
color: Colors.white,
color: Theme.of(context).colorScheme.surface,
width: 3,
)
: null,
@@ -134,7 +134,7 @@ class _DeliveryListItemState extends State<DeliveryListItem>
BoxShadow(
color: widget.isSelected
? statusColor.withValues(alpha: 0.5)
: Colors.black.withValues(
: Theme.of(context).colorScheme.scrim.withValues(
alpha: isDark ? 0.3 : 0.15,
),
blurRadius: widget.isSelected ? 12 : 8,
@@ -146,15 +146,15 @@ class _DeliveryListItemState extends State<DeliveryListItem>
),
child: Center(
child: widget.delivery.isWarehouseDelivery
? const Icon(
? Icon(
Icons.warehouse,
color: Colors.white,
color: Theme.of(context).colorScheme.onPrimary,
size: 32,
)
: Text(
'${widget.delivery.deliveryIndex + 1}',
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 26,
fontWeight: FontWeight.w700,
),
@@ -178,10 +178,10 @@ class _DeliveryListItemState extends State<DeliveryListItem>
),
child: Transform.rotate(
angle: 4.71239, // 270 degrees in radians (3*pi/2)
child: const Icon(
child: Icon(
Icons.note,
size: 12,
color: Colors.white,
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
@@ -232,7 +232,7 @@ class _DeliveryListItemState extends State<DeliveryListItem>
boxShadow: (_isHovered || widget.isSelected) && !widget.delivery.delivered
? [
BoxShadow(
color: Colors.black.withValues(
color: Theme.of(context).colorScheme.scrim.withValues(
alpha: isDark ? 0.3 : 0.08,
),
blurRadius: 8,
@@ -261,15 +261,15 @@ class _DeliveryListItemState extends State<DeliveryListItem>
),
child: Center(
child: widget.delivery.isWarehouseDelivery
? const Icon(
? Icon(
Icons.warehouse,
color: Colors.white,
color: Theme.of(context).colorScheme.onPrimary,
size: 24,
)
: Text(
'${widget.delivery.deliveryIndex + 1}',
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
@@ -343,7 +343,7 @@ class _DeliveryListItemState extends State<DeliveryListItem>
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
color: Theme.of(context).colorScheme.scrim.withValues(alpha: 0.2),
blurRadius: 4,
offset: const Offset(0, 2),
),
@@ -351,10 +351,10 @@ class _DeliveryListItemState extends State<DeliveryListItem>
),
child: Transform.rotate(
angle: 4.71239, // 270 degrees in radians (3*pi/2)
child: const Icon(
child: Icon(
Icons.note,
size: 14,
color: Colors.white,
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
+5 -5
View File
@@ -50,21 +50,21 @@ class _GlassmorphicRouteCardState extends State<GlassmorphicRouteCard>
// Red to orange (0-30%)
return Color.lerp(
SvrntyColors.crimsonRed,
const Color(0xFFFF9800),
SvrntyColors.progressLow,
(progress / 0.3),
)!;
} else if (progress < 0.7) {
// Orange to yellow (30-70%)
return Color.lerp(
const Color(0xFFFF9800),
const Color(0xFFFFC107),
SvrntyColors.progressLow,
SvrntyColors.progressMedium,
((progress - 0.3) / 0.4),
)!;
} else {
// Yellow to green (70-100%)
return Color.lerp(
const Color(0xFFFFC107),
const Color(0xFF4CAF50),
SvrntyColors.progressMedium,
SvrntyColors.progressHigh,
((progress - 0.7) / 0.3),
)!;
}
+13 -6
View File
@@ -193,8 +193,8 @@ class _MobileMapWithOverlayState extends ConsumerState<MobileMapWithOverlay>
scrollController: _listScrollController,
onDeliverySelected: (delivery) {
widget.onDeliverySelected(delivery);
// Optionally close the overlay after selection
// _toggleList();
// Auto-close the overlay after selection for better mobile UX
_toggleList();
},
onItemAction: (delivery, action) {
widget.onDeliveryAction(delivery, action);
@@ -213,14 +213,21 @@ class _MobileMapWithOverlayState extends ConsumerState<MobileMapWithOverlay>
// Floating toggle button (FAB) - only show when list is closed
if (!isListOpen)
Positioned(
bottom: 80, // Above bottom action buttons
bottom: 110, // Slightly lowered for better positioning
right: 16,
child: FloatingActionButton.extended(
heroTag: 'mobile_deliveries_toggle_fab',
onPressed: _toggleList,
icon: const Icon(Icons.list),
label: Text('$_completedCount/$_totalCount'),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer,
label: Text(
'$_completedCount/$_totalCount',
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 15,
),
),
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
elevation: 4,
),
),