import 'package:flutter/material.dart'; import 'package:farolero/tema/dialogo_confirmacion.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:farolero/modelos/inicio_partida_multijugador.dart'; import 'package:farolero/modelos/jugador.dart'; import 'package:farolero/pantallas/pantalla_notas_online.dart'; import 'package:farolero/pantallas/pantalla_revision_palabra.dart'; import 'package:farolero/tema/componentes_farolero.dart'; import 'package:farolero/tema/tema_app.dart'; /// Pantalla de votación para cliente multidispositivo. /// Un cliente puede manejar uno o varios jugadores, por eso se recoge un voto /// por cada jugador controlado activo. class PantallaVotacionCliente extends StatefulWidget { final List jugadores; final List jugadoresControlados; final String? partidaId; final String? pistaImpostor; final Function(Map votos) onVotos; /// El host abre esta pantalla desde el gestor: allí el atrás solo /// debe cerrarla, no ofrecer abandonar la partida. final bool protegerSalida; const PantallaVotacionCliente({ super.key, required this.jugadores, this.jugadoresControlados = const [], this.partidaId, this.pistaImpostor, required this.onVotos, this.protegerSalida = true, }); @override State createState() => _PantallaVotacionClienteState(); } class _PantallaVotacionClienteState extends State { final Map _votosPorVotante = {}; /// Solo los jugadores vivos pueden ser votados. List get _votables => widget.jugadores.where((jugador) => !jugador.eliminado).toList(); /// Y solo los jugadores vivos que controla este dispositivo pueden votar. List get _votantes => widget.jugadoresControlados .where( (controlado) => _votables.any((jugador) => jugador.id == controlado.jugadorId), ) .toList(); /// Protocolo antiguo: el cliente no recibió jugadores controlados y emite un /// único voto sin identificar al votante. bool get _modoLegacy => widget.jugadoresControlados.isEmpty; /// Todos los jugadores de este dispositivo están eliminados: no vota nadie. bool get _sinVotantesVivos => !_modoLegacy && _votantes.isEmpty; bool get _votacionCompleta { if (_sinVotantesVivos) return false; if (_modoLegacy) return _votosPorVotante.containsKey('_legacy'); return _votantes.every( (votante) => _votosPorVotante[votante.jugadorId] != null, ); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return GuardiaSalidaPartida( contexto: ContextoSalida.cliente, activo: widget.protegerSalida, child: Scaffold( backgroundColor: TemaApp.colorFondo, appBar: AppBar( title: Text(l10n.voting), automaticallyImplyLeading: false, backgroundColor: Colors.transparent, elevation: 0, actions: [ IconButton( tooltip: l10n.seeYourWord, icon: IconoFarolero(Icons.visibility), onPressed: widget.jugadoresControlados.isEmpty ? null : () => mostrarRevisionPalabraOnline( context: context, jugadoresControlados: widget.jugadoresControlados, pistaImpostor: widget.pistaImpostor, ), ), IconButton( tooltip: l10n.notesTitle, icon: IconoFarolero(Icons.edit_note), onPressed: _puedeAbrirNotas ? () => Navigator.push( context, MaterialPageRoute( builder: (_) => PantallaNotasOnline( partidaId: widget.partidaId!, jugadores: widget.jugadores, autoresControlados: widget.jugadoresControlados, ), ), ) : null, ), ], ), body: FondoFarolero( intenso: true, respetarBarras: true, child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const ArteGameplayFarolero.fase(height: 108), const SizedBox(height: 10), EncabezadoFarolero( icono: Icons.how_to_vote, titulo: l10n.whoDoYouThinkIsTheImpostor, subtitulo: l10n.selectOnePlayer, color: TemaApp.colorAcento, trailing: Image.asset( 'assets/ui/generated/meta/result_verdict_art.webp', width: 42, height: 42, opacity: const AlwaysStoppedAnimation(0.64), ), ), const SizedBox(height: 16), Expanded( child: _sinVotantesVivos ? Center( child: EstadoVacioFarolero( icono: Icons.hourglass_empty, titulo: l10n.eliminatedCannotVote, subtitulo: l10n.waitingVoting, ), ) : _modoLegacy ? _buildSelectorLegacy() : ListView.builder( itemCount: _votantes.length, itemBuilder: (context, index) { final votante = _votantes[index]; return _buildSelectorParaVotante(context, votante); }, ), ), const SizedBox(height: 16), BotonFarolero.secundario( texto: l10n.votar, icono: Icons.how_to_vote, assetIconPath: 'assets/ui/generated/actions/action_vote_mask.webp', onPressed: _votacionCompleta ? () => widget.onVotos(Map.unmodifiable(_votosPorVotante)) : null, ), ], ), ), ), ), ); } bool get _puedeAbrirNotas { return widget.partidaId != null && _votables.isNotEmpty && widget.jugadoresControlados.isNotEmpty; } Widget _buildSelectorLegacy() { final votables = _votables; return ListView.builder( itemCount: votables.length, itemBuilder: (context, index) { final jugador = votables[index]; final selected = _votosPorVotante['_legacy'] == jugador.id; return _buildJugadorVotable( jugador: jugador, index: index, selected: selected, onTap: () => setState(() => _votosPorVotante['_legacy'] = jugador.id), ); }, ); } Widget _buildSelectorParaVotante( BuildContext context, JugadorInicioPartida votante, ) { return PanelFarolero( margin: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.all(14), borderColor: TemaApp.colorAcento.withValues(alpha: 0.48), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( AppLocalizations.of(context)!.voteOf(votante.nombre), style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), ..._votables.asMap().entries.map((entry) { final jugador = entry.value; final selected = _votosPorVotante[votante.jugadorId] == jugador.id; return _buildJugadorVotable( jugador: jugador, index: entry.key, selected: selected, onTap: () => setState( () => _votosPorVotante[votante.jugadorId] = jugador.id, ), ); }), ], ), ); } Widget _buildJugadorVotable({ required Jugador jugador, required int index, required bool selected, required VoidCallback onTap, }) { return SelectorVotoFarolero( nombre: '${index + 1}. ${jugador.nombre}', seleccionado: selected, onTap: onTap, ); } }