where my git go?

This commit is contained in:
2025-11-15 10:03:27 -05:00
commit 695b6a07e3
150 changed files with 8838 additions and 0 deletions
@@ -0,0 +1,227 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../../core/theme/app_theme.dart';
import '../../data/models/route_model.dart';
import '../providers/route_provider.dart';
import '../widgets/route_card.dart';
import 'route_details_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
// Load routes for demo driver
// In production, use actual driver ID from authentication
Future.microtask(() {
context.read<RouteProvider>().loadRoutes('driver_1');
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('My Routes'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
context.read<RouteProvider>().loadRoutes('driver_1');
},
),
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
// Navigate to profile page
},
),
],
),
body: Consumer<RouteProvider>(
builder: (context, routeProvider, child) {
if (routeProvider.isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (routeProvider.error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: AppTheme.errorColor,
),
const SizedBox(height: 16),
Text(
'Error loading routes',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
routeProvider.error!,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: () {
routeProvider.loadRoutes('driver_1');
},
icon: const Icon(Icons.refresh),
label: const Text('Retry'),
),
],
),
);
}
if (routeProvider.routes.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.route,
size: 64,
color: Colors.grey,
),
const SizedBox(height: 16),
Text(
'No routes available',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
'Check back later for new routes',
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
);
}
return RefreshIndicator(
onRefresh: () async {
await routeProvider.loadRoutes('driver_1');
},
child: ListView(
padding: const EdgeInsets.symmetric(vertical: 8),
children: [
_buildSummaryCard(context, routeProvider.routes),
const SizedBox(height: 8),
...routeProvider.routes.map((route) {
return RouteCard(
route: route,
onTap: () {
routeProvider.setCurrentRoute(route);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const RouteDetailsPage(),
),
);
},
);
}),
],
),
);
},
),
);
}
Widget _buildSummaryCard(BuildContext context, List<RouteModel> routes) {
final todayRoutes = routes.where((route) {
return route.date.day == DateTime.now().day &&
route.date.month == DateTime.now().month &&
route.date.year == DateTime.now().year;
}).toList();
final completedRoutes =
todayRoutes.where((r) => r.status == RouteStatus.completed).length;
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Today\'s Summary',
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildSummaryItem(
context,
'Total Routes',
todayRoutes.length.toString(),
Icons.route,
AppTheme.primaryColor,
),
_buildSummaryItem(
context,
'Completed',
completedRoutes.toString(),
Icons.check_circle,
AppTheme.completedColor,
),
_buildSummaryItem(
context,
'Pending',
(todayRoutes.length - completedRoutes).toString(),
Icons.pending,
AppTheme.warningColor,
),
],
),
],
),
),
);
}
Widget _buildSummaryItem(
BuildContext context,
String label,
String value,
IconData icon,
Color color,
) {
return Column(
children: [
Icon(icon, color: color, size: 32),
const SizedBox(height: 8),
Text(
value,
style: Theme.of(context).textTheme.displaySmall?.copyWith(
color: color,
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
],
);
}
}
@@ -0,0 +1,339 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../services/location/location_service.dart';
import '../../data/models/route_model.dart';
import '../../data/models/stop_model.dart';
class MapViewPage extends StatefulWidget {
final RouteModel route;
const MapViewPage({super.key, required this.route});
@override
State<MapViewPage> createState() => _MapViewPageState();
}
class _MapViewPageState extends State<MapViewPage> {
final Completer<GoogleMapController> _mapController = Completer();
final LocationService _locationService = LocationService();
final Set<Marker> _markers = {};
final Set<Polyline> _polylines = {};
LatLng? _currentPosition;
StreamSubscription? _locationSubscription;
@override
void initState() {
super.initState();
_initializeMap();
_startLocationTracking();
}
@override
void dispose() {
_locationSubscription?.cancel();
super.dispose();
}
Future<void> _initializeMap() async {
await _createMarkers();
await _fitBounds();
}
Future<void> _createMarkers() async {
final markers = <Marker>{};
for (int i = 0; i < widget.route.stops.length; i++) {
final stop = widget.route.stops[i];
final position = LatLng(
stop.location.latitude,
stop.location.longitude,
);
markers.add(
Marker(
markerId: MarkerId(stop.id),
position: position,
icon: await _getMarkerIcon(stop),
infoWindow: InfoWindow(
title: '${i + 1}. ${stop.customerName}',
snippet: stop.type == StopType.pickup ? 'Pickup' : 'Dropoff',
),
onTap: () => _onMarkerTapped(stop),
),
);
}
if (mounted) {
setState(() {
_markers.addAll(markers);
});
}
}
Future<BitmapDescriptor> _getMarkerIcon(StopModel stop) async {
// Determine color based on status
final hue = switch (stop.status) {
StopStatus.completed => BitmapDescriptor.hueGreen,
StopStatus.inProgress => BitmapDescriptor.hueBlue,
StopStatus.failed => BitmapDescriptor.hueRed,
_ => BitmapDescriptor.hueOrange,
};
return BitmapDescriptor.defaultMarkerWithHue(hue);
}
Future<void> _fitBounds() async {
if (widget.route.stops.isEmpty) return;
final controller = await _mapController.future;
double minLat = widget.route.stops.first.location.latitude;
double maxLat = widget.route.stops.first.location.latitude;
double minLng = widget.route.stops.first.location.longitude;
double maxLng = widget.route.stops.first.location.longitude;
for (final stop in widget.route.stops) {
if (stop.location.latitude < minLat) minLat = stop.location.latitude;
if (stop.location.latitude > maxLat) maxLat = stop.location.latitude;
if (stop.location.longitude < minLng) minLng = stop.location.longitude;
if (stop.location.longitude > maxLng) maxLng = stop.location.longitude;
}
final bounds = LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
);
controller.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 100),
);
}
void _startLocationTracking() {
_locationSubscription = _locationService.getLocationStream().listen(
(position) {
final newPosition = LatLng(position.latitude, position.longitude);
if (mounted) {
setState(() {
_currentPosition = newPosition;
// Add or update current location marker
_markers.removeWhere(
(marker) => marker.markerId.value == 'current_location',
);
_markers.add(
Marker(
markerId: const MarkerId('current_location'),
position: newPosition,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueAzure,
),
infoWindow: const InfoWindow(title: 'Your Location'),
),
);
});
}
},
onError: (error) {
print('Error tracking location: $error');
},
);
}
void _onMarkerTapped(StopModel stop) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
stop.customerName,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
stop.type == StopType.pickup ? 'Pickup' : 'Dropoff',
style: Theme.of(context).textTheme.bodyMedium,
),
if (stop.location.address != null) ...[
const SizedBox(height: 8),
Text(
stop.location.address!,
style: Theme.of(context).textTheme.bodySmall,
),
],
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () {
Navigator.pop(context);
_navigateToStop(stop);
},
icon: const Icon(Icons.navigation),
label: const Text('Navigate to this stop'),
),
),
],
),
),
);
}
Future<void> _navigateToStop(StopModel stop) async {
// This will use the NavigationService to open external navigation
final navigationService =
(() => throw UnimplementedError('Add NavigationService'))();
}
Future<void> _centerOnCurrentLocation() async {
if (_currentPosition != null) {
final controller = await _mapController.future;
controller.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: _currentPosition!,
zoom: AppConstants.defaultZoom,
),
),
);
} else {
final position = await _locationService.getCurrentLocation();
if (position != null) {
final newPosition = LatLng(position.latitude, position.longitude);
final controller = await _mapController.future;
controller.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: newPosition,
zoom: AppConstants.defaultZoom,
),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Route ${widget.route.id} Map'),
actions: [
IconButton(
icon: const Icon(Icons.layers),
onPressed: () {
// Toggle map type
},
),
],
),
body: Stack(
children: [
GoogleMap(
onMapCreated: (controller) {
_mapController.complete(controller);
},
initialCameraPosition: CameraPosition(
target: widget.route.stops.isNotEmpty
? LatLng(
widget.route.stops.first.location.latitude,
widget.route.stops.first.location.longitude,
)
: const LatLng(0, 0),
zoom: AppConstants.defaultZoom,
),
markers: _markers,
polylines: _polylines,
myLocationEnabled: true,
myLocationButtonEnabled: false,
mapToolbarEnabled: true,
zoomControlsEnabled: false,
),
Positioned(
top: 16,
left: 16,
right: 16,
child: Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Route Progress',
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildLegendItem(
'Completed',
AppTheme.completedColor,
),
_buildLegendItem(
'In Progress',
AppTheme.inProgressColor,
),
_buildLegendItem(
'Pending',
AppTheme.pendingColor,
),
],
),
],
),
),
),
),
],
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: 'center',
mini: true,
onPressed: _centerOnCurrentLocation,
child: const Icon(Icons.my_location),
),
const SizedBox(height: 12),
FloatingActionButton(
heroTag: 'fit',
mini: true,
onPressed: _fitBounds,
child: const Icon(Icons.zoom_out_map),
),
],
),
);
}
Widget _buildLegendItem(String label, Color color) {
return Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
Text(
label,
style: const TextStyle(fontSize: 12),
),
],
);
}
}
@@ -0,0 +1,437 @@
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,
),
);
}
}
}