Actualizacion del programa

This commit is contained in:
2026-05-23 01:40:39 -06:00
parent 458af32fcf
commit c6a1a67469
132 changed files with 11009 additions and 168 deletions

View File

@@ -0,0 +1,233 @@
import 'dart:math' as math;
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<LatLng> _smooth(List<LatLng> pts) {
if (pts.length < 2) return pts;
final res = <LatLng>[];
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);
// Calcular bearing entre dos puntos
double _bearing(LatLng from, LatLng to) {
final lat1 = from.latitude * math.pi / 180;
final lat2 = to.latitude * math.pi / 180;
final dLng = (to.longitude - from.longitude) * math.pi / 180;
final y = math.sin(dLng) * math.cos(lat2);
final x = math.cos(lat1)*math.sin(lat2) - math.sin(lat1)*math.cos(lat2)*math.cos(dLng);
return (math.atan2(y, x) * 180 / math.pi + 360) % 360;
}
// ── Mapa ciudadano ────────────────────────────────────────────────────────
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())
: <LatLng>[];
// Calcular bearing hacia siguiente punto
double bear = 0;
if (posIdx < route.positions.length - 1) {
bear = _bearing(cur, route.positions[posIdx+1].latLng);
}
return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: SizedBox(height: height,
child: FlutterMap(
options: MapOptions(
initialCenter: cur, initialZoom: 14.5,
initialRotation: -bear, // Rotar mapa según dirección del camión
),
children: [
TileLayer(urlTemplate:'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName:'com.example.celaya_limpia'),
// Ruta completa (gris punteada)
PolylineLayer(polylines:[
Polyline(points:all, color:Colors.grey.shade400, strokeWidth:4,
borderColor:Colors.white54, borderStrokeWidth:1),
]),
// Tramo recorrido (guinda)
if (done.isNotEmpty) PolylineLayer(polylines:[
Polyline(points:done, color:AppColors.guindaPrimary, strokeWidth:5,
borderColor:AppColors.guindaDark, borderStrokeWidth:1),
]),
MarkerLayer(markers:[
// Camión con orientación
Marker(point:cur, width:56, height:56,
child:Transform.rotate(
angle: bear * math.pi / 180,
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.navigation:Icons.gps_off,
color:Colors.white,size:26)))),
// Origen
Marker(point:route.positions.first.latLng, width:24, height:24,
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:8))),
// Destino
Marker(point:route.positions.last.latLng, width:28, height:28,
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:14))),
]),
])));
}
}
// ── Mapa Admin (todas las rutas) ──────────────────────────────────────────
class AdminMapWidget extends StatefulWidget {
final List<RouteModel> routes;
final RouteSimulatorService simulator;
const AdminMapWidget({super.key, required this.routes, required this.simulator});
@override State<AdminMapWidget> createState() => _AdminMapWidgetState();
}
class _AdminMapWidgetState extends State<AdminMapWidget> {
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);
double bear = 0;
if (idx < r.positions.length - 1) bear = _bearing(pos, r.positions[idx+1].latLng);
return Marker(point:pos, width:44, height:44,
child:GestureDetector(
onTap:()=>setState(()=>_sel=_sel==r.routeId?null:r.routeId),
child:Transform.rotate(angle: bear*math.pi/180,
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) 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)),
])),
]);
}
}
// ── Mapa conductor (con bearing) ──────────────────────────────────────────
class DriverRouteMap extends StatelessWidget {
final RouteModel route;
final RouteSimulatorService simulator;
const DriverRouteMap({super.key, required this.route, required this.simulator});
@override
Widget build(BuildContext context) {
final idx = simulator.getPositionIndex(route.routeId);
final gps = simulator.isGpsActive(route.routeId);
final positions = route.positions;
final cur = idx < positions.length ? positions[idx].latLng : positions.last.latLng;
double bear = 0;
if (idx < positions.length - 1) bear = _bearing(cur, positions[idx+1].latLng);
final done = idx > 0
? _smooth(positions.take(idx+1).map((p)=>p.latLng).toList())
: <LatLng>[];
final pending = _smooth(positions.skip(idx).map((p)=>p.latLng).toList());
return FlutterMap(
options: MapOptions(initialCenter:cur, initialZoom:15, initialRotation:-bear),
children:[
TileLayer(urlTemplate:'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName:'com.example.celaya_limpia'),
PolylineLayer(polylines:[
Polyline(points:pending, color:Colors.grey.shade400, strokeWidth:5,
borderColor:Colors.white54, borderStrokeWidth:1),
if (done.isNotEmpty)
Polyline(points:done, color:AppColors.moradoConductor, strokeWidth:6,
borderColor:AppColors.moradoConductor.withOpacity(0.4), borderStrokeWidth:2),
]),
MarkerLayer(markers:[
// Waypoints pendientes
...positions.skip(idx+1).take(4).toList().asMap().entries.map((e)=>
Marker(point:e.value.latLng, width:22, height:22,
child:Container(decoration:BoxDecoration(color:Colors.amber,shape:BoxShape.circle,
border:Border.all(color:Colors.white,width:1.5)),
child:Center(child:Text('${idx+2+e.key}',
style:const TextStyle(color:Colors.white,fontSize:9,fontWeight:FontWeight.bold)))))),
// Camión orientado
Marker(point:cur, width:56, height:56,
child:Transform.rotate(angle:bear*math.pi/180,
child:Container(decoration:BoxDecoration(
color:gps?AppColors.moradoConductor:Colors.grey, shape:BoxShape.circle,
border:Border.all(color:Colors.white,width:2.5),
boxShadow:[BoxShadow(color:Colors.black38,blurRadius:8)]),
child:Icon(gps?Icons.navigation:Icons.gps_off,color:Colors.white,size:28)))),
]),
]);
}
}