checkpoint
This commit is contained in:
@@ -119,7 +119,9 @@ class _CollapsibleRoutesSidebarState extends ConsumerState<CollapsibleRoutesSide
|
||||
}
|
||||
|
||||
// On tablet/desktop, show full sidebar with toggle (expanded: 300px, collapsed: 80px for badge)
|
||||
return Container(
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
width: isExpanded ? 300 : 80,
|
||||
color: isDarkMode ? SvrntyColors.almostBlack : Colors.white,
|
||||
child: Column(
|
||||
@@ -141,12 +143,15 @@ class _CollapsibleRoutesSidebarState extends ConsumerState<CollapsibleRoutesSide
|
||||
children: [
|
||||
if (isExpanded)
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Routes',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: AppSpacing.md),
|
||||
child: Text(
|
||||
'Routes',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
|
||||
@@ -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/toast_helper.dart';
|
||||
|
||||
/// Enhanced dark-mode aware map component with custom styling
|
||||
class DarkModeMapComponent extends StatefulWidget {
|
||||
@@ -49,6 +50,21 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(DarkModeMapComponent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.selectedDelivery != widget.selectedDelivery) {
|
||||
_updateDestination();
|
||||
|
||||
// If navigation was active, restart navigation to new delivery
|
||||
if (_isNavigating &&
|
||||
widget.selectedDelivery != null &&
|
||||
widget.selectedDelivery!.deliveryAddress != null) {
|
||||
_restartNavigationToNewDelivery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
@@ -64,6 +80,21 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
_lastBrightness = currentBrightness;
|
||||
}
|
||||
|
||||
Future<void> _restartNavigationToNewDelivery() async {
|
||||
try {
|
||||
// Stop current navigation
|
||||
await _stopNavigation();
|
||||
// Wait a bit for stop to complete
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
// Start navigation to new delivery
|
||||
if (mounted && !_isDisposed) {
|
||||
await _startNavigation();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Restart navigation error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initializeNavigation() async {
|
||||
if (_isInitializing || _isSessionInitialized) return;
|
||||
|
||||
@@ -96,12 +127,7 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
_isInitializing = false;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Navigation initialization failed: $errorMessage'),
|
||||
duration: const Duration(seconds: 5),
|
||||
),
|
||||
);
|
||||
ToastHelper.showError(context, 'Navigation initialization failed: $errorMessage');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,14 +144,6 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
return errorString;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(DarkModeMapComponent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.selectedDelivery != widget.selectedDelivery) {
|
||||
_updateDestination();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateDestination() {
|
||||
if (widget.selectedDelivery != null) {
|
||||
final address = widget.selectedDelivery!.deliveryAddress;
|
||||
@@ -149,8 +167,101 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
try {
|
||||
if (!mounted || _isDisposed) return;
|
||||
|
||||
// Always use default (light) map style
|
||||
await _navigationController!.setMapStyle(null);
|
||||
// Force dark mode map style using Google's standard dark theme
|
||||
const String darkMapStyle = '''
|
||||
[
|
||||
{
|
||||
"elementType": "geometry",
|
||||
"stylers": [{"color": "#242f3e"}]
|
||||
},
|
||||
{
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [{"color": "#242f3e"}]
|
||||
},
|
||||
{
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#746855"}]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative.locality",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#d59563"}]
|
||||
},
|
||||
{
|
||||
"featureType": "poi",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#d59563"}]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "geometry",
|
||||
"stylers": [{"color": "#263c3f"}]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#6b9a76"}]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "geometry",
|
||||
"stylers": [{"color": "#38414e"}]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [{"color": "#212a37"}]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#9ca5b3"}]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry",
|
||||
"stylers": [{"color": "#746855"}]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [{"color": "#1f2835"}]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#f3d19c"}]
|
||||
},
|
||||
{
|
||||
"featureType": "transit",
|
||||
"elementType": "geometry",
|
||||
"stylers": [{"color": "#2f3948"}]
|
||||
},
|
||||
{
|
||||
"featureType": "transit.station",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#d59563"}]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "geometry",
|
||||
"stylers": [{"color": "#17263c"}]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [{"color": "#515c6d"}]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [{"color": "#17263c"}]
|
||||
}
|
||||
]
|
||||
''';
|
||||
|
||||
await _navigationController!.setMapStyle(darkMapStyle);
|
||||
debugPrint('Dark mode map style applied');
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
debugPrint('Error applying map style: $e');
|
||||
@@ -188,12 +299,7 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
setState(() {
|
||||
_isStartingNavigation = false;
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Navigation initialization timeout'),
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
ToastHelper.showError(context, 'Navigation initialization timeout');
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -270,12 +376,7 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
_isStartingNavigation = false;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Navigation error: $errorMessage'),
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
ToastHelper.showError(context, 'Navigation error: $errorMessage', duration: const Duration(seconds: 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,6 +420,13 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
}
|
||||
}
|
||||
|
||||
bool _hasNotes() {
|
||||
if (widget.selectedDelivery == null) return false;
|
||||
return widget.selectedDelivery!.orders.any((order) =>
|
||||
order.note != null && order.note!.isNotEmpty
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Driver's current location (defaults to Montreal if not available)
|
||||
@@ -479,12 +587,14 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Note button
|
||||
// Note button (only enabled if delivery has notes)
|
||||
Expanded(
|
||||
child: _buildBottomActionButton(
|
||||
label: 'Note',
|
||||
icon: Icons.note_add,
|
||||
onPressed: () => widget.onAction?.call('note'),
|
||||
onPressed: _hasNotes()
|
||||
? () => widget.onAction?.call('note')
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
||||
@@ -74,12 +74,22 @@ class _DeliveryListItemState extends State<DeliveryListItem>
|
||||
}
|
||||
|
||||
Color _getStatusColor(Delivery delivery) {
|
||||
// If delivered, always show green (even if selected)
|
||||
if (delivery.delivered == true) return SvrntyColors.success;
|
||||
// If selected and not delivered, show yellow/warning color
|
||||
if (widget.isSelected) return SvrntyColors.warning;
|
||||
// If skipped, show grey
|
||||
if (delivery.isSkipped == true) return SvrntyColors.statusCancelled;
|
||||
if (delivery.delivered == true) return SvrntyColors.statusCompleted;
|
||||
// Default: in-transit or pending deliveries
|
||||
return SvrntyColors.statusInTransit;
|
||||
}
|
||||
|
||||
bool _hasNote() {
|
||||
return widget.delivery.orders.any((order) =>
|
||||
order.note != null && order.note!.isNotEmpty
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
@@ -102,34 +112,73 @@ class _DeliveryListItemState extends State<DeliveryListItem>
|
||||
vertical: 10,
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: (_isHovered || widget.isSelected)
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(
|
||||
alpha: isDark ? 0.3 : 0.15,
|
||||
),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${widget.delivery.deliveryIndex + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w700,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: widget.isSelected
|
||||
? Border.all(
|
||||
color: Colors.white,
|
||||
width: 3,
|
||||
)
|
||||
: null,
|
||||
boxShadow: (_isHovered || widget.isSelected)
|
||||
? [
|
||||
BoxShadow(
|
||||
color: widget.isSelected
|
||||
? statusColor.withValues(alpha: 0.5)
|
||||
: Colors.black.withValues(
|
||||
alpha: isDark ? 0.3 : 0.15,
|
||||
),
|
||||
blurRadius: widget.isSelected ? 12 : 8,
|
||||
offset: const Offset(0, 4),
|
||||
spreadRadius: widget.isSelected ? 2 : 0,
|
||||
),
|
||||
]
|
||||
: [],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${widget.delivery.deliveryIndex + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_hasNote())
|
||||
Positioned(
|
||||
top: -4,
|
||||
right: -4,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
child: Transform.rotate(
|
||||
angle: 4.71239, // 270 degrees in radians (3*pi/2)
|
||||
child: const Icon(
|
||||
Icons.note,
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -161,7 +210,17 @@ class _DeliveryListItemState extends State<DeliveryListItem>
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: widget.delivery.delivered
|
||||
? Colors.green.withValues(alpha: 0.15)
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
: widget.isSelected
|
||||
? statusColor.withValues(alpha: 0.15)
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
border: widget.isSelected
|
||||
? Border.all(
|
||||
color: widget.delivery.delivered
|
||||
? SvrntyColors.success
|
||||
: statusColor,
|
||||
width: 2,
|
||||
)
|
||||
: null,
|
||||
boxShadow: (_isHovered || widget.isSelected) && !widget.delivery.delivered
|
||||
? [
|
||||
BoxShadow(
|
||||
@@ -175,80 +234,117 @@ class _DeliveryListItemState extends State<DeliveryListItem>
|
||||
: [],
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||
child: Column(
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
// Main delivery info row
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
Column(
|
||||
children: [
|
||||
// Order number badge (left of status bar)
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${widget.delivery.deliveryIndex + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
// Main delivery info row
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Order number badge (left of status bar)
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'${widget.delivery.deliveryIndex + 1}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Left accent bar (vertical status bar)
|
||||
Container(
|
||||
width: 4,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// Delivery info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Customer Name
|
||||
Text(
|
||||
widget.delivery.name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
const SizedBox(width: 8),
|
||||
// Left accent bar (vertical status bar)
|
||||
Container(
|
||||
width: 4,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// Address
|
||||
Text(
|
||||
widget.delivery.deliveryAddress
|
||||
?.formattedAddress ??
|
||||
'No address',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
fontSize: 13,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// Delivery info
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Customer Name
|
||||
Text(
|
||||
widget.delivery.name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// Address
|
||||
Text(
|
||||
widget.delivery.deliveryAddress
|
||||
?.formattedAddress ??
|
||||
'No address',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
fontSize: 13,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_hasNote())
|
||||
Positioned(
|
||||
top: -8,
|
||||
right: -4,
|
||||
child: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Transform.rotate(
|
||||
angle: 4.71239, // 270 degrees in radians (3*pi/2)
|
||||
child: const Icon(
|
||||
Icons.note,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -70,9 +70,9 @@ class _RouteListItemState extends State<RouteListItem>
|
||||
}
|
||||
|
||||
Color _getStatusColor(DeliveryRoute route) {
|
||||
if (route.completed) return SvrntyColors.statusCompleted;
|
||||
if (route.deliveredCount > 0) return SvrntyColors.statusInTransit;
|
||||
return SvrntyColors.statusPending;
|
||||
if (route.completed) return SvrntyColors.statusCompleted; // Green
|
||||
if (route.deliveredCount > 0) return SvrntyColors.warning; // Yellow - started but not complete
|
||||
return SvrntyColors.statusCancelled; // Grey - not started
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user