Primera app funcional
This commit is contained in:
456
lib/screens/driver/driver_home_screen.dart
Normal file
456
lib/screens/driver/driver_home_screen.dart
Normal file
@@ -0,0 +1,456 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../services/auth_service.dart';
|
||||
import '../../services/route_simulator_service.dart';
|
||||
import '../../database/db_helper.dart';
|
||||
import '../../models/models.dart';
|
||||
import '../../data/routes_data.dart';
|
||||
import '../../widgets/route_map_widget.dart';
|
||||
|
||||
class DriverHomeScreen extends StatefulWidget {
|
||||
const DriverHomeScreen({super.key});
|
||||
@override State<DriverHomeScreen> createState() => _DriverHomeScreenState();
|
||||
}
|
||||
|
||||
class _DriverHomeScreenState extends State<DriverHomeScreen> {
|
||||
int _tab = 0;
|
||||
List<AssignmentModel> _assignments = [];
|
||||
String? _todayRouteId;
|
||||
|
||||
@override void initState() { super.initState(); _load(); }
|
||||
|
||||
Future<void> _load() async {
|
||||
final auth = context.read<AuthService>();
|
||||
if (auth.currentUser == null) return;
|
||||
final list = await DbHelper.getAsignacionesByConductor(auth.currentUser!.id!);
|
||||
final today = _todayDia();
|
||||
setState(() {
|
||||
_assignments = list;
|
||||
final match = list.where((a) => a.diaSemana == today);
|
||||
_todayRouteId = match.isNotEmpty ? match.first.routeId : null;
|
||||
});
|
||||
if (_todayRouteId != null) {
|
||||
context.read<RouteSimulatorService>().startRoute(_todayRouteId!);
|
||||
}
|
||||
}
|
||||
|
||||
String _todayDia() {
|
||||
const d = ['','LUNES','MARTES','MIERCOLES','JUEVES','VIERNES','SABADO','DOMINGO'];
|
||||
return d[DateTime.now().weekday];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final auth = context.watch<AuthService>();
|
||||
final sim = context.watch<RouteSimulatorService>();
|
||||
final route = _todayRouteId != null ? getRouteById(_todayRouteId!) : null;
|
||||
|
||||
// Solo notificaciones de la ruta actual del conductor
|
||||
final lastNotif = _todayRouteId != null
|
||||
? sim.getNotificationForRoute(_todayRouteId!) : null;
|
||||
|
||||
final tabs = [
|
||||
_DriverMainTab(auth:auth, sim:sim, route:route,
|
||||
assignments:_assignments, todayRouteId:_todayRouteId, onRefresh:_load),
|
||||
if (route != null) _DriverMapTab(route:route, sim:sim)
|
||||
else const Center(child:Text('Sin ruta hoy')),
|
||||
_DriverReportesTab(conductorId:auth.currentUser?.id, todayRouteId:_todayRouteId),
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
body: Stack(children:[
|
||||
tabs[_tab],
|
||||
if (lastNotif != null)
|
||||
Positioned(top:MediaQuery.of(context).padding.top+8, left:0, right:0,
|
||||
child:_NotifBanner(notif:lastNotif,
|
||||
onDismiss:()=>sim.dismissRouteNotification(_todayRouteId??''))),
|
||||
]),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: _tab,
|
||||
onDestinationSelected: (i) => setState(()=>_tab=i),
|
||||
backgroundColor: Colors.white,
|
||||
indicatorColor: AppColors.moradoConductor.withOpacity(0.15),
|
||||
destinations: const [
|
||||
NavigationDestination(icon:Icon(Icons.dashboard_outlined),
|
||||
selectedIcon:Icon(Icons.dashboard,color:AppColors.moradoConductor),label:'Mi Ruta'),
|
||||
NavigationDestination(icon:Icon(Icons.map_outlined),
|
||||
selectedIcon:Icon(Icons.map,color:AppColors.moradoConductor),label:'Mapa'),
|
||||
NavigationDestination(icon:Icon(Icons.report_problem_outlined),
|
||||
selectedIcon:Icon(Icons.report_problem,color:AppColors.moradoConductor),label:'Incidente'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tab principal ─────────────────────────────────────────────────────────
|
||||
class _DriverMainTab extends StatefulWidget {
|
||||
final AuthService auth; final RouteSimulatorService sim;
|
||||
final route; final assignments; final todayRouteId; final VoidCallback onRefresh;
|
||||
const _DriverMainTab({required this.auth, required this.sim, required this.route,
|
||||
required this.assignments, required this.todayRouteId, required this.onRefresh});
|
||||
@override State<_DriverMainTab> createState() => _DriverMainTabState();
|
||||
}
|
||||
|
||||
class _DriverMainTabState extends State<_DriverMainTab> {
|
||||
List<ReporteModel> _ciudadanoReportes = [];
|
||||
|
||||
@override void initState() { super.initState(); _loadReportes(); }
|
||||
|
||||
Future<void> _loadReportes() async {
|
||||
if (widget.todayRouteId == null) return;
|
||||
final all = await DbHelper.getAllReportes();
|
||||
final filtered = all.where((r) => r.routeId == widget.todayRouteId).toList();
|
||||
if (mounted) setState(() => _ciudadanoReportes = filtered.take(5).toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final posIdx = widget.todayRouteId != null
|
||||
? widget.sim.getPositionIndex(widget.todayRouteId!) : 0;
|
||||
final gpsOk = widget.todayRouteId != null
|
||||
? widget.sim.isGpsActive(widget.todayRouteId!) : true;
|
||||
|
||||
return CustomScrollView(slivers:[
|
||||
SliverAppBar(pinned:true, backgroundColor:AppColors.moradoConductor, foregroundColor:Colors.white,
|
||||
bottom:PreferredSize(preferredSize:const Size.fromHeight(4),
|
||||
child:Container(height:4,color:AppColors.dorado)),
|
||||
title:Text('Conductor: ${widget.auth.currentUser?.nombre.split(' ').first ?? ''}',
|
||||
style:const TextStyle(fontSize:16,fontWeight:FontWeight.bold)),
|
||||
actions:[IconButton(icon:const Icon(Icons.logout),
|
||||
onPressed:()async{ await widget.auth.logout();
|
||||
if(context.mounted) Navigator.pushReplacementNamed(context,'/login');})]),
|
||||
|
||||
SliverPadding(padding:const EdgeInsets.all(14),sliver:SliverList(delegate:SliverChildListDelegate([
|
||||
// Ruta de hoy
|
||||
Card(color:AppColors.moradoConductor.withOpacity(0.08),
|
||||
shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12),
|
||||
side:BorderSide(color:AppColors.moradoConductor.withOpacity(0.3))),
|
||||
child:Padding(padding:const EdgeInsets.all(14),child:Column(
|
||||
crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||
Row(children:[
|
||||
const Icon(Icons.today,color:AppColors.moradoConductor),
|
||||
const SizedBox(width:8),
|
||||
Text('Hoy — ${_todayLabel()}',
|
||||
style:const TextStyle(fontWeight:FontWeight.bold,color:AppColors.moradoConductor,fontSize:15)),
|
||||
]),
|
||||
const Divider(),
|
||||
if (widget.route != null)...[
|
||||
Text(widget.route.name,style:const TextStyle(fontWeight:FontWeight.bold,fontSize:14)),
|
||||
Text('Camión ${widget.route.truckId} • Turno: ${widget.route.turno}',
|
||||
style:const TextStyle(color:AppColors.grisTexto,fontSize:12)),
|
||||
const SizedBox(height:8),
|
||||
Row(children:[
|
||||
Icon(gpsOk?Icons.gps_fixed:Icons.gps_off,
|
||||
color:gpsOk?AppColors.verdeExito:AppColors.rojoError,size:16),
|
||||
const SizedBox(width:4),
|
||||
Text(gpsOk?'GPS Activo':'⚠️ GPS Desactivado',
|
||||
style:TextStyle(color:gpsOk?AppColors.verdeExito:AppColors.rojoError,
|
||||
fontWeight:FontWeight.bold,fontSize:12)),
|
||||
const Spacer(),
|
||||
Text('Posición ${posIdx+1}/8',style:const TextStyle(color:AppColors.grisTexto,fontSize:12)),
|
||||
]),
|
||||
const SizedBox(height:8),
|
||||
LinearProgressIndicator(value:(posIdx+1)/8,
|
||||
backgroundColor:Colors.grey.shade300,
|
||||
valueColor:const AlwaysStoppedAnimation<Color>(AppColors.moradoConductor)),
|
||||
const SizedBox(height:6),
|
||||
Text(widget.sim.getEtaText(widget.todayRouteId??''),
|
||||
style:const TextStyle(fontSize:13,fontWeight:FontWeight.w500)),
|
||||
] else
|
||||
const Text('⚠️ Sin ruta asignada hoy.',style:TextStyle(color:AppColors.rojoError)),
|
||||
]))),
|
||||
const SizedBox(height:10),
|
||||
|
||||
// Instrucciones
|
||||
Card(child:Padding(padding:const EdgeInsets.all(12),child:Column(
|
||||
crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||
const Text('📋 Instrucciones de Ruta',
|
||||
style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.moradoConductor)),
|
||||
const Divider(),
|
||||
const Text('• Sigue la ruta asignada sin desviaciones\n'
|
||||
'• Mantén el GPS activo en todo momento\n'
|
||||
'• Reporta incidentes desde "Incidente"\n'
|
||||
'• Si hay problema, el admin decidirá si se cancela o retrasa',
|
||||
style:TextStyle(fontSize:12,color:AppColors.grisTexto)),
|
||||
]))),
|
||||
const SizedBox(height:10),
|
||||
|
||||
// Reportes ciudadanos de SU ruta
|
||||
if (_ciudadanoReportes.isNotEmpty) ...[
|
||||
Card(color:Colors.orange.shade50,
|
||||
shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(10),
|
||||
side:BorderSide(color:Colors.orange.shade200)),
|
||||
child:Padding(padding:const EdgeInsets.all(12),child:Column(
|
||||
crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||
const Row(children:[
|
||||
Icon(Icons.people,color:AppColors.naranjaAlerta,size:16),
|
||||
SizedBox(width:6),
|
||||
Text('Reportes de tu ruta hoy',
|
||||
style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.naranjaAlerta,fontSize:13)),
|
||||
]),
|
||||
const Divider(),
|
||||
..._ciudadanoReportes.map((r)=>Padding(
|
||||
padding:const EdgeInsets.symmetric(vertical:3),
|
||||
child:Row(children:[
|
||||
const Icon(Icons.person_outline,size:12,color:AppColors.grisTexto),
|
||||
const SizedBox(width:4),
|
||||
Expanded(child:Text(r.descripcion,style:const TextStyle(fontSize:11),
|
||||
maxLines:1,overflow:TextOverflow.ellipsis)),
|
||||
]))),
|
||||
]))),
|
||||
const SizedBox(height:10),
|
||||
],
|
||||
|
||||
// Horario LMV / MJS
|
||||
Card(child:Padding(padding:const EdgeInsets.all(12),child:Column(
|
||||
crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||
const Text('Mi Horario',
|
||||
style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.moradoConductor)),
|
||||
const Divider(),
|
||||
if (widget.assignments.isEmpty)
|
||||
const Text('Sin asignaciones. Contacta al administrador.',
|
||||
style:TextStyle(color:AppColors.grisTexto,fontSize:12))
|
||||
else ...[
|
||||
_scheduleGroup(widget.assignments,'LUNES','MIERCOLES','VIERNES',
|
||||
'Lunes, Miércoles y Viernes'),
|
||||
const SizedBox(height:8),
|
||||
_scheduleGroup(widget.assignments,'MARTES','JUEVES','SABADO',
|
||||
'Martes, Jueves y Sábado'),
|
||||
],
|
||||
]))),
|
||||
const SizedBox(height:80),
|
||||
]))),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _scheduleGroup(List<AssignmentModel> all, String d1, String d2, String d3, String label) {
|
||||
AssignmentModel? found;
|
||||
for (final dia in [d1,d2,d3]) {
|
||||
try { found = all.firstWhere((a)=>a.diaSemana==dia); break; } catch(_){}
|
||||
}
|
||||
return Container(padding:const EdgeInsets.all(10),
|
||||
decoration:BoxDecoration(color:AppColors.moradoConductor.withOpacity(0.06),
|
||||
borderRadius:BorderRadius.circular(8),
|
||||
border:Border.all(color:AppColors.moradoConductor.withOpacity(0.2))),
|
||||
child:Row(children:[
|
||||
const Icon(Icons.calendar_today,size:14,color:AppColors.moradoConductor),
|
||||
const SizedBox(width:6),
|
||||
Expanded(child:Text(label,style:const TextStyle(fontWeight:FontWeight.w600,fontSize:12))),
|
||||
if (found!=null)
|
||||
Container(padding:const EdgeInsets.symmetric(horizontal:8,vertical:3),
|
||||
decoration:BoxDecoration(color:AppColors.moradoConductor,borderRadius:BorderRadius.circular(8)),
|
||||
child:Text('${found.routeId} • ${found.turno}',
|
||||
style:const TextStyle(fontSize:11,color:Colors.white,fontWeight:FontWeight.bold)))
|
||||
else
|
||||
const Text('Sin asignar',style:TextStyle(fontSize:11,color:AppColors.grisTexto)),
|
||||
]));
|
||||
}
|
||||
|
||||
String _todayLabel() {
|
||||
const d=['','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado','Domingo'];
|
||||
return d[DateTime.now().weekday];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tab mapa ──────────────────────────────────────────────────────────────
|
||||
class _DriverMapTab extends StatelessWidget {
|
||||
final route; final sim;
|
||||
const _DriverMapTab({required this.route, required this.sim});
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar:AppBar(automaticallyImplyLeading:false,
|
||||
backgroundColor:AppColors.moradoConductor,foregroundColor:Colors.white,
|
||||
title:Text(route.name,style:const TextStyle(fontSize:13)),
|
||||
bottom:PreferredSize(preferredSize:const Size.fromHeight(4),
|
||||
child:Container(height:4,color:AppColors.dorado))),
|
||||
body:RouteMapWidget(route:route,simulator:sim,
|
||||
height:MediaQuery.of(context).size.height,showFullRoute:true));
|
||||
}
|
||||
|
||||
// ── Tab reporte incidente — usa routeId actual ────────────────────────────
|
||||
class _DriverReportesTab extends StatefulWidget {
|
||||
final int? conductorId;
|
||||
final String? todayRouteId; // Ruta actual del conductor
|
||||
const _DriverReportesTab({required this.conductorId, required this.todayRouteId});
|
||||
@override State<_DriverReportesTab> createState() => _DriverReportesTabState();
|
||||
}
|
||||
|
||||
class _DriverReportesTabState extends State<_DriverReportesTab> {
|
||||
String _tipo = 'INCIDENTE_LLANTA';
|
||||
final _desc = TextEditingController();
|
||||
bool _loading = false, _sent = false;
|
||||
List<AlertaModel> _misIncidentes = [];
|
||||
|
||||
static const _tipos = {
|
||||
'INCIDENTE_LLANTA': '🔧 Llanta ponchada',
|
||||
'INCIDENTE_MECANICA': '🔥 Falla mecánica',
|
||||
'INCIDENTE_ACCIDENTE': '🚑 Accidente',
|
||||
'INCIDENTE_CAMINO': '🚧 Camino bloqueado',
|
||||
'INCIDENTE_COMBUSTIBLE':'⛽ Sin combustible',
|
||||
'INCIDENTE_OTRO': '📝 Otro',
|
||||
};
|
||||
|
||||
@override void initState() { super.initState(); _load(); }
|
||||
|
||||
Future<void> _load() async {
|
||||
final all = await DbHelper.getAlertas();
|
||||
// Solo incidentes de la ruta actual del conductor
|
||||
final mine = all.where((a) =>
|
||||
a.tipo.startsWith('INCIDENTE_') &&
|
||||
a.routeId == (widget.todayRouteId ?? '')).toList();
|
||||
if (mounted) setState(() => _misIncidentes = mine);
|
||||
}
|
||||
|
||||
Future<void> _enviar() async {
|
||||
if (widget.todayRouteId == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:Text('No tienes ruta asignada hoy'),
|
||||
backgroundColor:AppColors.rojoError)); return;
|
||||
}
|
||||
if (_desc.text.trim().isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content:Text('Describe el incidente'),backgroundColor:AppColors.rojoError)); return;
|
||||
}
|
||||
setState(()=>_loading=true);
|
||||
// Guardar el incidente asociado a la RUTA ACTUAL
|
||||
await DbHelper.insertAlerta(AlertaModel(
|
||||
tipo: _tipo,
|
||||
routeId: widget.todayRouteId!, // ← ID de la ruta actual, no del conductor
|
||||
mensaje: '${_tipos[_tipo]}: ${_desc.text.trim()}',
|
||||
fecha: DateTime.now().toIso8601String(),
|
||||
));
|
||||
await _load();
|
||||
if (!mounted) return;
|
||||
setState(() { _loading=false; _sent=true; });
|
||||
_desc.clear();
|
||||
await Future.delayed(const Duration(seconds:2));
|
||||
if (mounted) setState(()=>_sent=false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
backgroundColor:AppColors.grisFondo,
|
||||
appBar:AppBar(automaticallyImplyLeading:false,
|
||||
backgroundColor:AppColors.moradoConductor,foregroundColor:Colors.white,
|
||||
title:const Text('Reportar Incidente'),
|
||||
bottom:PreferredSize(preferredSize:const Size.fromHeight(4),
|
||||
child:Container(height:4,color:AppColors.dorado))),
|
||||
body: _sent
|
||||
? const Center(child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[
|
||||
Icon(Icons.check_circle,color:AppColors.verdeExito,size:64),
|
||||
SizedBox(height:12),
|
||||
Text('¡Incidente reportado!',style:TextStyle(fontSize:18,fontWeight:FontWeight.bold,color:AppColors.verdeExito)),
|
||||
Text('El administrador será notificado.',style:TextStyle(color:AppColors.grisTexto)),
|
||||
]))
|
||||
: SingleChildScrollView(padding:const EdgeInsets.all(16),child:Column(children:[
|
||||
// Info ruta actual
|
||||
if (widget.todayRouteId != null)
|
||||
Container(margin:const EdgeInsets.only(bottom:12),
|
||||
padding:const EdgeInsets.all(10),
|
||||
decoration:BoxDecoration(color:AppColors.moradoConductor.withOpacity(0.08),
|
||||
borderRadius:BorderRadius.circular(8),
|
||||
border:Border.all(color:AppColors.moradoConductor.withOpacity(0.3))),
|
||||
child:Row(children:[
|
||||
const Icon(Icons.route,color:AppColors.moradoConductor,size:16),
|
||||
const SizedBox(width:6),
|
||||
Text('Incidente en: ${widget.todayRouteId}',
|
||||
style:const TextStyle(fontWeight:FontWeight.bold,color:AppColors.moradoConductor,fontSize:13)),
|
||||
]))
|
||||
else
|
||||
Container(margin:const EdgeInsets.only(bottom:12),
|
||||
padding:const EdgeInsets.all(10),
|
||||
decoration:BoxDecoration(color:Colors.orange.shade50,borderRadius:BorderRadius.circular(8)),
|
||||
child:const Text('⚠️ No tienes ruta asignada hoy',
|
||||
style:TextStyle(color:AppColors.naranjaAlerta,fontWeight:FontWeight.bold))),
|
||||
|
||||
Card(shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12)),
|
||||
child:Padding(padding:const EdgeInsets.all(16),child:Column(
|
||||
crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||
const Text('Tipo de incidente',
|
||||
style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.moradoConductor,fontSize:15)),
|
||||
const SizedBox(height:8),
|
||||
..._tipos.entries.map((e)=>RadioListTile<String>(dense:true,
|
||||
value:e.key,groupValue:_tipo,
|
||||
title:Text(e.value,style:const TextStyle(fontSize:13)),
|
||||
activeColor:AppColors.moradoConductor,
|
||||
onChanged:(v)=>setState(()=>_tipo=v!))),
|
||||
const SizedBox(height:10),
|
||||
const Text('Descripción',style:TextStyle(fontWeight:FontWeight.w600,fontSize:13)),
|
||||
const SizedBox(height:6),
|
||||
TextField(controller:_desc,maxLines:3,
|
||||
decoration:const InputDecoration(hintText:'Describe qué pasó...',
|
||||
border:OutlineInputBorder(),filled:true,fillColor:Colors.white)),
|
||||
const SizedBox(height:12),
|
||||
Container(padding:const EdgeInsets.all(10),
|
||||
decoration:BoxDecoration(color:Colors.orange.shade50,
|
||||
borderRadius:BorderRadius.circular(8),
|
||||
border:Border.all(color:Colors.orange.shade200)),
|
||||
child:const Text(
|
||||
'⚠️ El administrador verá este incidente en tu ruta actual '
|
||||
'y decidirá si continúa, se retrasa o se cancela.',
|
||||
style:TextStyle(fontSize:11,color:Colors.black87))),
|
||||
const SizedBox(height:14),
|
||||
SizedBox(width:double.infinity,height:48,
|
||||
child:ElevatedButton.icon(
|
||||
onPressed:(_loading||widget.todayRouteId==null)?null:_enviar,
|
||||
style:ElevatedButton.styleFrom(backgroundColor:AppColors.moradoConductor,
|
||||
foregroundColor:Colors.white,
|
||||
shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(8))),
|
||||
icon:_loading?const SizedBox(width:18,height:18,
|
||||
child:CircularProgressIndicator(color:Colors.white,strokeWidth:2))
|
||||
:const Icon(Icons.send),
|
||||
label:const Text('ENVIAR INCIDENTE',style:TextStyle(fontWeight:FontWeight.bold)))),
|
||||
]))),
|
||||
|
||||
if (_misIncidentes.isNotEmpty)...[
|
||||
const SizedBox(height:16),
|
||||
const Align(alignment:Alignment.centerLeft,
|
||||
child:Text('Mis incidentes de hoy',
|
||||
style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.moradoConductor,fontSize:14))),
|
||||
const SizedBox(height:8),
|
||||
..._misIncidentes.take(5).map((a)=>Card(margin:const EdgeInsets.only(bottom:6),
|
||||
child:ListTile(dense:true,
|
||||
leading:CircleAvatar(backgroundColor:AppColors.moradoConductor,radius:16,
|
||||
child:const Icon(Icons.warning,color:Colors.white,size:14)),
|
||||
title:Text(_tipos[a.tipo]??a.tipo,
|
||||
style:const TextStyle(fontSize:12,fontWeight:FontWeight.w600)),
|
||||
subtitle:Text(a.mensaje,maxLines:1,overflow:TextOverflow.ellipsis,
|
||||
style:const TextStyle(fontSize:11)),
|
||||
trailing:Icon(a.resuelta?Icons.check_circle:Icons.pending,
|
||||
color:a.resuelta?AppColors.verdeExito:AppColors.naranjaAlerta,size:18)))),
|
||||
],
|
||||
])),
|
||||
);
|
||||
|
||||
@override void dispose(){ _desc.dispose(); super.dispose(); }
|
||||
}
|
||||
|
||||
// ── Notif banner conductor ────────────────────────────────────────────────
|
||||
class _NotifBanner extends StatelessWidget {
|
||||
final AppNotification notif; final VoidCallback onDismiss;
|
||||
const _NotifBanner({required this.notif, required this.onDismiss});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = notif.event==NotifEvent.gpsLost?Colors.red.shade800
|
||||
:notif.event==NotifEvent.truckStopped?AppColors.naranjaAlerta
|
||||
:notif.event==NotifEvent.routeCancelled?AppColors.rojoError
|
||||
:AppColors.moradoConductor;
|
||||
return Material(color:Colors.transparent,
|
||||
child:Container(margin:const EdgeInsets.all(10),
|
||||
decoration:BoxDecoration(color:color,borderRadius:BorderRadius.circular(12),
|
||||
boxShadow:const[BoxShadow(color:Colors.black26,blurRadius:6)]),
|
||||
child:Padding(padding:const EdgeInsets.all(12),child:Row(children:[
|
||||
const Icon(Icons.notification_important,color:Colors.white,size:22),
|
||||
const SizedBox(width:8),
|
||||
Expanded(child:Column(crossAxisAlignment:CrossAxisAlignment.start,
|
||||
mainAxisSize:MainAxisSize.min,children:[
|
||||
Text(notif.title,style:const TextStyle(color:Colors.white,fontWeight:FontWeight.bold,fontSize:13)),
|
||||
Text(notif.body,style:const TextStyle(color:Colors.white70,fontSize:11),
|
||||
maxLines:2,overflow:TextOverflow.ellipsis),
|
||||
])),
|
||||
IconButton(icon:const Icon(Icons.close,color:Colors.white,size:18),onPressed:onDismiss),
|
||||
]))));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user