import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../core/app_colors.dart'; import '../../database/db_helper.dart'; import '../../models/models.dart'; import '../../services/auth_service.dart'; import '../../services/route_simulator_service.dart'; import '../../data/routes_data.dart'; import '../../widgets/route_map_widget.dart'; import 'citizen_guia_screen.dart'; import 'citizen_reporte_screen.dart'; import 'add_domicilio_screen.dart'; import 'review_screen.dart'; class CitizenHomeScreen extends StatefulWidget { const CitizenHomeScreen({super.key}); @override State createState() => _CitizenHomeScreenState(); } class _CitizenHomeScreenState extends State { int _tab = 0; @override Widget build(BuildContext context) { final auth = context.watch(); final sim = context.watch(); final dom = auth.primaryDomicilio; final last = dom != null ? sim.getNotificationForRoute(dom.routeId) : null; final tabs = [ _HomeTab(auth: auth, sim: sim), const CitizenGuiaScreen(), const CitizenReporteScreen(), ]; return Scaffold( backgroundColor: AppColors.grisFondo, body: Stack(children: [ tabs[_tab], if (last != null) Positioned( top: MediaQuery.of(context).padding.top + 8, left: 0, right: 0, child: _NotifBanner(notif: last, onDismiss: () => sim.dismissRouteNotification(dom?.routeId ?? '')), ), ]), bottomNavigationBar: NavigationBar( selectedIndex: _tab, onDestinationSelected: (i) => setState(() => _tab = i), backgroundColor: Colors.white, indicatorColor: AppColors.guindaPrimary.withOpacity(0.15), destinations: const [ NavigationDestination(icon:Icon(Icons.home_outlined), selectedIcon:Icon(Icons.home,color:AppColors.guindaPrimary),label:'Inicio'), NavigationDestination(icon:Icon(Icons.eco_outlined), selectedIcon:Icon(Icons.eco,color:AppColors.guindaPrimary),label:'Guía'), NavigationDestination(icon:Icon(Icons.report_outlined), selectedIcon:Icon(Icons.report,color:AppColors.guindaPrimary),label:'Reportar'), ], ), ); } } class _HomeTab extends StatefulWidget { final AuthService auth; final RouteSimulatorService sim; const _HomeTab({required this.auth, required this.sim}); @override State<_HomeTab> createState() => _HomeTabState(); } class _HomeTabState extends State<_HomeTab> { RouteStatusModel? _routeStatus; RouteDefinitionModel? _routeDef; @override void initState() { super.initState(); _loadStatus(); } Future _loadStatus() async { final dom = widget.auth.primaryDomicilio; if (dom == null) return; final s = await DbHelper.getRouteStatus(dom.routeId); final rd = await DbHelper.getRouteDefinitionById(dom.routeId); if (mounted) setState(() { _routeStatus = s; _routeDef = rd; }); } bool get _isRouteProblematic { final s = _routeStatus?.status ?? RouteStatus.enRuta; return s == RouteStatus.cancelada || s == RouteStatus.fallaMecanica || s == RouteStatus.retrasada; } @override Widget build(BuildContext context) { final dom = widget.auth.primaryDomicilio; final allDoms = widget.auth.allDomicilios; final routeId = dom?.routeId ?? ''; final route = dom != null ? getRouteById(dom.routeId) : null; final isTruckClose = widget.sim.isTruckClose(routeId); final isCompleted = widget.sim.isRouteCompleted(routeId); final needsReview = widget.sim.needsReviewPrompt(routeId); return RefreshIndicator( onRefresh: _loadStatus, child: CustomScrollView(slivers: [ SliverAppBar(expandedHeight: 120, pinned: true, backgroundColor: AppColors.guindaPrimary, bottom: PreferredSize(preferredSize: const Size.fromHeight(4), child: Container(height: 4, color: AppColors.dorado)), flexibleSpace: FlexibleSpaceBar(background: Container( color: AppColors.guindaPrimary, padding: const EdgeInsets.fromLTRB(20, 50, 20, 16), child: Row(children: [ const Icon(Icons.delete_sweep_rounded, color: AppColors.dorado, size: 30), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Hola, ${widget.auth.currentUser?.nombre.split(' ').first ?? ''}', style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), const Text('Celaya Limpia', style: TextStyle(color: AppColors.dorado, fontSize: 12)), ])), IconButton(icon: const Icon(Icons.logout, color: Colors.white70), onPressed: () async { await widget.auth.logout(); if (context.mounted) Navigator.pushReplacementNamed(context, '/login'); }), ]), )), ), SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList(delegate: SliverChildListDelegate([ // ── Selector de domicilio ──────────────────────────────────── if (allDoms.length > 1) _DomicilioSelector( auth: widget.auth, onChanged: _loadStatus), // ── Prompt de calificación ─────────────────────────────────── if (needsReview && dom != null) _ReviewPromptCard(routeId: routeId, colonia: dom.colonia, sim: widget.sim), // ── Estado de ruta (cancelada/falla/retrasada) ─────────────── if (_isRouteProblematic) _RouteStatusBanner(status: _routeStatus!) else ...[ // ETA Card _EtaCard(sim: widget.sim, routeId: routeId, dom: dom, route: route), const SizedBox(height: 12), // Información detallada de la ruta (días y horario) if (_routeDef != null) _RouteInfoCard(routeDef: _routeDef!), if (_routeDef == null && dom != null) _BasicRouteInfo(dom: dom), const SizedBox(height: 12), // Mapa solo cuando camión está cerca (<15 min) if (isTruckClose && route != null && !isCompleted) ...[ _WarningNoPursue(), const SizedBox(height: 8), RouteMapWidget(route: route, simulator: widget.sim, height: 220), const SizedBox(height: 12), ], ], // Aviso privacidad _PrivacyBanner(), const SizedBox(height: 12), // Mis domicilios _DomiciliosCard(auth: widget.auth), const SizedBox(height: 12), // Historial notificaciones if (widget.sim.historyForRoute(routeId).isNotEmpty) _HistorialCard(sim: widget.sim, routeId: routeId), const SizedBox(height: 80), ])), ), ]), ); } } // ── Selector de domicilio activo ────────────────────────────────────────── class _DomicilioSelector extends StatelessWidget { final AuthService auth; final VoidCallback onChanged; const _DomicilioSelector({required this.auth, required this.onChanged}); @override Widget build(BuildContext context) { return Container(margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10), border: Border.all(color: AppColors.guindaPrimary.withOpacity(0.3)), boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 4)]), child: DropdownButtonHideUnderline( child: DropdownButton( isExpanded: true, value: auth.primaryDomicilio?.id, icon: const Icon(Icons.swap_horiz, color: AppColors.guindaPrimary), items: auth.allDomicilios.map((d) => DropdownMenuItem( value: d.id, child: Row(children: [ Icon(d.isPrimary ? Icons.home : Icons.location_on_outlined, color: AppColors.guindaPrimary, size: 16), const SizedBox(width: 6), Expanded(child: Text('${d.alias} — ${d.colonia}', style: const TextStyle(fontSize: 13), overflow: TextOverflow.ellipsis)), ]))).toList(), onChanged: (id) async { if (id != null) { await DbHelper.setPrimaryDomicilio(id, auth.currentUser!.id!); await auth.reloadDomicilios(); onChanged(); } }, ), )); } } // ── Prompt de reseña ────────────────────────────────────────────────────── class _ReviewPromptCard extends StatelessWidget { final String routeId, colonia; final RouteSimulatorService sim; const _ReviewPromptCard({required this.routeId, required this.colonia, required this.sim}); @override Widget build(BuildContext context) => Card( color: Colors.amber.shade50, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12), side: BorderSide(color: Colors.amber.shade300, width: 1.5)), child: Padding(padding: const EdgeInsets.all(14), child: Column(children: [ const Row(children: [ Text('⭐', style: TextStyle(fontSize: 24)), SizedBox(width: 8), Expanded(child: Text('¿Cómo estuvo el servicio de hoy?', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14))), ]), const SizedBox(height: 4), const Text('El camión pasó por tu colonia. Toma un momento para calificar el servicio.', style: TextStyle(fontSize: 12, color: AppColors.grisTexto)), const SizedBox(height: 10), Row(children: [ Expanded(child: ElevatedButton.icon( onPressed: () => Navigator.push(context, MaterialPageRoute( builder: (_) => ReviewScreen(routeId: routeId, colonia: colonia))), style: ElevatedButton.styleFrom(backgroundColor: Colors.amber, foregroundColor: Colors.black87), icon: const Icon(Icons.star, size: 16), label: const Text('Calificar', style: TextStyle(fontWeight: FontWeight.bold)))), const SizedBox(width: 8), TextButton(onPressed: () => sim.clearReviewPrompt(routeId), child: const Text('Después', style: TextStyle(color: AppColors.grisTexto))), ]), ]))); } // ── Info detallada de la ruta ───────────────────────────────────────────── class _RouteInfoCard extends StatelessWidget { final RouteDefinitionModel routeDef; const _RouteInfoCard({required this.routeDef}); @override Widget build(BuildContext context) => Card( child: Padding(padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row(children: [ Icon(Icons.schedule, color: AppColors.guindaPrimary, size: 16), SizedBox(width: 6), Text('Información de tu ruta', style: TextStyle( fontWeight: FontWeight.bold, color: AppColors.guindaPrimary)), ]), const Divider(), Text(routeDef.nombre, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)), const SizedBox(height: 4), Row(children: [ const Icon(Icons.access_time, size: 13, color: AppColors.grisTexto), const SizedBox(width: 4), Text('${routeDef.horaInicio} — ${routeDef.horaFin} (${_turnoLabel(routeDef.turno)})', style: const TextStyle(fontSize: 12, color: AppColors.negroTexto)), ]), const SizedBox(height: 4), Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.calendar_today, size: 13, color: AppColors.grisTexto), const SizedBox(width: 4), Expanded(child: Text( routeDef.dias.map(AppDias.label).join(', '), style: const TextStyle(fontSize: 12, color: AppColors.negroTexto))), ]), ]))); String _turnoLabel(String t) => t=='MATUTINO'?'🌄 Matutino':t=='VESPERTINO'?'🌅 Vespertino':'🌙 Nocturno'; } class _BasicRouteInfo extends StatelessWidget { final DomicilioModel dom; const _BasicRouteInfo({required this.dom}); @override Widget build(BuildContext context) => Card( child: Padding(padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Row(children: [ Icon(Icons.schedule, color: AppColors.guindaPrimary, size: 16), SizedBox(width: 6), Text('Tu servicio de recolección', style: TextStyle( fontWeight: FontWeight.bold, color: AppColors.guindaPrimary)), ]), const Divider(), Text('Ruta: ${dom.routeId}', style: const TextStyle(fontWeight: FontWeight.w600)), Text('Horario: ${dom.horarioEstimado}', style: const TextStyle(color: AppColors.grisTexto, fontSize: 12)), ]))); } // ── Aviso anti-persecución ──────────────────────────────────────────────── class _WarningNoPursue extends StatelessWidget { @override Widget build(BuildContext context) => Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.red.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.shade300)), child: const Row(children: [ Icon(Icons.warning_amber_rounded, color: AppColors.rojoError, size: 20), SizedBox(width: 8), Expanded(child: Text( '⚠️ Ya es momento de sacar tu basura.\n' '🚫 NO persigas ni interceptes el camión en movimiento.\n' '✅ Coloca tus bolsas en la acera y espera.', style: TextStyle(fontSize: 11, color: AppColors.rojoError, fontWeight: FontWeight.w500))), ])); } // ── Mis domicilios ──────────────────────────────────────────────────────── class _DomiciliosCard extends StatelessWidget { final AuthService auth; const _DomiciliosCard({required this.auth}); @override Widget build(BuildContext context) { final doms = auth.allDomicilios; return Card(child: Padding(padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ const Icon(Icons.home_outlined, color: AppColors.guindaPrimary, size: 16), const SizedBox(width: 6), const Expanded(child: Text('Mis Domicilios', style: TextStyle(fontWeight: FontWeight.bold, color: AppColors.guindaPrimary))), TextButton.icon( onPressed: () async { final result = await Navigator.push(context, MaterialPageRoute(builder: (_) => const AddDomicilioScreen())); if (result == true) await auth.reloadDomicilios(); }, icon: const Icon(Icons.add, size: 14), label: const Text('Agregar', style: TextStyle(fontSize: 12)), style: TextButton.styleFrom(foregroundColor: AppColors.guindaPrimary)), ]), const Divider(), if (doms.isEmpty) const Text('Sin domicilios registrados', style: TextStyle(color: AppColors.grisTexto, fontSize: 12)) else ...doms.map((d) => Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row(children: [ Icon(d.isPrimary ? Icons.home : Icons.location_on_outlined, color: d.isPrimary ? AppColors.guindaPrimary : AppColors.grisTexto, size: 16), const SizedBox(width: 8), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('${d.alias} — ${d.colonia}', style: TextStyle(fontWeight: d.isPrimary ? FontWeight.bold : FontWeight.normal, fontSize: 12)), Text(d.calle, style: const TextStyle(color: AppColors.grisTexto, fontSize: 11)), Text('${d.routeId} • ${d.horarioEstimado}', style: const TextStyle(color: AppColors.grisTexto, fontSize: 10)), ])), if (!d.isPrimary) IconButton(icon: const Icon(Icons.star_border, size: 16, color: AppColors.dorado), tooltip: 'Hacer principal', onPressed: () async { await DbHelper.setPrimaryDomicilio(d.id!, auth.currentUser!.id!); await auth.reloadDomicilios(); }), IconButton(icon: const Icon(Icons.edit_outlined, size: 14, color: AppColors.grisTexto), onPressed: () async { final result = await Navigator.push(context, MaterialPageRoute( builder: (_) => AddDomicilioScreen(editing: d))); if (result == true) await auth.reloadDomicilios(); }), if (!d.isPrimary) IconButton(icon: const Icon(Icons.delete_outline, size: 14, color: AppColors.rojoError), onPressed: () async { await DbHelper.deleteDomicilio(d.id!); await auth.reloadDomicilios(); }), ]))), ]))); } } // ── Banner de ruta con problema ─────────────────────────────────────────── class _RouteStatusBanner extends StatelessWidget { final RouteStatusModel status; const _RouteStatusBanner({required this.status}); @override Widget build(BuildContext context) { final isCancelled = status.status == RouteStatus.cancelada; final isFalla = status.status == RouteStatus.fallaMecanica; final isRetrasada = status.status == RouteStatus.retrasada; final color = isCancelled ? AppColors.rojoError : isFalla ? Colors.red.shade800 : AppColors.naranjaAlerta; final icon = isCancelled ? Icons.cancel : isFalla ? Icons.build : Icons.access_time; final titulo = isCancelled ? '❌ Ruta Cancelada Hoy' : isFalla ? '🔧 Falla Mecánica en Servicio' : '⏱️ Servicio con Retraso'; return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Container(width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(12)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(icon, color: Colors.white, size: 26), const SizedBox(width: 10), Expanded(child: Text(titulo, style: const TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.bold))), ]), const SizedBox(height: 8), Text(isCancelled ? 'El servicio no se realizará hoy. Guarda tus residuos para mañana.' : isFalla ? 'El camión presentó una falla. El Ayuntamiento atiende la situación.' : 'El camión presenta un retraso. El servicio se realizará con demora.', style: const TextStyle(color: Colors.white, fontSize: 13)), ])), if (status.mensaje != null && status.mensaje!.isNotEmpty) ...[ const SizedBox(height: 10), Container(width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withOpacity(0.4))), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.admin_panel_settings, color: color, size: 16), const SizedBox(width: 6), Text('Mensaje del Ayuntamiento', style: TextStyle( fontWeight: FontWeight.bold, color: color, fontSize: 13)), ]), const SizedBox(height: 6), Text(status.mensaje!, style: const TextStyle(fontSize: 13)), ])), ], const SizedBox(height: 10), Container(padding: const EdgeInsets.all(12), decoration: BoxDecoration(color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade300)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('💡 Recomendaciones:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12, color: AppColors.grisTexto)), const SizedBox(height: 4), Text(isCancelled ? '• Guarda tus bolsas en lugar cerrado\n• No dejes residuos en la acera\n• Revisa la app mañana' : isRetrasada ? '• Espera el aviso de 15 minutos antes de sacar tu basura\n• El camión llegará eventualmente\n• Recibe la notificación en esta app' : '• Espera confirmación del Ayuntamiento\n• Puede enviarse unidad de reemplazo', style: const TextStyle(fontSize: 12, color: AppColors.grisTexto)), ])), const SizedBox(height: 12), ]); } } // ── ETA Card ────────────────────────────────────────────────────────────── class _EtaCard extends StatelessWidget { final RouteSimulatorService sim; final String routeId; final dom; final route; const _EtaCard({required this.sim, required this.routeId, required this.dom, required this.route}); @override Widget build(BuildContext context) => Container( decoration: BoxDecoration( gradient: const LinearGradient(colors:[AppColors.guindaPrimary,AppColors.guindaDark], begin:Alignment.topLeft,end:Alignment.bottomRight), borderRadius: BorderRadius.circular(14), boxShadow: [BoxShadow(color:AppColors.guindaDark.withOpacity(0.4),blurRadius:8,offset:const Offset(0,4))]), padding: const EdgeInsets.all(18), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children:[ const Icon(Icons.local_shipping,color:AppColors.dorado,size:22), const SizedBox(width:8), Expanded(child:Text(route?.name??dom?.routeId??'Tu ruta', style:const TextStyle(color:AppColors.dorado,fontSize:13,fontWeight:FontWeight.w600))), ]), const SizedBox(height:8), Text(sim.getEtaText(routeId), style:const TextStyle(color:Colors.white,fontSize:16,fontWeight:FontWeight.bold)), const SizedBox(height:6), if (dom!=null) Text('⏰ ${dom.horarioEstimado}', style:const TextStyle(color:Colors.white60,fontSize:11)), const SizedBox(height:10), LinearProgressIndicator( value:route!=null?(sim.getPositionIndex(routeId)+1)/route.positions.length:0, backgroundColor:Colors.white24, valueColor:const AlwaysStoppedAnimation(AppColors.dorado)), ])); } // ── Privacidad ──────────────────────────────────────────────────────────── class _PrivacyBanner extends StatelessWidget { @override Widget build(BuildContext context) => Container( padding:const EdgeInsets.all(10), decoration:BoxDecoration(color:Colors.amber.shade50,borderRadius:BorderRadius.circular(8), border:Border.all(color:Colors.amber.shade300)), child:const Row(children:[ Icon(Icons.shield_outlined,color:Colors.amber,size:18), SizedBox(width:6), Expanded(child:Text('🔒 Solo ves la información de tu ruta asignada.', style:TextStyle(fontSize:11,color:Colors.black87))), ])); } // ── Historial notificaciones ────────────────────────────────────────────── class _HistorialCard extends StatelessWidget { final RouteSimulatorService sim; final String routeId; const _HistorialCard({required this.sim, required this.routeId}); @override Widget build(BuildContext context) { final notifs = sim.historyForRoute(routeId).take(5).toList(); return Card(child:Padding(padding:const EdgeInsets.all(14),child:Column( crossAxisAlignment:CrossAxisAlignment.start, children:[ const Text('Alertas recientes',style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.guindaPrimary)), const Divider(), ...notifs.map((n){ final color = n.event==NotifEvent.truckProximity||n.event==NotifEvent.truckApproaching15min ?AppColors.naranjaAlerta:n.event==NotifEvent.routeCompleted||n.event==NotifEvent.reviewPrompt ?AppColors.verdeExito:n.event==NotifEvent.routeCancelled?AppColors.rojoError:AppColors.azulInfo; return Padding(padding:const EdgeInsets.symmetric(vertical:3), child:Row(children:[ Icon(Icons.circle,size:8,color:color), const SizedBox(width:8), Expanded(child:Text(n.title,style:const TextStyle(fontSize:12,fontWeight:FontWeight.w500))), Text('${n.timestamp.hour.toString().padLeft(2,'0')}:${n.timestamp.minute.toString().padLeft(2,'0')}', style:const TextStyle(fontSize:10,color:AppColors.grisTexto)), ])); }), ]))); } } // ── Notif Banner ────────────────────────────────────────────────────────── class _NotifBanner extends StatelessWidget { final AppNotification notif; final VoidCallback onDismiss; const _NotifBanner({required this.notif, required this.onDismiss}); @override Widget build(BuildContext context) { final isUrgent = notif.event==NotifEvent.truckProximity||notif.event==NotifEvent.truckApproaching15min; final isReview = notif.event==NotifEvent.reviewPrompt; final color = isUrgent?AppColors.naranjaAlerta :isReview?Colors.amber.shade700 :notif.event==NotifEvent.routeCancelled?AppColors.rojoError :notif.event==NotifEvent.gpsLost?Colors.red.shade800 :AppColors.azulInfo; return Material(color:Colors.transparent, child:Container(margin:const EdgeInsets.all(12), decoration:BoxDecoration(color:color,borderRadius:BorderRadius.circular(12), boxShadow:const[BoxShadow(color:Colors.black26,blurRadius:8,offset:Offset(0,4))]), child:Padding(padding:const EdgeInsets.all(12),child:Row(children:[ Icon(isReview?Icons.star:Icons.notifications_active,color:Colors.white,size:24), const SizedBox(width:10), 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), ])))); } }