Improve map navigation UI and add collapsible deliveries sidebar
Changes: - Remove zoom controls (+ and - buttons) from map navigation - Remove duplicate delivery info from top of delivery page - Add collapsible sidebar to map layout with smooth animations - Sidebar collapses to 64px width when not expanded - Expanded sidebar shows 280px width with delivery list - Added "Deliveries" header with animated chevron toggle button - Updated dark mode styling for collapsible header - Frees up more space for map navigation window Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
57b81d1e95
commit
dc9282e2c7
@ -293,36 +293,6 @@ 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) return;
|
||||
try {
|
||||
@ -340,9 +310,8 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
// Driver's current location (defaults to Montreal if not available)
|
||||
final initialPosition = const LatLng(latitude: 45.5017, longitude: -73.5673);
|
||||
|
||||
// Calculate dynamic padding for top info panel and bottom button bar
|
||||
// Increased to accommodate navigation widget info and action buttons
|
||||
final topPadding = widget.selectedDelivery != null ? 110.0 : 0.0;
|
||||
// Calculate dynamic padding for bottom button bar
|
||||
final topPadding = 0.0;
|
||||
final bottomPadding = widget.selectedDelivery != null ? 110.0 : 0.0;
|
||||
|
||||
return Stack(
|
||||
@ -369,27 +338,6 @@ 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 above bottom button bar)
|
||||
Positioned(
|
||||
bottom: 120,
|
||||
@ -415,87 +363,6 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
],
|
||||
),
|
||||
),
|
||||
// Selected delivery info panel (dark themed)
|
||||
if (widget.selectedDelivery != null)
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
widget.selectedDelivery!.name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.selectedDelivery!.deliveryAddress
|
||||
?.formattedAddress ??
|
||||
'No address',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// Status indicator
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.selectedDelivery!.delivered
|
||||
? SvrntyColors.statusCompleted
|
||||
: SvrntyColors.statusPending,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
widget.selectedDelivery!.delivered
|
||||
? 'Delivered'
|
||||
: 'Pending',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.copyWith(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Bottom action button bar
|
||||
if (widget.selectedDelivery != null)
|
||||
Positioned(
|
||||
@ -633,40 +500,6 @@ class _DarkModeMapComponentState extends State<DarkModeMapComponent> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconButton({
|
||||
required IconData icon,
|
||||
required VoidCallback onPressed,
|
||||
}) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 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,
|
||||
|
||||
@ -195,29 +195,6 @@ class _DeliveryMapState extends State<DeliveryMap> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: Column(
|
||||
children: [
|
||||
FloatingActionButton.small(
|
||||
heroTag: 'zoom_in',
|
||||
onPressed: () {
|
||||
_navigationController?.animateCamera(CameraUpdate.zoomIn());
|
||||
},
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
FloatingActionButton.small(
|
||||
heroTag: 'zoom_out',
|
||||
onPressed: () {
|
||||
_navigationController?.animateCamera(CameraUpdate.zoomOut());
|
||||
},
|
||||
child: const Icon(Icons.remove),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../utils/breakpoints.dart';
|
||||
import '../theme/spacing_system.dart';
|
||||
import '../theme/size_system.dart';
|
||||
import '../theme/animation_system.dart';
|
||||
import '../theme/color_system.dart';
|
||||
|
||||
class MapSidebarLayout extends StatelessWidget {
|
||||
class MapSidebarLayout extends StatefulWidget {
|
||||
final Widget mapWidget;
|
||||
final Widget sidebarWidget;
|
||||
final double mapRatio;
|
||||
@ -14,22 +18,118 @@ class MapSidebarLayout extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMobile = MediaQuery.of(context).size.width < Breakpoints.tablet;
|
||||
|
||||
if (isMobile) {
|
||||
return sidebarWidget;
|
||||
State<MapSidebarLayout> createState() => _MapSidebarLayoutState();
|
||||
}
|
||||
|
||||
class _MapSidebarLayoutState extends State<MapSidebarLayout>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
bool _isExpanded = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
if (_isExpanded) {
|
||||
_animationController.forward();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleSidebar() {
|
||||
setState(() {
|
||||
_isExpanded = !_isExpanded;
|
||||
});
|
||||
if (_isExpanded) {
|
||||
_animationController.forward();
|
||||
} else {
|
||||
_animationController.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMobile = MediaQuery.of(context).size.width < Breakpoints.tablet;
|
||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
if (isMobile) {
|
||||
return widget.sidebarWidget;
|
||||
}
|
||||
|
||||
// Desktop: Show map with collapsible sidebar
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: (mapRatio * 100).toInt(),
|
||||
child: mapWidget,
|
||||
flex: (widget.mapRatio * 100).toInt(),
|
||||
child: widget.mapWidget,
|
||||
),
|
||||
// Collapsible sidebar with toggle button
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
width: _isExpanded ? 280 : 64,
|
||||
color: isDarkMode ? SvrntyColors.almostBlack : Colors.white,
|
||||
child: Column(
|
||||
children: [
|
||||
// Header with toggle button
|
||||
Container(
|
||||
height: kToolbarHeight,
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.xs),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: isDarkMode
|
||||
? SvrntyColors.darkSlate
|
||||
: SvrntyColors.slateGray.withValues(alpha: 0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: _isExpanded
|
||||
? MainAxisAlignment.spaceBetween
|
||||
: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (_isExpanded)
|
||||
Expanded(
|
||||
flex: ((1 - mapRatio) * 100).toInt(),
|
||||
child: sidebarWidget,
|
||||
child: Text(
|
||||
'Deliveries',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: AppSizes.buttonHeightMd,
|
||||
height: AppSizes.buttonHeightMd,
|
||||
child: IconButton(
|
||||
icon: AnimatedRotation(
|
||||
turns: _isExpanded ? 0 : -0.5,
|
||||
duration: Duration(
|
||||
milliseconds:
|
||||
AppAnimations.durationFast.inMilliseconds,
|
||||
),
|
||||
child: const Icon(Icons.chevron_right),
|
||||
),
|
||||
onPressed: _toggleSidebar,
|
||||
iconSize: AppSizes.iconMd,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Sidebar content
|
||||
Expanded(
|
||||
child: _isExpanded ? widget.sidebarWidget : const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user