ionic-planb-logistic-app-fl.../lib/components/collapsible_routes_sidebar.dart
Jean-Philippe Brule 5714fd8443 Implement UI/UX enhancements with collapsible routes sidebar and glassmorphic route cards
Adds new components (CollapsibleRoutesSidebar, GlassmorphicRouteCard) and
internationalization support. Updates deliveries and routes pages with improved
navigation and visual presentation using Material Design 3 principles.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 17:55:18 -05:00

201 lines
5.9 KiB
Dart

import 'package:flutter/material.dart';
import '../models/delivery_route.dart';
import '../theme/spacing_system.dart';
import '../theme/size_system.dart';
import '../theme/animation_system.dart';
import '../theme/color_system.dart';
import '../utils/breakpoints.dart';
import 'glassmorphic_route_card.dart';
class CollapsibleRoutesSidebar extends StatefulWidget {
final List<DeliveryRoute> routes;
final DeliveryRoute? selectedRoute;
final ValueChanged<DeliveryRoute> onRouteSelected;
const CollapsibleRoutesSidebar({
super.key,
required this.routes,
this.selectedRoute,
required this.onRouteSelected,
});
@override
State<CollapsibleRoutesSidebar> createState() =>
_CollapsibleRoutesSidebarState();
}
class _CollapsibleRoutesSidebarState extends State<CollapsibleRoutesSidebar>
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 = context.isMobile;
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
// On mobile, always show as collapsible
if (isMobile) {
return Container(
color: isDarkMode ? SvrntyColors.almostBlack : Colors.white,
child: Column(
children: [
// Header with toggle button
Container(
padding: EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: isDarkMode ? SvrntyColors.darkSlate : SvrntyColors.slateGray.withValues(alpha: 0.2),
width: 1,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (_isExpanded)
Text(
'Routes',
style: Theme.of(context).textTheme.titleMedium,
),
IconButton(
icon: AnimatedRotation(
turns: _isExpanded ? 0 : -0.25,
duration: Duration(
milliseconds: AppAnimations.durationFast.inMilliseconds,
),
child: const Icon(Icons.chevron_right),
),
onPressed: _toggleSidebar,
iconSize: AppSizes.iconMd,
),
],
),
),
// Collapsible content
if (_isExpanded)
Expanded(
child: _buildRoutesList(context),
),
],
),
);
}
// On tablet/desktop, show full sidebar with toggle
return Container(
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(
child: Text(
'Routes',
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,
),
),
],
),
),
// Routes list
Expanded(
child: _buildRoutesList(context),
),
],
),
);
}
Widget _buildRoutesList(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(AppSpacing.sm),
itemCount: widget.routes.length,
itemBuilder: (context, index) {
final route = widget.routes[index];
final isSelected = widget.selectedRoute?.id == route.id;
return Padding(
padding: EdgeInsets.only(bottom: AppSpacing.sm),
child: _buildRouteButton(context, route, isSelected),
);
},
);
}
Widget _buildRouteButton(
BuildContext context,
DeliveryRoute route,
bool isSelected,
) {
return GlassmorphicRouteCard(
route: route,
isSelected: isSelected,
isCollapsed: !_isExpanded,
onTap: () => widget.onRouteSelected(route),
);
}
}