ionic-planb-logistic-app-fl.../lib/pages/routes_page.dart
Claude Code 4b03e9aba5 Initial commit: Plan B Logistics Flutter app with dark mode and responsive design
Implements complete refactor of Ionic Angular logistics app to Flutter/Dart with:
- Svrnty dark mode console theme (Material Design 3)
- Responsive layouts (mobile, tablet, desktop) following FRONTEND standards
- CQRS API integration with Result<T> error handling
- OAuth2/OIDC authentication support (mocked for initial testing)
- Delivery route and delivery management features
- Multi-language support (EN/FR) with i18n
- Native integrations (camera, phone calls, maps)
- Strict typing throughout codebase
- Mock data for UI testing without backend

Follows all FRONTEND style guides, design patterns, and conventions.
App is running in dark mode and fully responsive across all device sizes.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 04:58:10 -04:00

184 lines
5.8 KiB
Dart

import 'package:flutter/material.dart';
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 'deliveries_page.dart';
import 'settings_page.dart';
class RoutesPage extends ConsumerWidget {
const RoutesPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final routesData = ref.watch(deliveryRoutesProvider);
final userProfile = ref.watch(userProfileProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Delivery Routes'),
elevation: 0,
actions: [
userProfile.when(
data: (profile) => PopupMenuButton<String>(
onSelected: (value) {
if (value == 'settings') {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const SettingsPage(),
),
);
}
},
itemBuilder: (BuildContext context) => [
PopupMenuItem(
value: 'profile',
child: Text(profile?.fullName ?? 'User'),
enabled: false,
),
const PopupMenuDivider(),
const PopupMenuItem(
value: 'settings',
child: Text('Settings'),
),
],
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Center(
child: Text(
profile?.fullName ?? 'User',
style: Theme.of(context).textTheme.titleSmall,
),
),
),
),
loading: () => const Padding(
padding: EdgeInsets.all(16.0),
child: SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
error: (error, stackTrace) => const SizedBox(),
),
],
),
body: routesData.when(
data: (routes) {
if (routes.isEmpty) {
return const Center(
child: Text('No routes available'),
);
}
return RefreshIndicator(
onRefresh: () async {
// ignore: unused_result
ref.refresh(deliveryRoutesProvider);
},
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: $error'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => ref.refresh(deliveryRoutesProvider),
child: const Text('Retry'),
),
],
),
),
),
);
}
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 Card(
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DeliveriesPage(
routeFragmentId: route.routeFragmentId,
routeName: route.name,
),
),
);
},
child: Padding(
padding: EdgeInsets.all(ResponsiveSpacing.md(context)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
route.name,
style: Theme.of(context).textTheme.titleLarge,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: ResponsiveSpacing.sm(context)),
Text(
'${route.completedDeliveries}/${route.totalDeliveries} completed',
style: Theme.of(context).textTheme.bodySmall,
),
SizedBox(height: ResponsiveSpacing.md(context)),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: route.progress,
minHeight: 8,
),
),
],
),
),
),
);
}
}