import 'dart:async'; import 'dart:math'; 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/inicio_partida_multijugador.dart'; import '../modelos/jugador.dart'; import '../modelos/partida.dart'; import '../modelos/snapshot_partida_online.dart'; import '../servicios/servicio_nearby.dart'; import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_notas_online.dart'; import 'pantalla_revision_palabra.dart'; import 'pantalla_votacion_cliente.dart'; import 'pantalla_palabras_cliente.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; bool _hostListo = false; String? _primerTurnoId; String? _primerTurnoNombre; 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['votadoId'] as String? ?? mensaje.datos['votoporId'] as String?; if (votanteId != null && votoId != null) { context.read().registrarVoto(votanteId, votoId); 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(); final primero = _elegirPrimerTurno(); nearby.enviarCambioFase('debate', { ..._snapshot(fase: 'debate').toJson(), if (primero != null) ...{ 'primerTurnoId': primero.id, 'primerTurnoNombre': primero.nombre, }, if (estado.partida?.config.tiempoDebateSegundos != null) 'tiempoDebateSegundos': estado.partida!.config.tiempoDebateSegundos, }); _iniciarTemporizador(); break; case FaseJuego.votacion: estado.iniciarVotacion(); nearby.enviarCambioFase('votacion', _snapshot(fase: 'votacion').toJson()); _votosRecibidos.clear(); break; case FaseJuego.resultado: final resultado = estado.procesarVotacion(); if (resultado != null) { nearby.enviarResultadoVotacion( _snapshot( fase: 'resultado', resultadoActual: resultado, mensaje: _mensajeSiguienteAccion(estado, resultado), ).toJson(), ); } 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 todosListos = _hostListo && _clientesListos.length >= nearby.jugadores.length; final todosVotaron = estado.todosHanVotado(); return Scaffold( appBar: AppBar( title: Text(l10n.hostGame), automaticallyImplyLeading: false, actions: [ IconButton( tooltip: l10n.seeYourWord, icon: const Icon(Icons.visibility), onPressed: partida.fase.index <= FaseJuego.verPalabra.index ? null : () => mostrarRevisionPalabraOnline( context: context, jugadoresControlados: _jugadoresHostControlados( partida, nearby, ), pistaCategoria: partida.config.pistaImpostor ? partida.categoriaReal : null, ), ), IconButton( tooltip: l10n.notesTitle, icon: const Icon(Icons.edit_note), onPressed: partida.fase.index < FaseJuego.debate.index || nearby.roomId == null ? null : () => Navigator.push( context, MaterialPageRoute( builder: (_) => PantallaNotasOnline( partidaId: nearby.roomId!, jugadores: partida.jugadoresActivos, autoresControlados: _jugadoresHostControlados( partida, nearby, ), ), ), ), ), IconButton( icon: const Icon(Icons.close), onPressed: () async { await nearby.desconectar(); widget.onPartidaFin(); }, ), ], ), body: FondoFarolero( child: 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), (FaseJuego.adivinanza, l10n.guess), (FaseJuego.finPartida, l10n.gameOver), ]; 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); case FaseJuego.resultado: return _buildFaseResultado(context, l10n); case FaseJuego.adivinanza: return _buildFaseAdivinanza(context, l10n); case FaseJuego.finPartida: return _buildFaseFinOnline(context, l10n); default: return const Center(child: Text('Fin de la partida')); } } SnapshotPartidaOnline _snapshot({ required String fase, ResultadoVotacion? resultadoActual, String? mensaje, bool revelarFinal = false, }) { final estado = context.read(); final nearby = context.read(); final partida = estado.partida!; return SnapshotPartidaOnline.desdePartida( partida, roomId: nearby.roomId, fase: fase, resultadoActual: resultadoActual, mensaje: mensaje, revelarPalabra: revelarFinal, revelarImpostores: revelarFinal, ); } String _mensajeSiguienteAccion( EstadoJuego estado, ResultadoVotacion resultado, ) { final partida = estado.partida; if (partida != null && _hayFinTrasVotacion(partida)) { return 'La partida ha terminado.'; } if (resultado.eraImpostor) { return 'El impostor eliminado puede intentar adivinar la palabra.'; } return 'La partida continúa en la siguiente ronda.'; } bool _hayFinTrasVotacion(Partida partida) { final impostoresVivos = partida.impostoresActivos.length; final jugadoresVivos = partida.jugadoresNormalesActivos.length; return impostoresVivos == 0 || impostoresVivos >= jugadoresVivos; } 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, _hostListo), ...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), ), ], ), ), ], ), ), ); } List _jugadoresHostControlados( Partida partida, ServicioNearby nearby, ) { final sala = nearby.estadoSala; if (sala == null) return const []; return sala .usuariosPorCliente(sala.hostClientId) .where((usuario) => partida.jugadores.any((j) => j.id == usuario.id)) .map((usuario) { final jugador = partida.jugadores.firstWhere( (j) => j.id == usuario.id, ); return JugadorInicioPartida( jugadorId: jugador.id, nombre: jugador.nombre, esImpostor: jugador.esImpostor, palabra: jugador.palabra ?? partida.palabraSecreta, ); }) .toList(); } void _mostrarPalabraHost(BuildContext context) { final estado = context.read(); final nearby = context.read(); final partida = estado.partida; if (partida == null) return; final jugadoresHost = _jugadoresHostControlados(partida, nearby); if (jugadoresHost.length > 1) { Navigator.push( context, MaterialPageRoute( builder: (_) => PantallaPalabrasCliente( jugadores: jugadoresHost, pistaCategoria: partida.config.pistaImpostor ? partida.categoriaReal : null, onTodosVistos: () { setState(() => _hostListo = true); Navigator.of(context).pop(); }, ), ), ); return; } final hostLocal = jugadoresHost.isNotEmpty ? partida.jugadores.firstWhere((j) => j.id == jugadoresHost.first.jugadorId) : 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, onVisto: () => setState(() => _hostListo = true), ), ), ); } Jugador? _elegirPrimerTurno() { final partida = context.read().partida; if (partida == null || partida.jugadoresActivos.isEmpty) return null; if (_primerTurnoId != null) { return partida.jugadoresActivos.firstWhere( (j) => j.id == _primerTurnoId, orElse: () => partida.jugadoresActivos.first, ); } final elegido = partida.jugadoresActivos[ Random.secure().nextInt(partida.jugadoresActivos.length)]; _primerTurnoId = elegido.id; _primerTurnoNombre = elegido.nombre; return elegido; } 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), ], _buildPrimerTurno(context), 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 _buildPrimerTurno(BuildContext context) { final primero = _elegirPrimerTurno(); final nombre = _primerTurnoNombre ?? primero?.nombre; if (nombre == null) return const SizedBox.shrink(); return Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: TemaApp.decoracionPanel( color: TemaApp.colorNaranja.withValues(alpha: 0.16), borderColor: TemaApp.colorNaranja.withValues(alpha: 0.7), ), child: Row( children: [ const Icon(Icons.record_voice_over, color: TemaApp.colorNaranja), const SizedBox(width: 12), Expanded( child: Text( 'Empieza $nombre diciendo su palabra.', style: Theme.of(context).textTheme.titleMedium, ), ), ], ), ); } Widget _buildFaseVotacion( BuildContext context, AppLocalizations l10n, bool todosVotaron, ServicioNearby nearby, ) { final estado = context.watch(); final partida = estado.partida!; final totalVotos = partida.jugadoresActivos.length; final votosEmitidos = estado.votos.length; final progreso = totalVotos == 0 ? 0.0 : votosEmitidos / totalVotos; 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(votosEmitidos, totalVotos)), const SizedBox(height: 8), ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: progreso.clamp(0.0, 1.0).toDouble(), backgroundColor: TemaApp.colorSuperficie, valueColor: const AlwaysStoppedAnimation( TemaApp.colorAcento, ), minHeight: 8, ), ), ], ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _hostYaVoto(context) ? null : () => _abrirVotacionHost(context), icon: const Icon(Icons.how_to_vote), label: Text( _hostYaVoto(context) ? 'Votos del host registrados' : 'Votar por los jugadores de este m?vil', ), ), ), const SizedBox(height: 16), Text( l10n.playersVoted, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), Expanded( child: ListView.builder( itemCount: partida.jugadoresActivos.length, itemBuilder: (context, index) { final jugador = partida.jugadoresActivos[index]; final haVotado = estado.votos.containsKey(jugador.id); return _buildJugadorTile(jugador.nombre, false, 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 _buildFaseResultado(BuildContext context, AppLocalizations l10n) { final partida = context.watch().partida; final resultado = partida?.historialVotaciones.isNotEmpty == true ? partida!.historialVotaciones.last : null; if (partida == null || resultado == null) { return const Center(child: Text('Sin resultado')); } final conteo = {}; for (final votadoId in resultado.votos.values) { conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; } final maxVotos = conteo.values.isEmpty ? 1 : conteo.values.reduce((a, b) => a > b ? a : b); final ranking = conteo.entries.toList() ..sort((a, b) => b.value.compareTo(a.value)); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.result, style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: TemaApp.decoracionPanel( color: resultado.eraImpostor ? TemaApp.colorVerde.withValues(alpha: 0.18) : TemaApp.colorAcento.withValues(alpha: 0.18), borderColor: resultado.eraImpostor ? TemaApp.colorVerde : TemaApp.colorAcento, ), child: Column( children: [ Text( resultado.eliminadoNombre, style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 6), Text( resultado.eraImpostor ? l10n.wasImpostor : l10n.wasInnocent, style: TextStyle( color: resultado.eraImpostor ? TemaApp.colorVerde : TemaApp.colorAcento, fontWeight: FontWeight.bold, ), ), ], ), ), const SizedBox(height: 16), Text(l10n.votesThisRound, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 12), Expanded( child: ListView( children: [ ...ranking.map((entry) { final jugador = partida.jugadores.firstWhere( (j) => j.id == entry.key, orElse: () => partida.jugadores.first, ); return _buildBarraResultado( context, nombre: jugador.nombre, votos: entry.value, maxVotos: maxVotos, destacado: entry.key == resultado.eliminadoId, ); }), const Divider(height: 24), ...resultado.votos.entries.map((entry) { final votante = partida.jugadores.firstWhere( (j) => j.id == entry.key, orElse: () => partida.jugadores.first, ); final votado = partida.jugadores.firstWhere( (j) => j.id == entry.value, orElse: () => partida.jugadores.first, ); return ListTile( dense: true, leading: const Icon(Icons.how_to_vote), title: Text('${votante.nombre} → ${votado.nombre}'), ); }), ], ), ), ], ), ), ); } Widget _buildBarraResultado( BuildContext context, { required String nombre, required int votos, required int maxVotos, required bool destacado, }) { final color = destacado ? TemaApp.colorAcento : TemaApp.colorNaranja; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded(child: Text(nombre)), Text('$votos', style: TextStyle(color: color, fontWeight: FontWeight.bold)), ], ), const SizedBox(height: 6), ClipRRect( borderRadius: BorderRadius.circular(999), child: LinearProgressIndicator( value: (votos / maxVotos).clamp(0.0, 1.0).toDouble(), minHeight: 10, backgroundColor: TemaApp.colorSuperficie, valueColor: AlwaysStoppedAnimation(color), ), ), ], ), ); } Widget _buildFaseAdivinanza(BuildContext context, AppLocalizations l10n) { final partida = context.watch().partida; final ultimo = partida?.historialVotaciones.isNotEmpty == true ? partida!.historialVotaciones.last : null; return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.psychology, size: 56, color: TemaApp.colorNaranja), const SizedBox(height: 16), Text( l10n.impostorGuessTitle, style: Theme.of(context).textTheme.headlineSmall, textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( ultimo == null ? l10n.impostorCanGuess : '${ultimo.eliminadoNombre}: ${l10n.impostorCanGuess}', textAlign: TextAlign.center, ), ], ), ), ); } Widget _buildFaseFinOnline(BuildContext context, AppLocalizations l10n) { final partida = context.watch().partida; final ganaronJugadores = partida?.ganador == 'jugadores'; return Card( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( ganaronJugadores ? '🎉' : '🎭', style: const TextStyle(fontSize: 64), ), const SizedBox(height: 16), Text( ganaronJugadores ? l10n.playersWin : l10n.impostorsWin, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 12), Text( partida == null ? '' : l10n.theWordWas(partida.palabraSecreta), textAlign: TextAlign.center, ), ], ), ), ); } bool _hostYaVoto(BuildContext context) { final estado = context.read(); final sala = context.read().estadoSala; if (sala == null || estado.partida == null) return false; final hostIds = sala.usuariosPorCliente(sala.hostClientId).map((u) => u.id); return hostIds.every((id) => estado.votos.containsKey(id)); } void _abrirVotacionHost(BuildContext context) { final estado = context.read(); final sala = context.read().estadoSala; final partida = estado.partida; if (sala == null || partida == null) return; final jugadoresHost = sala.usuariosPorCliente(sala.hostClientId) .where((usuario) => partida.jugadoresActivos.any((j) => j.id == usuario.id)) .map( (usuario) => JugadorInicioPartida( jugadorId: usuario.id, nombre: usuario.nombre, esImpostor: partida.jugadores.firstWhere((j) => j.id == usuario.id).esImpostor, palabra: partida.jugadores.firstWhere((j) => j.id == usuario.id).palabra, ), ) .toList(); Navigator.push( context, MaterialPageRoute( builder: (_) => PantallaVotacionCliente( jugadores: partida.jugadoresActivos, jugadoresControlados: jugadoresHost, partidaId: context.read().roomId, pistaCategoria: partida.config.pistaImpostor ? partida.categoriaReal : null, onVotos: (votos) { for (final entry in votos.entries) { estado.registrarVoto(entry.key, entry.value); _votosRecibidos[entry.key] = entry.value; } if (mounted) setState(() {}); Navigator.of(context).pop(); }, ), ), ); } 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), ), ); case FaseJuego.resultado: return _buildAccionesResultado(context, l10n); case FaseJuego.adivinanza: return _buildAccionesAdivinanza(context, l10n); case FaseJuego.finPartida: return SizedBox( width: double.infinity, height: 56, child: OutlinedButton.icon( onPressed: () async { final nearby = context.read(); await nearby.desconectar(); widget.onPartidaFin(); }, icon: const Icon(Icons.home), label: Text(l10n.mainMenu), ), ); default: return const SizedBox.shrink(); } } Widget _buildAccionesResultado(BuildContext context, AppLocalizations l10n) { final estado = context.read(); final partida = estado.partida; final resultado = partida?.historialVotaciones.isNotEmpty == true ? partida!.historialVotaciones.last : null; if (partida == null || resultado == null) return const SizedBox.shrink(); if (_hayFinTrasVotacion(partida)) { return SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: () => _finalizarPartidaOnline(context), icon: const Icon(Icons.emoji_events), label: Text(l10n.seeEndResult), ), ); } if (resultado.eraImpostor) { return Column( children: [ SizedBox( width: double.infinity, height: 56, child: OutlinedButton.icon( onPressed: () => _iniciarAdivinanzaOnline(context), icon: const Icon(Icons.psychology), label: Text(l10n.impostorGuessWord), ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: () => _siguienteRondaOnline(context), icon: const Icon(Icons.skip_next), label: Text(l10n.nextRound), ), ), ], ); } return SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: () => _siguienteRondaOnline(context), icon: const Icon(Icons.skip_next), label: Text(l10n.nextRound), ), ); } Widget _buildAccionesAdivinanza(BuildContext context, AppLocalizations l10n) { return Column( children: [ SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: () => _resolverAdivinanzaOnline(context), icon: const Icon(Icons.check_circle), label: Text(l10n.guess), ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, height: 56, child: OutlinedButton.icon( onPressed: () => _siguienteRondaOnline(context), icon: const Icon(Icons.skip_next), label: Text(l10n.dontGuess), ), ), ], ); } Future _finalizarPartidaOnline(BuildContext context) async { final estado = context.read(); final nearby = context.read(); estado.comprobarFinPartida(); await nearby.enviarFinPartida( _snapshot(fase: 'finPartida', revelarFinal: true).toJson(), ); if (mounted) setState(() {}); } Future _iniciarAdivinanzaOnline(BuildContext context) async { final estado = context.read(); final nearby = context.read(); estado.iniciarAdivinanza(); await nearby.enviarCambioFase( 'adivinanza', _snapshot( fase: 'adivinanza', mensaje: AppLocalizations.of(context)!.impostorCanGuess, ).toJson(), ); if (mounted) setState(() {}); } Future _resolverAdivinanzaOnline(BuildContext context) async { final l10n = AppLocalizations.of(context)!; final controller = TextEditingController(); final intento = await showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(l10n.impostorGuessTitle), content: TextField( controller: controller, autofocus: true, decoration: InputDecoration(hintText: l10n.guessWordHint), onSubmitted: (value) => Navigator.pop(ctx, value), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: Text(l10n.cancel), ), TextButton( onPressed: () => Navigator.pop(ctx, controller.text), child: Text(l10n.accept), ), ], ), ); controller.dispose(); if (!context.mounted) return; if (intento == null || intento.trim().isEmpty) { await _siguienteRondaOnline(context); return; } final estado = context.read(); final acierto = estado.intentarAdivinar(intento); if (acierto) { final nearby = context.read(); await nearby.enviarFinPartida( _snapshot(fase: 'finPartida', revelarFinal: true).toJson(), ); if (mounted) setState(() {}); return; } await _siguienteRondaOnline(context); } Future _siguienteRondaOnline(BuildContext context) async { final estado = context.read(); final nearby = context.read(); estado.siguienteRonda(); _primerTurnoId = null; _primerTurnoNombre = null; final primero = _elegirPrimerTurno(); await nearby.enviarCambioFase('debate', { ..._snapshot(fase: 'debate').toJson(), if (primero != null) ...{ 'primerTurnoId': primero.id, 'primerTurnoNombre': primero.nombre, }, if (estado.partida?.config.tiempoDebateSegundos != null) 'tiempoDebateSegundos': estado.partida!.config.tiempoDebateSegundos, }); _timer?.cancel(); _iniciarTemporizador(); if (mounted) setState(() {}); } } class _PantallaRevelarPalabraHost extends StatefulWidget { final String nombre; final bool esImpostor; final String palabra; final bool pistaActiva; final String categoria; final VoidCallback onVisto; const _PantallaRevelarPalabraHost({ required this.nombre, required this.esImpostor, required this.palabra, required this.pistaActiva, required this.categoria, required this.onVisto, }); @override State<_PantallaRevelarPalabraHost> createState() => _PantallaRevelarPalabraHostState(); } class _PantallaRevelarPalabraHostState extends State<_PantallaRevelarPalabraHost> { bool _manteniendo = false; bool _haRevelado = false; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Scaffold( appBar: AppBar(title: Text(widget.nombre)), body: FondoFarolero( intenso: true, child: 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), TarjetaPalabraFarolero(palabra: widget.palabra), ], if (widget.esImpostor && widget.pistaActiva) ...[ const SizedBox(height: 12), Text( 'Categoría: ${widget.categoria}', style: Theme.of(context).textTheme.bodyLarge ?.copyWith(color: TemaApp.colorNaranja), ), ], ], ) : Column( children: [ const Text('🔒', 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; _haRevelado = 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, ), ), ), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: _haRevelado ? () { widget.onVisto(); Navigator.of(context).pop(); } : null, icon: const Icon(Icons.check), label: Text( _haRevelado ? l10n.iveSeenIt : l10n.tapToSee, ), ), ), ], ), ), ), ), ); } }