import 'package:flutter/material.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/modelos/partida.dart'; import 'package:farolero/modelos/snapshot_partida_online.dart'; import 'package:farolero/pantallas/pantalla_notas_online.dart'; import 'package:farolero/pantallas/pantalla_revision_palabra.dart'; import 'package:farolero/pantallas/pantalla_resultado_online.dart'; import 'package:farolero/servicios/servicio_nearby.dart'; import 'package:farolero/tema/componentes_farolero.dart'; import 'package:farolero/tema/tema_app.dart'; import 'package:provider/provider.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? pistaCategoria; final Function(Map votos) onVotos; const PantallaVotacionCliente({ super.key, required this.jugadores, this.jugadoresControlados = const [], this.partidaId, this.pistaCategoria, required this.onVotos, }); @override State createState() => _PantallaVotacionClienteState(); } class _PantallaVotacionClienteState extends State { final Map _votosPorVotante = {}; OnMensajeCallback? _listener; ServicioNearby? _nearby; List get _votantes => widget.jugadoresControlados; bool get _votacionCompleta { if (_votantes.isEmpty) return _votosPorVotante.containsKey('_legacy'); return _votantes.every((votante) => _votosPorVotante[votante.jugadorId] != null); } @override void initState() { super.initState(); _listener = (endpointId, mensaje) { if (mensaje.tipo != TipoMensaje.votacionResultado || !mounted) return; if (mensaje.datos.containsKey('jugadoresTodos')) { final snapshot = SnapshotPartidaOnline.fromJson(mensaje.datos); Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => PantallaResultadoOnline( snapshot: snapshot, jugadoresControlados: widget.jugadoresControlados, pistaCategoria: widget.pistaCategoria, ), ), ); } else { final votosRaw = mensaje.datos['votos'] as Map? ?? {}; final snapshot = SnapshotPartidaOnline( roomId: widget.partidaId, fase: 'resultado', ronda: 1, categoria: '', jugadores: widget.jugadores, resultadoActual: ResultadoVotacion( eliminadoId: mensaje.datos['eliminadoId'] as String? ?? '', eliminadoNombre: mensaje.datos['eliminadoNombre'] as String? ?? '?', eraImpostor: mensaje.datos['eraImpostor'] as bool? ?? false, votos: votosRaw.map( (key, value) => MapEntry(key.toString(), value.toString()), ), ), ); Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => PantallaResultadoOnline( snapshot: snapshot, jugadoresControlados: widget.jugadoresControlados, pistaCategoria: widget.pistaCategoria, ), ), ); } }; WidgetsBinding.instance.addPostFrameCallback((_) { final listener = _listener; if (listener != null && mounted) { _nearby = context.read(); _nearby!.onMensaje(listener); } }); } @override void dispose() { final listener = _listener; if (listener != null) { _nearby?.removeMensajeListener(listener); } super.dispose(); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Scaffold( backgroundColor: TemaApp.colorFondo, appBar: AppBar( title: Text(l10n.voting), automaticallyImplyLeading: false, backgroundColor: Colors.transparent, elevation: 0, actions: [ IconButton( tooltip: l10n.seeYourWord, icon: const Icon(Icons.visibility), onPressed: widget.jugadoresControlados.isEmpty ? null : () => mostrarRevisionPalabraOnline( context: context, jugadoresControlados: widget.jugadoresControlados, pistaCategoria: widget.pistaCategoria, ), ), IconButton( tooltip: l10n.notesTitle, icon: const Icon(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, 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: _votantes.isEmpty ? _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, onPressed: _votacionCompleta ? () => widget.onVotos(Map.unmodifiable(_votosPorVotante)) : null, ), ], ), ), ), ); } bool get _puedeAbrirNotas { return widget.partidaId != null && widget.jugadores.isNotEmpty && widget.jugadoresControlados.isNotEmpty; } Widget _buildSelectorLegacy() { return ListView.builder( itemCount: widget.jugadores.length, itemBuilder: (context, index) { final jugador = widget.jugadores[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), ...widget.jugadores.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, ); } }