diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 775ee20..ef72359 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -3,6 +3,9 @@
android:label="planb_logistic"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
+
deliveries;
final Delivery? selectedDelivery;
final ValueChanged? 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 {
}
}
+ Future _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 _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 _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 {
),
),
// 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 {
),
),
),
+ // 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,
diff --git a/lib/components/delivery_list_item.dart b/lib/components/delivery_list_item.dart
index c907019..7159cee 100644
--- a/lib/components/delivery_list_item.dart
+++ b/lib/components/delivery_list_item.dart
@@ -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
),
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(
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index 5493696..49ff46b 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -331,6 +331,114 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'{completed}/{total} completed'**
String completedDeliveries(int completed, int total);
+
+ /// No description provided for @navigationTcTitle.
+ ///
+ /// In en, this message translates to:
+ /// **'Navigation Service'**
+ String get navigationTcTitle;
+
+ /// No description provided for @navigationTcDescription.
+ ///
+ /// In en, this message translates to:
+ /// **'This app uses Google Navigation to provide turn-by-turn navigation for deliveries.'**
+ String get navigationTcDescription;
+
+ /// No description provided for @navigationTcAttribution.
+ ///
+ /// In en, this message translates to:
+ /// **'Attribution: Maps and navigation services provided by Google Maps.'**
+ String get navigationTcAttribution;
+
+ /// No description provided for @navigationTcTerms.
+ ///
+ /// In en, this message translates to:
+ /// **'By accepting, you agree to Google\'s Terms of Service and Privacy Policy for Navigation services.'**
+ String get navigationTcTerms;
+
+ /// No description provided for @accept.
+ ///
+ /// In en, this message translates to:
+ /// **'Accept'**
+ String get accept;
+
+ /// No description provided for @decline.
+ ///
+ /// In en, this message translates to:
+ /// **'Decline'**
+ String get decline;
+
+ /// No description provided for @locationPermissionRequired.
+ ///
+ /// In en, this message translates to:
+ /// **'Location Permission'**
+ String get locationPermissionRequired;
+
+ /// No description provided for @locationPermissionMessage.
+ ///
+ /// In en, this message translates to:
+ /// **'This app requires location permission to navigate to deliveries.'**
+ String get locationPermissionMessage;
+
+ /// No description provided for @locationPermissionDenied.
+ ///
+ /// In en, this message translates to:
+ /// **'Location permission denied. Navigation cannot proceed.'**
+ String get locationPermissionDenied;
+
+ /// No description provided for @permissionPermanentlyDenied.
+ ///
+ /// In en, this message translates to:
+ /// **'Permission Required'**
+ String get permissionPermanentlyDenied;
+
+ /// No description provided for @openSettingsMessage.
+ ///
+ /// In en, this message translates to:
+ /// **'Location permission is permanently denied. Please enable it in app settings.'**
+ String get openSettingsMessage;
+
+ /// No description provided for @openSettings.
+ ///
+ /// In en, this message translates to:
+ /// **'Open Settings'**
+ String get openSettings;
+
+ /// No description provided for @cancel.
+ ///
+ /// In en, this message translates to:
+ /// **'Cancel'**
+ String get cancel;
+
+ /// No description provided for @ok.
+ ///
+ /// In en, this message translates to:
+ /// **'OK'**
+ String get ok;
+
+ /// No description provided for @requestPermission.
+ ///
+ /// In en, this message translates to:
+ /// **'Request Permission'**
+ String get requestPermission;
+
+ /// No description provided for @navigationArrived.
+ ///
+ /// In en, this message translates to:
+ /// **'You have arrived at the destination'**
+ String get navigationArrived;
+
+ /// No description provided for @navigatingTo.
+ ///
+ /// In en, this message translates to:
+ /// **'Navigating to'**
+ String get navigatingTo;
+
+ /// No description provided for @initializingNavigation.
+ ///
+ /// In en, this message translates to:
+ /// **'Initializing navigation...'**
+ String get initializingNavigation;
}
class _AppLocalizationsDelegate
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 5ec892f..5e9a4fe 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -134,4 +134,64 @@ class AppLocalizationsEn extends AppLocalizations {
String completedDeliveries(int completed, int total) {
return '$completed/$total completed';
}
+
+ @override
+ String get navigationTcTitle => 'Navigation Service';
+
+ @override
+ String get navigationTcDescription =>
+ 'This app uses Google Navigation to provide turn-by-turn navigation for deliveries.';
+
+ @override
+ String get navigationTcAttribution =>
+ 'Attribution: Maps and navigation services provided by Google Maps.';
+
+ @override
+ String get navigationTcTerms =>
+ 'By accepting, you agree to Google\'s Terms of Service and Privacy Policy for Navigation services.';
+
+ @override
+ String get accept => 'Accept';
+
+ @override
+ String get decline => 'Decline';
+
+ @override
+ String get locationPermissionRequired => 'Location Permission';
+
+ @override
+ String get locationPermissionMessage =>
+ 'This app requires location permission to navigate to deliveries.';
+
+ @override
+ String get locationPermissionDenied =>
+ 'Location permission denied. Navigation cannot proceed.';
+
+ @override
+ String get permissionPermanentlyDenied => 'Permission Required';
+
+ @override
+ String get openSettingsMessage =>
+ 'Location permission is permanently denied. Please enable it in app settings.';
+
+ @override
+ String get openSettings => 'Open Settings';
+
+ @override
+ String get cancel => 'Cancel';
+
+ @override
+ String get ok => 'OK';
+
+ @override
+ String get requestPermission => 'Request Permission';
+
+ @override
+ String get navigationArrived => 'You have arrived at the destination';
+
+ @override
+ String get navigatingTo => 'Navigating to';
+
+ @override
+ String get initializingNavigation => 'Initializing navigation...';
}
diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart
index 5d5b701..1aa9662 100644
--- a/lib/l10n/app_localizations_fr.dart
+++ b/lib/l10n/app_localizations_fr.dart
@@ -134,4 +134,64 @@ class AppLocalizationsFr extends AppLocalizations {
String completedDeliveries(int completed, int total) {
return '$completed/$total livrs';
}
+
+ @override
+ String get navigationTcTitle => 'Service de Navigation';
+
+ @override
+ String get navigationTcDescription =>
+ 'Cette application utilise Google Navigation pour fournir une navigation virage par virage pour les livraisons.';
+
+ @override
+ String get navigationTcAttribution =>
+ 'Attribution: Services de cartes et de navigation fournis par Google Maps.';
+
+ @override
+ String get navigationTcTerms =>
+ 'En acceptant, vous acceptez les conditions d\'utilisation et la politique de confidentialit de Google pour les services de navigation.';
+
+ @override
+ String get accept => 'Accepter';
+
+ @override
+ String get decline => 'Refuser';
+
+ @override
+ String get locationPermissionRequired => 'Permission de localisation';
+
+ @override
+ String get locationPermissionMessage =>
+ 'Cette application ncessite la permission de localisation pour naviguer vers les livraisons.';
+
+ @override
+ String get locationPermissionDenied =>
+ 'Permission de localisation refuse. La navigation ne peut pas continuer.';
+
+ @override
+ String get permissionPermanentlyDenied => 'Permission requise';
+
+ @override
+ String get openSettingsMessage =>
+ 'La permission de localisation est dfinitivement refuse. Veuillez l\'activer dans les paramtres de l\'application.';
+
+ @override
+ String get openSettings => 'Ouvrir les paramtres';
+
+ @override
+ String get cancel => 'Annuler';
+
+ @override
+ String get ok => 'OK';
+
+ @override
+ String get requestPermission => 'Demander la permission';
+
+ @override
+ String get navigationArrived => 'Vous tes arriv la destination';
+
+ @override
+ String get navigatingTo => 'Navigation vers';
+
+ @override
+ String get initializingNavigation => 'Initialisation de la navigation...';
}
diff --git a/lib/pages/deliveries_page.dart b/lib/pages/deliveries_page.dart
index 77cb380..bc6c019 100644
--- a/lib/pages/deliveries_page.dart
+++ b/lib/pages/deliveries_page.dart
@@ -11,8 +11,6 @@ import '../utils/breakpoints.dart';
import '../components/map_sidebar_layout.dart';
import '../components/dark_mode_map.dart';
import '../components/delivery_list_item.dart';
-import '../components/collapsible_routes_sidebar.dart'
- show CollapsibleRoutesSidebar;
class DeliveriesPage extends ConsumerStatefulWidget {
final int routeFragmentId;
@@ -29,22 +27,38 @@ class DeliveriesPage extends ConsumerStatefulWidget {
}
class _DeliveriesPageState extends ConsumerState {
- late PageController _pageController;
- int _currentSegment = 0;
+ late ScrollController _listScrollController;
Delivery? _selectedDelivery;
+ int? _lastRouteFragmentId;
@override
void initState() {
super.initState();
- _pageController = PageController();
+ _listScrollController = ScrollController();
}
@override
void dispose() {
- _pageController.dispose();
+ _listScrollController.dispose();
super.dispose();
}
+ Future _autoScrollToFirstPending(List deliveries) async {
+ final firstPendingIndex = deliveries.indexWhere((d) => !d.delivered && !d.isSkipped);
+
+ if (_listScrollController.hasClients && firstPendingIndex != -1) {
+ await Future.delayed(const Duration(milliseconds: 200));
+ // Scroll to position first pending delivery at top of list
+ // Each item is approximately 70 pixels tall
+ final scrollOffset = firstPendingIndex * 70.0;
+ _listScrollController.animateTo(
+ scrollOffset.clamp(0, _listScrollController.position.maxScrollExtent),
+ duration: const Duration(milliseconds: 500),
+ curve: Curves.easeInOut,
+ );
+ }
+ }
+
@override
Widget build(BuildContext context) {
final deliveriesData = ref.watch(deliveriesProvider(widget.routeFragmentId));
@@ -58,6 +72,14 @@ class _DeliveriesPageState extends ConsumerState {
),
body: deliveriesData.when(
data: (deliveries) {
+ // Auto-scroll to first pending delivery when page loads or route changes
+ if (_lastRouteFragmentId != widget.routeFragmentId) {
+ _lastRouteFragmentId = widget.routeFragmentId;
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ _autoScrollToFirstPending(deliveries);
+ });
+ }
+
final todoDeliveries = deliveries
.where((d) => !d.delivered && !d.isSkipped)
.toList();
@@ -76,27 +98,7 @@ class _DeliveriesPageState extends ConsumerState {
currentRoute = routes.isNotEmpty ? routes.first : null;
}
- return Row(
- children: [
- if (context.isDesktop && routes.isNotEmpty)
- CollapsibleRoutesSidebar(
- routes: routes,
- selectedRoute: currentRoute,
- onRouteSelected: (route) {
- if (route.id != widget.routeFragmentId) {
- Navigator.of(context).pushReplacement(
- MaterialPageRoute(
- builder: (context) => DeliveriesPage(
- routeFragmentId: route.id,
- routeName: route.name,
- ),
- ),
- );
- }
- },
- ),
- Expanded(
- child: MapSidebarLayout(
+ return MapSidebarLayout(
mapWidget: DarkModeMapComponent(
deliveries: deliveries,
selectedDelivery: _selectedDelivery,
@@ -105,75 +107,25 @@ class _DeliveriesPageState extends ConsumerState {
_selectedDelivery = delivery;
});
},
+ onAction: (action) => _selectedDelivery != null
+ ? _handleDeliveryAction(context, _selectedDelivery!, action, token)
+ : null,
),
- sidebarWidget: Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: SegmentedButton(
- segments: const [
- ButtonSegment(
- value: 0,
- label: Text('To Do'),
- ),
- ButtonSegment(
- value: 1,
- label: Text('Delivered'),
- ),
- ],
- selected: {_currentSegment},
- onSelectionChanged: (Set newSelection) {
- setState(() {
- _currentSegment = newSelection.first;
- _pageController.animateToPage(
- _currentSegment,
- duration: const Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- );
- });
- },
- ),
- ),
- Expanded(
- child: PageView(
- controller: _pageController,
- onPageChanged: (index) {
- setState(() {
- _currentSegment = index;
- });
- },
- children: [
- DeliveryListView(
- deliveries: todoDeliveries,
- selectedDelivery: _selectedDelivery,
- onDeliverySelected: (delivery) {
- setState(() {
- _selectedDelivery = delivery;
- });
- },
- onAction: (delivery, action) =>
- _handleDeliveryAction(context, delivery, action, token),
- ),
- DeliveryListView(
- deliveries: completedDeliveries,
- selectedDelivery: _selectedDelivery,
- onDeliverySelected: (delivery) {
- setState(() {
- _selectedDelivery = delivery;
- });
- },
- onAction: (delivery, action) =>
- _handleDeliveryAction(context, delivery, action, token),
- ),
- ],
- ),
- ),
- ],
+ sidebarWidget: UnifiedDeliveryListView(
+ deliveries: deliveries,
+ selectedDelivery: _selectedDelivery,
+ scrollController: _listScrollController,
+ onDeliverySelected: (delivery) {
+ setState(() {
+ _selectedDelivery = delivery;
+ });
+ },
+ onItemAction: (delivery, action) {
+ _handleDeliveryAction(context, delivery, action, token);
+ _autoScrollToFirstPending(deliveries);
+ },
),
- ),
- ),
- ],
- );
+ );
},
loading: () => const Center(
child: CircularProgressIndicator(),
@@ -187,70 +139,23 @@ class _DeliveriesPageState extends ConsumerState {
_selectedDelivery = delivery;
});
},
+ onAction: (action) => _selectedDelivery != null
+ ? _handleDeliveryAction(context, _selectedDelivery!, action, token)
+ : null,
),
- sidebarWidget: Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: SegmentedButton(
- segments: const [
- ButtonSegment(
- value: 0,
- label: Text('To Do'),
- ),
- ButtonSegment(
- value: 1,
- label: Text('Delivered'),
- ),
- ],
- selected: {_currentSegment},
- onSelectionChanged: (Set newSelection) {
- setState(() {
- _currentSegment = newSelection.first;
- _pageController.animateToPage(
- _currentSegment,
- duration: const Duration(milliseconds: 300),
- curve: Curves.easeInOut,
- );
- });
- },
- ),
- ),
- Expanded(
- child: PageView(
- controller: _pageController,
- onPageChanged: (index) {
- setState(() {
- _currentSegment = index;
- });
- },
- children: [
- DeliveryListView(
- deliveries: todoDeliveries,
- selectedDelivery: _selectedDelivery,
- onDeliverySelected: (delivery) {
- setState(() {
- _selectedDelivery = delivery;
- });
- },
- onAction: (delivery, action) =>
- _handleDeliveryAction(context, delivery, action, token),
- ),
- DeliveryListView(
- deliveries: completedDeliveries,
- selectedDelivery: _selectedDelivery,
- onDeliverySelected: (delivery) {
- setState(() {
- _selectedDelivery = delivery;
- });
- },
- onAction: (delivery, action) =>
- _handleDeliveryAction(context, delivery, action, token),
- ),
- ],
- ),
- ),
- ],
+ sidebarWidget: UnifiedDeliveryListView(
+ deliveries: deliveries,
+ selectedDelivery: _selectedDelivery,
+ scrollController: _listScrollController,
+ onDeliverySelected: (delivery) {
+ setState(() {
+ _selectedDelivery = delivery;
+ });
+ },
+ onItemAction: (delivery, action) {
+ _handleDeliveryAction(context, delivery, action, token);
+ _autoScrollToFirstPending(deliveries);
+ },
),
),
);
@@ -291,7 +196,6 @@ class _DeliveriesPageState extends ConsumerState {
endpoint: 'completeDelivery',
command: CompleteDeliveryCommand(
deliveryId: delivery.id,
- deliveredAt: DateTime.now().toIso8601String(),
),
);
result.when(
@@ -351,18 +255,20 @@ class _DeliveriesPageState extends ConsumerState {
}
}
-class DeliveryListView extends StatelessWidget {
+class UnifiedDeliveryListView extends StatelessWidget {
final List deliveries;
final Delivery? selectedDelivery;
+ final ScrollController scrollController;
final ValueChanged onDeliverySelected;
- final Function(Delivery, String) onAction;
+ final Function(Delivery, String) onItemAction;
- const DeliveryListView({
+ const UnifiedDeliveryListView({
super.key,
required this.deliveries,
this.selectedDelivery,
+ required this.scrollController,
required this.onDeliverySelected,
- required this.onAction,
+ required this.onItemAction,
});
@override
@@ -378,6 +284,7 @@ class DeliveryListView extends StatelessWidget {
// Trigger refresh via provider
},
child: ListView.builder(
+ controller: scrollController,
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: deliveries.length,
itemBuilder: (context, index) {
@@ -386,7 +293,8 @@ class DeliveryListView extends StatelessWidget {
delivery: delivery,
isSelected: selectedDelivery?.id == delivery.id,
onTap: () => onDeliverySelected(delivery),
- onCall: () => onAction(delivery, 'call'),
+ onCall: () => onItemAction(delivery, 'call'),
+ onAction: (action) => onItemAction(delivery, action),
animationIndex: index,
);
},
diff --git a/lib/pages/navigation_page.dart b/lib/pages/navigation_page.dart
index 446a451..a8f81ec 100644
--- a/lib/pages/navigation_page.dart
+++ b/lib/pages/navigation_page.dart
@@ -52,11 +52,8 @@ class _NavigationPageState extends ConsumerState {
setState(() {
_hasLocationPermission = true;
+ _isNavigationInitialized = true;
});
-
- if (mounted) {
- _initializeNavigationSession();
- }
} catch (e) {
if (mounted) {
_showErrorDialog('Initialization error: ${e.toString()}');
@@ -67,19 +64,10 @@ class _NavigationPageState extends ConsumerState {
Future _initializeNavigationSession() async {
try {
await GoogleMapsNavigationViewController.initializeNavigationSession();
-
- if (mounted) {
- setState(() {
- _isNavigationInitialized = true;
- });
-
- // Set destination after session is initialized
- await _setDestination();
- }
} catch (e) {
- if (mounted) {
- _showErrorDialog('Failed to initialize navigation: ${e.toString()}');
- }
+ debugPrint('Navigation session initialization error: $e');
+ // Don't show error dialog, just log it
+ // The session might already be initialized
}
}
@@ -263,8 +251,11 @@ class _NavigationPageState extends ConsumerState {
),
body: _hasLocationPermission && _isNavigationInitialized
? GoogleMapsNavigationView(
- onViewCreated: (controller) {
+ onViewCreated: (controller) async {
_navigationViewController = controller;
+ await _initializeNavigationSession();
+ await Future.delayed(const Duration(milliseconds: 500));
+ await _setDestination();
},
initialCameraPosition: CameraPosition(
target: LatLng(
diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj
index 3260472..79cdf2e 100644
--- a/macos/Runner.xcodeproj/project.pbxproj
+++ b/macos/Runner.xcodeproj/project.pbxproj
@@ -588,7 +588,7 @@
338D0CEB231458BD00FA5F75 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
- CODE_SIGN_STYLE = Manual;
+ CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
@@ -708,7 +708,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
- DEVELOPMENT_TEAM = LD76P8L42W;
+ DEVELOPMENT_TEAM = 833P6TSX55;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -744,7 +744,7 @@
33CC111C2044C6BA0003C045 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
- CODE_SIGN_STYLE = Manual;
+ CODE_SIGN_STYLE = Automatic;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig
index b11c3b9..5b5b89b 100644
--- a/macos/Runner/Configs/AppInfo.xcconfig
+++ b/macos/Runner/Configs/AppInfo.xcconfig
@@ -8,7 +8,7 @@
PRODUCT_NAME = planb_logistic
// The application's bundle identifier
-PRODUCT_BUNDLE_IDENTIFIER = com.goutezplanb.planbLogistic
+PRODUCT_BUNDLE_IDENTIFIER = com.local.planbLogistic
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2025 com.goutezplanb. All rights reserved.