import 'dart:async'; import 'package:flutter/material.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; import '../modelos/partida.dart'; import '../servicios/servicio_nearby.dart'; import '../tema/tema_app.dart'; class PantallaGestorHost extends StatefulWidget { final VoidCallback onPartidaFin; const PantallaGestorHost({super.key, required this.onPartidaFin}); @override State createState() => _PantallaGestorHostState(); } class _PantallaGestorHostState extends State { Timer? _timer; int _segundosRestantes = 0; final Map _clientesListos = {}; final Map _votosRecibidos = {}; @override void initState() { super.initState(); _iniciarTemporizador(); _registrarListeners(); } void _iniciarTemporizador() { final estado = context.read(); final tiempo = estado.partida?.config.tiempoDebateSegundos; if (tiempo != null) { _segundosRestantes = tiempo; _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_segundosRestantes > 0) { setState(() => _segundosRestantes--); } else { timer.cancel(); } }); } } void _registrarListeners() { final nearby = context.read(); nearby.onMensaje((endpointId, mensaje) { if (mensaje.tipo == TipoMensaje.listo) { setState(() => _clientesListos[endpointId] = true); } else if (mensaje.tipo == TipoMensaje.voto) { final votanteId = mensaje.datos['votanteId'] as String?; final votoId = mensaje.datos['votoporId'] as String?; if (votanteId != null && votoId != null) { setState(() => _votosRecibidos[votanteId] = votoId); } } }); } @override void dispose() { _timer?.cancel(); super.dispose(); } String _formatearTiempo(int segundos) { final min = segundos ~/ 60; final seg = segundos % 60; return '${min.toString().padLeft(2, '0')}:${seg.toString().padLeft(2, '0')}'; } void _avanzarAFase(FaseJuego fase) { final estado = context.read(); final nearby = context.read(); switch (fase) { case FaseJuego.debate: estado.iniciarDebate(); nearby.enviarCambioFase('debate'); _iniciarTemporizador(); break; case FaseJuego.votacion: estado.iniciarVotacion(); nearby.enviarCambioFase('votacion'); _votosRecibidos.clear(); break; case FaseJuego.resultado: final resultado = estado.procesarVotacion(); if (resultado != null) { nearby.enviarResultadoVotacion({ 'eliminadoId': resultado.eliminadoId, 'eliminadoNombre': resultado.eliminadoNombre, 'eraImpostor': resultado.eraImpostor, 'votos': resultado.votos, }); } break; default: break; } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final estado = context.watch(); final nearby = context.watch(); final partida = estado.partida; if (partida == null) { return Scaffold( appBar: AppBar(title: Text(l10n.hostGame)), body: const Center(child: Text('Error: Sin partida')), ); } final numJugadores = partida.jugadores.length + 1; final todosListos = _clientesListos.length >= numJugadores - 1; final todosVotaron = _votosRecibidos.length >= numJugadores - 1; return Scaffold( appBar: AppBar( title: Text(l10n.hostGame), automaticallyImplyLeading: false, actions: [ IconButton( icon: const Icon(Icons.close), onPressed: () async { await nearby.desconectar(); widget.onPartidaFin(); }, ), ], ), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ _buildFaseIndicator(context, partida.fase, l10n), const SizedBox(height: 16), Expanded( child: _buildContenidoFase( context, partida.fase, l10n, todosListos, todosVotaron, ), ), const SizedBox(height: 16), _buildBotonAccion( context, partida.fase, l10n, todosListos, todosVotaron, ), ], ), ), ); } Widget _buildFaseIndicator( BuildContext context, FaseJuego fase, AppLocalizations l10n, ) { final fases = [ (FaseJuego.verPalabra, l10n.seeYourWord), (FaseJuego.debate, l10n.debate), (FaseJuego.votacion, l10n.voting), (FaseJuego.resultado, l10n.result), ]; return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: fases.map((e) { final esActiva = fase == e.$1 || fase.index > e.$1.index; return Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: esActiva ? TemaApp.colorAcento : TemaApp.colorSuperficie, borderRadius: BorderRadius.circular(20), ), child: Text( e.$2, style: TextStyle( color: esActiva ? Colors.white : TemaApp.colorTextoSecundario, fontWeight: esActiva ? FontWeight.bold : FontWeight.normal, ), ), ); }).toList(), ), ); } Widget _buildContenidoFase( BuildContext context, FaseJuego fase, AppLocalizations l10n, bool todosListos, bool todosVotaron, ) { final nearby = context.watch(); switch (fase) { case FaseJuego.verPalabra: return _buildFaseVerPalabra(context, l10n, todosListos, nearby); case FaseJuego.debate: return _buildFaseDebate(context, l10n, nearby); case FaseJuego.votacion: return _buildFaseVotacion(context, l10n, todosVotaron, nearby); default: return const Center(child: Text('Fin de la partida')); } } Widget _buildFaseVerPalabra( BuildContext context, AppLocalizations l10n, bool todosListos, ServicioNearby nearby, ) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.waitingPlayersSeeWord, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), Text( l10n.connectedPlayers, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), _buildJugadorTile(nearby.miNombre ?? 'Host', true, false), ...nearby.jugadores.map( (j) => _buildJugadorTile( j.nombre, false, _clientesListos[j.endpointId] ?? false, ), ), const Spacer(), // Botón para que el host vea su palabra SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () => _mostrarPalabraHost(context), icon: const Icon(Icons.visibility), label: Text(l10n.seeYourWord), style: ElevatedButton.styleFrom( backgroundColor: TemaApp.colorNaranja, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), ), ), ), const SizedBox(height: 12), if (todosListos) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: TemaApp.colorVerde.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.check_circle, color: TemaApp.colorVerde), const SizedBox(width: 8), Text( l10n.allSeenStartDebate, style: const TextStyle(color: TemaApp.colorVerde), ), ], ), ), ], ), ), ); } void _mostrarPalabraHost(BuildContext context) { final estado = context.read(); final partida = estado.partida; if (partida == null) return; // Buscar el jugador host local final hostLocal = partida.jugadores.firstWhere( (j) => j.nombre == context.read().miNombre, orElse: () => partida.jugadores.first, ); Navigator.push( context, MaterialPageRoute( builder: (_) => _PantallaRevelarPalabraHost( nombre: hostLocal.nombre, esImpostor: hostLocal.esImpostor, palabra: partida.palabraSecreta, pistaActiva: partida.config.pistaImpostor, categoria: partida.categoriaReal, ), ), ); } Widget _buildFaseDebate( BuildContext context, AppLocalizations l10n, ServicioNearby nearby, ) { final estado = context.read(); final tiempo = estado.partida?.config.tiempoDebateSegundos; return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (tiempo != null) ...[ Text(l10n.debate, style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: _segundosRestantes == 0 ? TemaApp.colorAcento.withValues(alpha: 0.3) : TemaApp.colorTarjeta, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ Text( _segundosRestantes == 0 ? l10n.timeUp : l10n.timeRemaining, style: Theme.of(context).textTheme.bodyMedium, ), Text( _formatearTiempo(_segundosRestantes), style: Theme.of(context).textTheme.headlineLarge, ), ], ), ), const SizedBox(height: 16), ], Text( l10n.activePlayers, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), Expanded( child: ListView.builder( itemCount: nearby.jugadores.length + 1, itemBuilder: (context, index) { if (index == 0) { return _buildJugadorTile( nearby.miNombre ?? 'Host', true, true, ); } final j = nearby.jugadores[index - 1]; return _buildJugadorTile(j.nombre, false, true); }, ), ), ], ), ), ); } Widget _buildFaseVotacion( BuildContext context, AppLocalizations l10n, bool todosVotaron, ServicioNearby nearby, ) { return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.voting, style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 16), Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: TemaApp.colorTarjeta, borderRadius: BorderRadius.circular(12), ), child: Column( children: [ Text( l10n.votesProgress( _votosRecibidos.length, nearby.jugadores.length + 1, ), ), const SizedBox(height: 8), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: _votosRecibidos.length / (nearby.jugadores.length + 1), backgroundColor: TemaApp.colorSuperficie, valueColor: const AlwaysStoppedAnimation( TemaApp.colorAcento, ), minHeight: 8, ), ), ], ), ), const SizedBox(height: 16), Text( l10n.playersVoted, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), Expanded( child: ListView.builder( itemCount: nearby.jugadores.length + 1, itemBuilder: (context, index) { final esHost = index == 0; final nombre = esHost ? (nearby.miNombre ?? 'Host') : nearby.jugadores[index - 1].nombre; final haVotado = esHost || _votosRecibidos.containsKey(nombre); return _buildJugadorTile(nombre, esHost, haVotado); }, ), ), if (todosVotaron) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: TemaApp.colorVerde.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.check_circle, color: TemaApp.colorVerde), const SizedBox(width: 8), Text( l10n.allVoted, style: const TextStyle(color: TemaApp.colorVerde), ), ], ), ), ], ), ), ); } Widget _buildJugadorTile(String nombre, bool esHost, bool listo) { return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: listo ? TemaApp.colorVerde.withValues(alpha: 0.2) : TemaApp.colorTarjeta, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Text( esHost ? 'Host' : 'Cliente', style: const TextStyle(fontSize: 18), ), const SizedBox(width: 8), Expanded(child: Text(nombre)), if (listo) const Icon(Icons.check_circle, color: TemaApp.colorVerde, size: 20), ], ), ); } Widget _buildBotonAccion( BuildContext context, FaseJuego fase, AppLocalizations l10n, bool todosListos, bool todosVotaron, ) { switch (fase) { case FaseJuego.verPalabra: return SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: todosListos ? () => _avanzarAFase(FaseJuego.debate) : null, icon: const Icon(Icons.forum), label: Text( todosListos ? l10n.allSeenStartDebate : l10n.waitingPlayersSeeWord, ), ), ); case FaseJuego.debate: return SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: () => _avanzarAFase(FaseJuego.votacion), icon: const Icon(Icons.how_to_vote), label: Text(l10n.goToVoting), ), ); case FaseJuego.votacion: return SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: todosVotaron ? () => _avanzarAFase(FaseJuego.resultado) : null, icon: const Icon(Icons.visibility), label: Text(todosVotaron ? l10n.revealResult : l10n.waitingVoting), ), ); default: return const SizedBox.shrink(); } } } class _PantallaRevelarPalabraHost extends StatefulWidget { final String nombre; final bool esImpostor; final String palabra; final bool pistaActiva; final String categoria; const _PantallaRevelarPalabraHost({ required this.nombre, required this.esImpostor, required this.palabra, required this.pistaActiva, required this.categoria, }); @override State<_PantallaRevelarPalabraHost> createState() => _PantallaRevelarPalabraHostState(); } class _PantallaRevelarPalabraHostState extends State<_PantallaRevelarPalabraHost> { bool _manteniendo = false; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar(title: Text(widget.nombre)), body: Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( widget.nombre, style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 32), AnimatedContainer( duration: const Duration(milliseconds: 200), width: double.infinity, padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: _manteniendo ? (widget.esImpostor ? TemaApp.colorAcento.withValues(alpha: 0.3) : TemaApp.colorVerde.withValues(alpha: 0.3)) : TemaApp.colorTarjeta, borderRadius: BorderRadius.circular(20), border: Border.all( color: _manteniendo ? (widget.esImpostor ? TemaApp.colorAcento : TemaApp.colorVerde) : Colors.transparent, width: 2, ), ), child: _manteniendo ? Column( children: [ Text( widget.esImpostor ? 'Impostor' : 'Ciudadano', style: const TextStyle(fontSize: 48), ), const SizedBox(height: 16), Text( widget.esImpostor ? l10n.youAreImpostor : l10n.yourWordIs, style: Theme.of(context).textTheme.titleLarge ?.copyWith( color: widget.esImpostor ? TemaApp.colorAcento : TemaApp.colorVerde, ), ), if (!widget.esImpostor) ...[ const SizedBox(height: 12), Text( widget.palabra, style: Theme.of(context).textTheme.headlineLarge ?.copyWith(fontSize: 32, color: Colors.white), textAlign: TextAlign.center, ), ], if (widget.esImpostor && widget.pistaActiva) ...[ const SizedBox(height: 12), Text( 'Categoria: ${widget.categoria}', style: Theme.of(context).textTheme.bodyLarge ?.copyWith(color: TemaApp.colorNaranja), ), ], ], ) : Column( children: [ const Text('Candado', style: TextStyle(fontSize: 48)), const SizedBox(height: 16), Text( l10n.holdToSeeWord, style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( l10n.makeSureNoOneLooks, style: Theme.of(context).textTheme.bodyMedium, ), ], ), ), const SizedBox(height: 24), GestureDetector( onLongPressStart: (_) => setState(() => _manteniendo = true), onLongPressEnd: (_) => setState(() => _manteniendo = false), child: Container( width: double.infinity, height: 64, decoration: BoxDecoration( gradient: LinearGradient( colors: _manteniendo ? [TemaApp.colorNaranja, TemaApp.colorAcento] : [TemaApp.colorAcento, TemaApp.colorAcento], ), borderRadius: BorderRadius.circular(16), ), child: Center( child: Text( _manteniendo ? l10n.showingWord : l10n.holdToSee, style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), ), ], ), ), ), ); } }