import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import '../core/app_colors.dart'; import '../models/route_model.dart'; import '../services/route_simulator_service.dart'; List _smooth(List pts) { if (pts.length < 2) return pts; final res = []; for (int i = 0; i < pts.length - 1; i++) { final p0 = pts[i > 0 ? i - 1 : i]; final p1 = pts[i]; final p2 = pts[i + 1]; final p3 = pts[i + 2 < pts.length ? i + 2 : i + 1]; res.add(p1); for (int j = 1; j <= 8; j++) { final t = j / 9.0; res.add(LatLng(_cr(p0.latitude, p1.latitude, p2.latitude, p3.latitude, t), _cr(p0.longitude, p1.longitude, p2.longitude, p3.longitude, t))); } } res.add(pts.last); return res; } double _cr(double a, double b, double c, double d, double t) => 0.5 * ( (2*b) + (-a+c)*t + (2*a-5*b+4*c-d)*t*t + (-a+3*b-3*c+d)*t*t*t); class RouteMapWidget extends StatelessWidget { final RouteModel route; final RouteSimulatorService simulator; final bool showFullRoute; final double height; const RouteMapWidget({super.key, required this.route, required this.simulator, this.showFullRoute = false, this.height = 300}); @override Widget build(BuildContext context) { final posIdx = simulator.getPositionIndex(route.routeId); final cur = posIdx < route.positions.length ? route.positions[posIdx].latLng : route.positions.last.latLng; final gps = simulator.isGpsActive(route.routeId); final all = _smooth(route.polylinePoints); final done = posIdx > 0 ? _smooth(route.positions.take(posIdx+1).map((p) => p.latLng).toList()) : []; return ClipRRect(borderRadius: BorderRadius.circular(12), child: SizedBox(height: height, child: FlutterMap( options: MapOptions(initialCenter: cur, initialZoom: 14), children: [ TileLayer(urlTemplate:'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName:'com.example.celaya_limpia'), PolylineLayer(polylines:[ Polyline(points:all, color:Colors.grey.withOpacity(0.4), strokeWidth:4, borderColor:Colors.white54, borderStrokeWidth:1), ]), if (done.isNotEmpty) PolylineLayer(polylines:[ Polyline(points:done, color:AppColors.guindaPrimary, strokeWidth:5, borderColor:AppColors.guindaDark, borderStrokeWidth:1), ]), MarkerLayer(markers:[ Marker(point:cur, width:52, height:52, child:Container(decoration:BoxDecoration( color:gps?AppColors.guindaPrimary:Colors.grey, shape:BoxShape.circle, border:Border.all(color:Colors.white,width:2.5), boxShadow:[BoxShadow(color:Colors.black38,blurRadius:6)]), child:Icon(gps?Icons.local_shipping:Icons.gps_off,color:Colors.white,size:24))), Marker(point:route.positions.first.latLng, width:28, height:28, child:Container(decoration:BoxDecoration(color:AppColors.verdeExito,shape:BoxShape.circle, border:Border.all(color:Colors.white,width:2)), child:const Icon(Icons.circle,color:Colors.white,size:10))), Marker(point:route.positions.last.latLng, width:32, height:32, child:Container(decoration:BoxDecoration(color:AppColors.rojoError,shape:BoxShape.circle, border:Border.all(color:Colors.white,width:2)), child:const Icon(Icons.flag,color:Colors.white,size:16))), ]), ]))); } } class AdminMapWidget extends StatefulWidget { final List routes; final RouteSimulatorService simulator; const AdminMapWidget({super.key, required this.routes, required this.simulator}); @override State createState() => _AdminMapWidgetState(); } class _AdminMapWidgetState extends State { String? _sel; static const _colors = [ Colors.blue, Colors.green, Colors.orange, Colors.purple, Colors.teal, Colors.red, Colors.indigo, Colors.brown, Colors.cyan, Colors.pink, Colors.amber, Colors.lime, Colors.deepOrange, Colors.lightBlue, Colors.deepPurple, ]; @override Widget build(BuildContext context) { return Column(children:[ Expanded(child: FlutterMap( options: const MapOptions(initialCenter:LatLng(20.5211,-100.8196), initialZoom:12), children:[ TileLayer(urlTemplate:'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName:'com.example.celaya_limpia'), PolylineLayer(polylines: widget.routes.asMap().entries.map((e){ final c = _colors[e.key % _colors.length]; final isS = _sel==null||_sel==e.value.routeId; return Polyline(points:_smooth(e.value.polylinePoints), color:c.withOpacity(isS?0.85:0.2), strokeWidth:isS?4:1.5, borderColor:Colors.white.withOpacity(isS?0.4:0), borderStrokeWidth:1); }).toList()), MarkerLayer(markers: widget.routes.asMap().entries.map((e){ final r = e.value; final idx = widget.simulator.getPositionIndex(r.routeId); final pos = idx < r.positions.length ? r.positions[idx].latLng : r.positions.last.latLng; final c = _colors[e.key % _colors.length]; final gps = widget.simulator.isGpsActive(r.routeId); return Marker(point:pos, width:46, height:46, child:GestureDetector( onTap:()=>setState(()=>_sel=_sel==r.routeId?null:r.routeId), child:Container(decoration:BoxDecoration(color:gps?c:Colors.grey, shape:BoxShape.circle, border:Border.all(color:Colors.white,width:2), boxShadow:[BoxShadow(color:Colors.black26,blurRadius:4)]), child:Center(child:Text(r.truckId.toString().substring(1), style:const TextStyle(color:Colors.white,fontSize:11,fontWeight:FontWeight.bold)))))); }).toList()), ], )), if (_sel!=null) ...[ const Divider(height:1), Container(padding:const EdgeInsets.symmetric(horizontal:16,vertical:10), color:AppColors.guindaPrimary, child:Row(children:[ const Icon(Icons.local_shipping,color:Colors.white,size:16), const SizedBox(width:8), Expanded(child:Text(widget.routes.firstWhere((r)=>r.routeId==_sel).name, style:const TextStyle(color:Colors.white,fontWeight:FontWeight.bold,fontSize:13))), Text('Pos ${widget.simulator.getPositionIndex(_sel!)}/7', style:const TextStyle(color:AppColors.dorado,fontSize:12)), ])), ], ]); } }