ionic-planb-logistic-app-fl.../lib/features/routes/presentation/pages/route_details_page.dart

438 lines
14 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/widgets/status_badge.dart';
import '../../../../services/location/location_service.dart';
import '../../../../services/maps/navigation_service.dart';
import '../../data/models/route_model.dart';
import '../../data/models/stop_model.dart';
import '../providers/route_provider.dart';
import '../widgets/stop_card.dart';
import 'map_view_page.dart';
class RouteDetailsPage extends StatelessWidget {
const RouteDetailsPage({super.key});
@override
Widget build(BuildContext context) {
return Consumer<RouteProvider>(
builder: (context, routeProvider, child) {
final route = routeProvider.currentRoute;
if (route == null) {
return Scaffold(
appBar: AppBar(
title: const Text('Route Details'),
),
body: const Center(
child: Text('No route selected'),
),
);
}
return Scaffold(
appBar: AppBar(
title: Text('Route ${route.id}'),
actions: [
IconButton(
icon: const Icon(Icons.map),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MapViewPage(route: route),
),
);
},
),
],
),
body: Column(
children: [
_buildRouteHeader(context, route),
const Divider(height: 1),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: route.stops.length,
itemBuilder: (context, index) {
final stop = route.stops[index];
return StopCard(
stop: stop,
onTap: () {
_showStopDetails(context, route, stop);
},
onNavigate: () {
_navigateToStop(context, stop);
},
);
},
),
),
],
),
floatingActionButton: _buildActionButton(context, route),
);
},
);
}
Widget _buildRouteHeader(BuildContext context, RouteModel route) {
return Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
DateFormat('EEEE, MMM dd').format(route.date),
style: Theme.of(context).textTheme.titleMedium,
),
StatusBadge(
status: route.status.toString().split('.').last,
fontSize: 14,
),
],
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoItem(
context,
Icons.route,
'${route.totalDistance.toStringAsFixed(1)} km',
'Distance',
),
_buildInfoItem(
context,
Icons.access_time,
'${route.estimatedDuration} min',
'Duration',
),
_buildInfoItem(
context,
Icons.location_on,
'${route.stops.length}',
'Stops',
),
],
),
const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: LinearProgressIndicator(
value: route.progressPercentage / 100,
minHeight: 12,
backgroundColor: Colors.grey[300],
valueColor: AlwaysStoppedAnimation<Color>(
route.status == RouteStatus.completed
? AppTheme.completedColor
: AppTheme.inProgressColor,
),
),
),
const SizedBox(height: 8),
Text(
'${route.completedStopsCount}/${route.totalStopsCount} stops completed',
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildInfoItem(
BuildContext context,
IconData icon,
String value,
String label,
) {
return Column(
children: [
Icon(icon, color: AppTheme.primaryColor, size: 24),
const SizedBox(height: 8),
Text(
value,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
],
);
}
Widget? _buildActionButton(BuildContext context, RouteModel route) {
if (route.status == RouteStatus.completed ||
route.status == RouteStatus.cancelled) {
return null;
}
final routeProvider = context.read<RouteProvider>();
if (route.status == RouteStatus.notStarted) {
return FloatingActionButton.extended(
onPressed: () async {
final success = await routeProvider.startRoute(route.id);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
success ? 'Route started!' : 'Failed to start route',
),
backgroundColor: success ? AppTheme.successColor : AppTheme.errorColor,
),
);
}
},
icon: const Icon(Icons.play_arrow),
label: const Text('Start Route'),
);
}
if (route.status == RouteStatus.inProgress) {
final allCompleted =
route.stops.every((stop) => stop.status == StopStatus.completed);
if (allCompleted) {
return FloatingActionButton.extended(
onPressed: () async {
final success = await routeProvider.completeRoute(route.id);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
success ? 'Route completed!' : 'Failed to complete route',
),
backgroundColor:
success ? AppTheme.successColor : AppTheme.errorColor,
),
);
}
},
icon: const Icon(Icons.check),
label: const Text('Complete Route'),
);
}
}
return null;
}
void _showStopDetails(
BuildContext context, RouteModel route, StopModel stop) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) => DraggableScrollableSheet(
initialChildSize: 0.7,
minChildSize: 0.5,
maxChildSize: 0.9,
expand: false,
builder: (context, scrollController) => Padding(
padding: const EdgeInsets.all(16),
child: ListView(
controller: scrollController,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Stop Details',
style: Theme.of(context).textTheme.titleLarge,
),
StatusBadge(
status: stop.status.toString().split('.').last,
),
],
),
const SizedBox(height: 24),
_buildDetailRow(
context,
'Customer',
stop.customerName,
Icons.person,
),
_buildDetailRow(
context,
'Type',
stop.type == StopType.pickup ? 'Pickup' : 'Dropoff',
Icons.local_shipping,
),
if (stop.customerPhone != null)
_buildDetailRow(
context,
'Phone',
stop.customerPhone!,
Icons.phone,
),
_buildDetailRow(
context,
'Scheduled',
DateFormat('hh:mm a').format(stop.scheduledTime),
Icons.schedule,
),
if (stop.location.address != null)
_buildDetailRow(
context,
'Address',
stop.location.address!,
Icons.location_on,
),
if (stop.items.isNotEmpty) ...[
const SizedBox(height: 16),
Text(
'Items',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
...stop.items.map((item) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
const Icon(Icons.check_circle_outline, size: 20),
const SizedBox(width: 8),
Text(item),
],
),
)),
],
const SizedBox(height: 24),
if (stop.status == StopStatus.pending ||
stop.status == StopStatus.inProgress) ...[
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
Navigator.pop(context);
_navigateToStop(context, stop);
},
icon: const Icon(Icons.navigation),
label: const Text('Navigate'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () async {
Navigator.pop(context);
final success = await context
.read<RouteProvider>()
.updateStopStatus(
route.id,
stop.id,
StopStatus.completed,
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
success
? 'Stop completed!'
: 'Failed to complete stop',
),
backgroundColor: success
? AppTheme.successColor
: AppTheme.errorColor,
),
);
}
},
icon: const Icon(Icons.check),
label: const Text('Complete'),
),
),
],
),
],
],
),
),
),
);
}
Widget _buildDetailRow(
BuildContext context,
String label,
String value,
IconData icon,
) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 20, color: Colors.grey),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 4),
Text(
value,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
],
),
);
}
Future<void> _navigateToStop(BuildContext context, StopModel stop) async {
final locationService = LocationService();
final navigationService = NavigationService();
// Get current location
final currentPosition = await locationService.getCurrentLocation();
final success = await navigationService.openNavigation(
destinationLat: stop.location.latitude,
destinationLng: stop.location.longitude,
originLat: currentPosition?.latitude,
originLng: currentPosition?.longitude,
destinationName: stop.customerName,
);
if (!success && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Failed to open navigation'),
backgroundColor: AppTheme.errorColor,
),
);
}
}
}