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>
This commit is contained in:
+188
-71
@@ -2,15 +2,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../models/delivery.dart';
|
||||
import '../models/delivery_route.dart';
|
||||
import '../providers/providers.dart';
|
||||
import '../api/client.dart';
|
||||
import '../api/openapi_config.dart';
|
||||
import '../models/delivery_commands.dart';
|
||||
import '../utils/breakpoints.dart';
|
||||
import '../utils/responsive.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;
|
||||
@@ -46,6 +48,7 @@ class _DeliveriesPageState extends ConsumerState<DeliveriesPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final deliveriesData = ref.watch(deliveriesProvider(widget.routeFragmentId));
|
||||
final routesData = ref.watch(deliveryRoutesProvider);
|
||||
final token = ref.watch(authTokenProvider).valueOrNull;
|
||||
|
||||
return Scaffold(
|
||||
@@ -62,79 +65,193 @@ class _DeliveriesPageState extends ConsumerState<DeliveriesPage> {
|
||||
.where((d) => d.delivered)
|
||||
.toList();
|
||||
|
||||
return MapSidebarLayout(
|
||||
mapWidget: DarkModeMapComponent(
|
||||
deliveries: deliveries,
|
||||
selectedDelivery: _selectedDelivery,
|
||||
onDeliverySelected: (delivery) {
|
||||
setState(() {
|
||||
_selectedDelivery = delivery;
|
||||
});
|
||||
},
|
||||
return routesData.when(
|
||||
data: (routes) {
|
||||
DeliveryRoute? currentRoute;
|
||||
try {
|
||||
currentRoute = routes.firstWhere(
|
||||
(r) => r.id == widget.routeFragmentId,
|
||||
);
|
||||
} catch (_) {
|
||||
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(
|
||||
mapWidget: DarkModeMapComponent(
|
||||
deliveries: deliveries,
|
||||
selectedDelivery: _selectedDelivery,
|
||||
onDeliverySelected: (delivery) {
|
||||
setState(() {
|
||||
_selectedDelivery = delivery;
|
||||
});
|
||||
},
|
||||
),
|
||||
sidebarWidget: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SegmentedButton<int>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: 0,
|
||||
label: Text('To Do'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: 1,
|
||||
label: Text('Delivered'),
|
||||
),
|
||||
],
|
||||
selected: <int>{_currentSegment},
|
||||
onSelectionChanged: (Set<int> 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
sidebarWidget: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SegmentedButton<int>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: 0,
|
||||
label: Text('To Do'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: 1,
|
||||
label: Text('Delivered'),
|
||||
),
|
||||
],
|
||||
selected: <int>{_currentSegment},
|
||||
onSelectionChanged: (Set<int> newSelection) {
|
||||
setState(() {
|
||||
_currentSegment = newSelection.first;
|
||||
_pageController.animateToPage(
|
||||
_currentSegment,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
},
|
||||
error: (error, stackTrace) => MapSidebarLayout(
|
||||
mapWidget: DarkModeMapComponent(
|
||||
deliveries: deliveries,
|
||||
selectedDelivery: _selectedDelivery,
|
||||
onDeliverySelected: (delivery) {
|
||||
setState(() {
|
||||
_selectedDelivery = delivery;
|
||||
});
|
||||
},
|
||||
),
|
||||
sidebarWidget: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SegmentedButton<int>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: 0,
|
||||
label: Text('To Do'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: 1,
|
||||
label: Text('Delivered'),
|
||||
),
|
||||
],
|
||||
selected: <int>{_currentSegment},
|
||||
onSelectionChanged: (Set<int> 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),
|
||||
),
|
||||
],
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+77
-58
@@ -3,17 +3,29 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../models/delivery_route.dart';
|
||||
import '../providers/providers.dart';
|
||||
import '../utils/breakpoints.dart';
|
||||
import '../utils/responsive.dart';
|
||||
import '../components/premium_route_card.dart';
|
||||
import '../components/collapsible_routes_sidebar.dart';
|
||||
import '../components/dark_mode_map.dart';
|
||||
import 'deliveries_page.dart';
|
||||
import 'settings_page.dart';
|
||||
|
||||
class RoutesPage extends ConsumerWidget {
|
||||
const RoutesPage({super.key});
|
||||
|
||||
void _navigateToDeliveries(BuildContext context, DeliveryRoute route) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DeliveriesPage(
|
||||
routeFragmentId: route.id,
|
||||
routeName: route.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final routesData = ref.watch(deliveryRoutesProvider);
|
||||
final allDeliveriesData = ref.watch(allDeliveriesProvider);
|
||||
final userProfile = ref.watch(userProfileProvider);
|
||||
|
||||
return Scaffold(
|
||||
@@ -73,14 +85,70 @@ class RoutesPage extends ConsumerWidget {
|
||||
child: Text('No routes available'),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
// ignore: unused_result
|
||||
ref.refresh(deliveryRoutesProvider);
|
||||
return allDeliveriesData.when(
|
||||
data: (allDeliveries) {
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
// ignore: unused_result
|
||||
ref.refresh(deliveryRoutesProvider);
|
||||
// ignore: unused_result
|
||||
ref.refresh(allDeliveriesProvider);
|
||||
},
|
||||
child: context.isDesktop
|
||||
? Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DarkModeMapComponent(
|
||||
deliveries: allDeliveries,
|
||||
selectedDelivery: null,
|
||||
onDeliverySelected: null,
|
||||
),
|
||||
),
|
||||
CollapsibleRoutesSidebar(
|
||||
routes: routes,
|
||||
selectedRoute: null,
|
||||
onRouteSelected: (route) {
|
||||
_navigateToDeliveries(context, route);
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DarkModeMapComponent(
|
||||
deliveries: allDeliveries,
|
||||
selectedDelivery: null,
|
||||
onDeliverySelected: null,
|
||||
),
|
||||
),
|
||||
CollapsibleRoutesSidebar(
|
||||
routes: routes,
|
||||
selectedRoute: null,
|
||||
onRouteSelected: (route) {
|
||||
_navigateToDeliveries(context, route);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: context.isDesktop
|
||||
? _buildDesktopGrid(context, routes)
|
||||
: _buildMobileList(context, routes),
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
error: (error, stackTrace) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Error loading deliveries: $error'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () => ref.refresh(allDeliveriesProvider),
|
||||
child: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
@@ -103,53 +171,4 @@ class RoutesPage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMobileList(BuildContext context, List<DeliveryRoute> routes) {
|
||||
final spacing = ResponsiveSpacing.md(context);
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.all(ResponsiveSpacing.md(context)),
|
||||
itemCount: routes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final route = routes[index];
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: spacing),
|
||||
child: _buildRouteCard(context, route),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDesktopGrid(BuildContext context, List<DeliveryRoute> routes) {
|
||||
final spacing = ResponsiveSpacing.lg(context);
|
||||
final columns = context.isTablet ? 2 : 3;
|
||||
return GridView.builder(
|
||||
padding: EdgeInsets.all(spacing),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
crossAxisSpacing: spacing,
|
||||
mainAxisSpacing: spacing,
|
||||
childAspectRatio: 1.2,
|
||||
),
|
||||
itemCount: routes.length,
|
||||
itemBuilder: (context, index) {
|
||||
final route = routes[index];
|
||||
return _buildRouteCard(context, route);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRouteCard(BuildContext context, DeliveryRoute route) {
|
||||
return PremiumRouteCard(
|
||||
route: route,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DeliveriesPage(
|
||||
routeFragmentId: route.id,
|
||||
routeName: route.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user