import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../core/app_colors.dart'; import '../../database/db_helper.dart'; import '../../services/auth_service.dart'; class ReporteChatScreen extends StatefulWidget { final int reporteId; final String folio; final bool isClosed; const ReporteChatScreen({super.key, required this.reporteId, required this.folio, this.isClosed = false}); @override State createState() => _ReporteChatScreenState(); } class _ReporteChatScreenState extends State { final _ctrl = TextEditingController(); final _scroll = ScrollController(); List> _msgs = []; bool _loading = true; Timer? _timer; String? _myRol; int? _myUserId; @override void initState() { super.initState(); final auth = context.read(); _myRol = auth.currentUser?.rol ?? 'CIUDADANO'; _myUserId = auth.currentUser?.id; _load(); _timer = Timer.periodic(const Duration(seconds: 4), (_) => _load()); } Future _load() async { // Cargar TODOS los mensajes del reporte sin filtro de rol final msgs = await DbHelper.getChatMsgs(widget.reporteId); // Marcar como leídos los que NO son míos if (_myRol != null) { try { await DbHelper.markChatRead(widget.reporteId, _myRol!); } catch (_) {} } if (mounted) { setState(() { _msgs = msgs; _loading = false; }); _scrollToBottom(); } } void _scrollToBottom() { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scroll.hasClients) { _scroll.animateTo(_scroll.position.maxScrollExtent, duration: const Duration(milliseconds: 200), curve: Curves.easeOut); } }); } Future _send() async { final text = _ctrl.text.trim(); if (text.isEmpty || _myRol == null || _myUserId == null) return; _ctrl.clear(); await DbHelper.insertChatMsg(widget.reporteId, _myUserId!, _myRol!, text); await _load(); } bool _isMe(Map msg) { // Un mensaje es mío si fue enviado con el mismo rol (admin ve sus msgs, ciudadano ve los suyos) final msgRol = msg['rol'] as String? ?? ''; final msgUserId = msg['user_id'] as int?; // Comparar por user_id si disponible, sino por rol if (_myUserId != null && msgUserId != null) return msgUserId == _myUserId; return msgRol == _myRol; } @override Widget build(BuildContext context) { final isAdmin = _myRol == 'ADMINISTRADOR'; return Scaffold( backgroundColor: AppColors.grisFondo, appBar: AppBar( backgroundColor: AppColors.guindaPrimary, foregroundColor: Colors.white, title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Chat del Reporte', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold)), Text('Folio: ${widget.folio}', style: const TextStyle(fontSize: 11, color: Colors.white70)), ]), bottom: PreferredSize(preferredSize: const Size.fromHeight(4), child: Container(height: 4, color: AppColors.dorado)), actions: [ if (widget.isClosed) Container(margin: const EdgeInsets.only(right: 12), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration(color: AppColors.verdeExito, borderRadius: BorderRadius.circular(12)), child: const Text('COMPLETADO', style: TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold))), IconButton(icon: const Icon(Icons.refresh), onPressed: _load), ], ), body: Column(children: [ if (widget.isClosed) Container(width: double.infinity, padding: const EdgeInsets.all(10), color: AppColors.verdeExito.withOpacity(0.08), child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.lock, size: 14, color: AppColors.verdeExito), SizedBox(width: 6), Text('Reporte completado — Chat cerrado', style: TextStyle(fontSize: 12, color: AppColors.verdeExito, fontWeight: FontWeight.w600)), ])), // Mensajes Expanded(child: _loading ? const Center(child: CircularProgressIndicator( color: AppColors.guindaPrimary)) : _msgs.isEmpty ? Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.chat_bubble_outline, size: 48, color: AppColors.grisTexto), const SizedBox(height: 12), Text(isAdmin ? 'Sin mensajes aún. Inicia la conversación.' : 'Escribe tu mensaje al Dir. Gral. Servicios Municipales.', style: const TextStyle(color: AppColors.grisTexto), textAlign: TextAlign.center), ])) : ListView.builder( controller: _scroll, padding: const EdgeInsets.fromLTRB(12, 12, 12, 4), itemCount: _msgs.length, itemBuilder: (_, i) { final m = _msgs[i]; final me = _isMe(m); final rol = m['rol'] as String? ?? ''; final fecha = DateTime.tryParse(m['fecha'] as String? ?? ''); final hora = fecha != null ? '${fecha.hour.toString().padLeft(2,'0')}:${fecha.minute.toString().padLeft(2,'0')}' : ''; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: me ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ if (!me) ...[ CircleAvatar(radius: 16, backgroundColor: AppColors.guindaPrimary.withOpacity(0.15), child: Icon( rol == 'ADMINISTRADOR' ? Icons.admin_panel_settings : Icons.person, size: 15, color: AppColors.guindaPrimary)), const SizedBox(width: 6), ], Flexible(child: Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.72), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: me ? AppColors.guindaPrimary : Colors.white, borderRadius: BorderRadius.only( topLeft: const Radius.circular(18), topRight: const Radius.circular(18), bottomLeft: Radius.circular(me ? 18 : 4), bottomRight: Radius.circular(me ? 4 : 18), ), boxShadow: [BoxShadow( color: Colors.black.withOpacity(0.07), blurRadius: 4, offset: const Offset(0, 2))], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (!me) ...[ Text( rol == 'ADMINISTRADOR' ? 'Dir. Gral. Servicios Municipales' : 'Ciudadano', style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: AppColors.guindaPrimary)), const SizedBox(height: 2), ], Text(m['mensaje'] as String? ?? '', style: TextStyle(fontSize: 13, height: 1.4, color: me ? Colors.white : AppColors.negroTexto)), const SizedBox(height: 2), Align(alignment: Alignment.bottomRight, child: Text(hora, style: TextStyle(fontSize: 9, color: me ? Colors.white54 : AppColors.grisTexto))), ], ), )), if (me) const SizedBox(width: 6), ], ), ); })), // Input if (!widget.isClosed) Container( padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), decoration: BoxDecoration( color: Colors.white, boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.08), blurRadius: 6, offset: const Offset(0, -2))]), child: SafeArea(top: false, child: Row(children: [ Expanded(child: TextField( controller: _ctrl, maxLines: 4, minLines: 1, textCapitalization: TextCapitalization.sentences, decoration: InputDecoration( hintText: isAdmin ? 'Responde al ciudadano...' : 'Escribe al Dir. Gral. Servicios Municipales...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(24), borderSide: BorderSide.none), filled: true, fillColor: AppColors.grisFondo, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), isDense: true, ), onSubmitted: (_) => _send(), )), const SizedBox(width: 8), CircleAvatar( radius: 24, backgroundColor: AppColors.guindaPrimary, child: IconButton( icon: const Icon(Icons.send, color: Colors.white, size: 20), onPressed: _send)), ]))) else Container( padding: const EdgeInsets.all(14), color: Colors.white, child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.lock_outline, size: 16, color: AppColors.grisTexto), SizedBox(width: 6), Text('Chat cerrado — Reporte completado', style: TextStyle(color: AppColors.grisTexto, fontSize: 12)), ])), ]), ); } @override void dispose() { _ctrl.dispose(); _scroll.dispose(); _timer?.cancel(); super.dispose(); } }