checkpoint

This commit is contained in:
2025-11-26 15:59:20 -05:00
parent d46ac9dc14
commit ef5c0c1a95
14 changed files with 5281 additions and 260 deletions
+11 -6
View File
@@ -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(
+140 -30
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/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),
+189 -93
View File
@@ -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,
),
),
),
),
],
),
),
+3 -3
View File
@@ -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