Primera app funcional

This commit is contained in:
2026-05-22 18:27:43 -06:00
parent 43661dc2b0
commit 37e83a8226
30 changed files with 4053 additions and 291 deletions

View File

@@ -0,0 +1,146 @@
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);
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>[];
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<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);
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)),
])),
],
]);
}
}