import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:http/http.dart' as http; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/resources/source_loctaions_model.dart'; import '../resources/drivers_model.dart'; import '../resources/tankers_model.dart'; class AssignDriverScreenNewDesign extends StatefulWidget { final dynamic order; final dynamic status; const AssignDriverScreenNewDesign({ super.key, this.order, this.status, }); @override State createState() => _AssignDriverScreenNewDesignState(); } class _AssignDriverScreenNewDesignState extends State { GoogleMapController? _mapController; List driversList = []; List tankersList = []; List sourceLocationsList = []; DriversModel? selectedDriver; TankersModel? selectedTanker; SourceLocationsModel? selectedSource; bool loading = true; bool routeLoading = false; Set _markers = {}; Set _polylines = {}; String eta = ''; String distanceText = ''; String routeError = ''; static const String googleApiKey = 'AIzaSyDJpK9RVhlBejtJu9xSGfneuTN6HOfJgSM'; @override void initState() { super.initState(); loadData(); } Future loadData() async { try { final d = await AppSettings.getDrivers(); driversList = (jsonDecode(d)['data'] as List) .map((e) => DriversModel.fromJson(e)) .toList(); final t = await AppSettings.getTankers(); tankersList = (jsonDecode(t)['data'] as List) .map((e) => TankersModel.fromJson(e)) .toList(); final s = await AppSettings.getSourceLoctaions(); sourceLocationsList = (jsonDecode(s)['data'] as List) .map((e) => SourceLocationsModel.fromJson(e)) .toList(); } catch (e) { debugPrint('Load error: $e'); } if (!mounted) return; setState(() { loading = false; _rebuildMarkers(); }); WidgetsBinding.instance.addPostFrameCallback((_) { _fitMap(); }); } double? _toDouble(dynamic value) { if (value == null) return null; if (value is double) return value; if (value is int) return value.toDouble(); return double.tryParse(value.toString().trim()); } LatLng? _deliveryPosition() { try { final lat = _toDouble( widget.order?.lat ?? widget.order?.delivery_lat ?? widget.order?.location_lat, ); final lng = _toDouble( widget.order?.lng ?? widget.order?.delivery_lng ?? widget.order?.location_lng, ); if (lat == null || lng == null) return null; return LatLng(lat, lng); } catch (e) { debugPrint('Delivery parse error: $e'); return null; } } LatLng? _sourcePosition(SourceLocationsModel source) { try { final lat = _toDouble(source.latitude); final lng = _toDouble(source.longitude); if (lat == null || lng == null) return null; return LatLng(lat, lng); } catch (e) { debugPrint('Source parse error: $e'); return null; } } void _rebuildMarkers() { final Set markers = {}; final delivery = _deliveryPosition(); if (delivery != null) { markers.add( Marker( markerId: const MarkerId('delivery'), position: delivery, icon: BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueRed, ), infoWindow: InfoWindow( title: widget.order?.building_name?.toString() ?? 'Delivery', snippet: 'Customer location', ), ), ); } for (final source in sourceLocationsList) { final pos = _sourcePosition(source); if (pos == null) continue; final bool isSelected = selectedSource?.dbId == source.dbId; markers.add( Marker( markerId: MarkerId('source_${source.dbId}'), position: pos, icon: BitmapDescriptor.defaultMarkerWithHue( isSelected ? BitmapDescriptor.hueViolet : BitmapDescriptor.hueAzure, ), infoWindow: InfoWindow( title: source.source_name?.toString() ?? 'Source', snippet: isSelected ? 'Selected source' : 'Tap to select source', ), onTap: () async { setState(() { selectedSource = source; routeError = ''; eta = ''; distanceText = ''; _rebuildMarkers(); }); await _loadRouteAndEta(); WidgetsBinding.instance.addPostFrameCallback((_) { _fitMap(); }); }, ), ); } _markers = markers; } Future _loadRouteAndEta() async { final source = selectedSource; final sourcePos = source == null ? null : _sourcePosition(source); final deliveryPos = _deliveryPosition(); if (sourcePos == null || deliveryPos == null) { setState(() { routeError = 'Source or delivery coordinates missing'; _polylines = {}; }); return; } setState(() { routeLoading = true; routeError = ''; }); try { await Future.wait([ _loadDirectionsPolyline(sourcePos, deliveryPos), _loadEtaWithDirections(sourcePos, deliveryPos), ]); } catch (e) { debugPrint('Route load error: $e'); if (mounted) { setState(() { routeError = 'Unable to load route'; }); } } if (!mounted) return; setState(() { routeLoading = false; }); } Future _loadEtaWithDirections( LatLng origin, LatLng destination, ) async { try { final uri = Uri.parse( 'https://maps.googleapis.com/maps/api/directions/json' '?origin=${origin.latitude},${origin.longitude}' '&destination=${destination.latitude},${destination.longitude}' '&mode=driving' '&departure_time=now' '&traffic_model=best_guess' '&key=$googleApiKey', ); final response = await http.get(uri); final data = jsonDecode(response.body); debugPrint('Directions ETA response: $data'); if (data['status'] != 'OK') { setState(() { routeError = 'ETA unavailable: ${data['status']}'; }); return; } final routes = data['routes'] as List?; if (routes == null || routes.isEmpty) { setState(() { routeError = 'ETA unavailable'; }); return; } final legs = routes.first['legs'] as List?; if (legs == null || legs.isEmpty) { setState(() { routeError = 'ETA unavailable'; }); return; } final leg = legs.first; final distance = leg['distance']; final duration = leg['duration']; final durationInTraffic = leg['duration_in_traffic']; setState(() { distanceText = (distance?['text'] ?? '').toString(); eta = (durationInTraffic?['text'] ?? duration?['text'] ?? '').toString(); }); } catch (e) { debugPrint('ETA error: $e'); setState(() { routeError = 'ETA request failed'; }); } } Future _loadDirectionsPolyline( LatLng origin, LatLng destination, ) async { try { final uri = Uri.parse( 'https://maps.googleapis.com/maps/api/directions/json' '?origin=${origin.latitude},${origin.longitude}' '&destination=${destination.latitude},${destination.longitude}' '&mode=driving' '&departure_time=now' '&traffic_model=best_guess' '&key=$googleApiKey', ); final response = await http.get(uri); final data = jsonDecode(response.body); debugPrint('Directions route response: $data'); if (data['status'] != 'OK') { setState(() { routeError = 'Route unavailable: ${data['status']}'; _polylines = {}; }); return; } final routes = data['routes'] as List?; if (routes == null || routes.isEmpty) { setState(() { routeError = 'No route found'; _polylines = {}; }); return; } final encoded = routes.first['overview_polyline']?['points']?.toString(); if (encoded == null || encoded.isEmpty) { setState(() { routeError = 'Route polyline missing'; _polylines = {}; }); return; } final polylinePoints = PolylinePoints(); final decoded = polylinePoints.decodePolyline(encoded); final points = decoded .map((p) => LatLng(p.latitude, p.longitude)) .toList(); if (points.isEmpty) { setState(() { routeError = 'Failed to decode route'; _polylines = {}; }); return; } setState(() { _polylines = { Polyline( polylineId: const PolylineId('source_delivery_route'), points: points, color: Colors.blue, width: 6, ), }; }); } catch (e) { debugPrint('Polyline error: $e'); setState(() { routeError = 'Route request failed'; _polylines = {}; }); } } Future _fitMap() async { if (_mapController == null) return; final List points = []; final delivery = _deliveryPosition(); if (delivery != null) points.add(delivery); for (final source in sourceLocationsList) { final pos = _sourcePosition(source); if (pos != null) points.add(pos); } if (points.isEmpty) return; if (points.length == 1) { await _mapController!.animateCamera( CameraUpdate.newCameraPosition( CameraPosition(target: points.first, zoom: 14), ), ); return; } double minLat = points.first.latitude; double maxLat = points.first.latitude; double minLng = points.first.longitude; double maxLng = points.first.longitude; for (final point in points) { if (point.latitude < minLat) minLat = point.latitude; if (point.latitude > maxLat) maxLat = point.latitude; if (point.longitude < minLng) minLng = point.longitude; if (point.longitude > maxLng) maxLng = point.longitude; } try { await _mapController!.animateCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: LatLng(minLat, minLng), northeast: LatLng(maxLat, maxLng), ), 80, ), ); } catch (_) { await Future.delayed(const Duration(milliseconds: 300)); try { await _mapController!.animateCamera( CameraUpdate.newLatLngBounds( LatLngBounds( southwest: LatLng(minLat, minLng), northeast: LatLng(maxLat, maxLng), ), 80, ), ); } catch (_) {} } } Future assign() async { if (selectedDriver == null) { _msg('Select driver'); return; } if (selectedTanker == null) { _msg('Select tanker'); return; } if (selectedSource == null) { _msg('Select source from map'); return; } final payload = {}; payload["tankerName"] = selectedTanker!.tanker_name; payload["delivery_agent"] = selectedDriver!.driver_name; payload["delivery_agent_mobile"] = selectedDriver!.phone_number; payload["water_source_location"] = selectedSource!.source_name; AppSettings.preLoaderDialog(context); bool status = false; try { status = await AppSettings.assignTanker(payload, widget.order.dbId); } catch (e) { debugPrint('Assign error: $e'); } if (mounted) { Navigator.pop(context); } if (!mounted) return; if (status) { _msg('Assigned successfully'); Navigator.pop(context, true); } else { _msg('Assignment failed'); } } void _msg(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); } Widget _buildInfoRow(IconData icon, String text) { return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( children: [ Icon(icon, size: 18), const SizedBox(width: 8), Expanded(child: Text(text)), ], ), ); } Widget _buildBottomCard() { return Container( margin: const EdgeInsets.all(15), padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), boxShadow: const [ BoxShadow( blurRadius: 10, color: Colors.black26, offset: Offset(0, 3), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ DropdownButton( isExpanded: true, hint: const Text('Select Driver'), value: selectedDriver, items: driversList.map((driver) { return DropdownMenuItem( value: driver, child: Text(driver.driver_name?.toString() ?? 'Driver'), ); }).toList(), onChanged: (value) { setState(() { selectedDriver = value; }); }, ), const SizedBox(height: 8), DropdownButton( isExpanded: true, hint: const Text('Select Tanker'), value: selectedTanker, items: tankersList.map((tanker) { return DropdownMenuItem( value: tanker, child: Text( '${tanker.tanker_name} (${tanker.capacity})', ), ); }).toList(), onChanged: (value) { setState(() { selectedTanker = value; }); }, ), const SizedBox(height: 8), _buildInfoRow( Icons.water_drop_outlined, selectedSource?.source_name?.toString() ?? 'Select source from map', ), if (routeLoading) ...[ const SizedBox(height: 8), const LinearProgressIndicator(), ], if (distanceText.isNotEmpty || eta.isNotEmpty) ...[ const SizedBox(height: 10), Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0XFFF5F4FF), borderRadius: BorderRadius.circular(12), ), child: Column( children: [ if (distanceText.isNotEmpty) _buildInfoRow(Icons.route, 'Distance: $distanceText'), if (eta.isNotEmpty) _buildInfoRow(Icons.timer_outlined, 'ETA: $eta'), ], ), ), ], if (routeError.isNotEmpty) ...[ const SizedBox(height: 8), Text( routeError, style: const TextStyle( color: Colors.red, fontSize: 12, ), ), ], const SizedBox(height: 12), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: assign, child: const Text('ASSIGN'), ), ), ], ), ); } @override Widget build(BuildContext context) { if (loading) { return const Scaffold( body: Center( child: CircularProgressIndicator(), ), ); } return Scaffold( appBar: AppBar( title: const Text('Enterprise Dispatch Map'), ), body: Stack( children: [ GoogleMap( initialCameraPosition: CameraPosition( target: _deliveryPosition() ?? const LatLng(17.3850, 78.4867), zoom: 12, ), markers: _markers, polylines: _polylines, trafficEnabled: true, zoomControlsEnabled: true, myLocationButtonEnabled: true, mapToolbarEnabled: false, onMapCreated: (controller) { _mapController = controller; Future.delayed(const Duration(milliseconds: 400), () { _fitMap(); }); }, ), Align( alignment: Alignment.bottomCenter, child: _buildBottomCard(), ), ], ), ); } }