Files
farolero/lib/pantallas/pantalla_votacion_cliente.dart
Javier Bautista Fernández a8d5b0f002
Some checks failed
Build & Deploy Farolero / Análisis de código (push) Has been cancelled
Build & Deploy Farolero / Build APK + AAB release (push) Has been cancelled
feat: Implement multiplayer game session management
- Add models for managing player assignments and game session initialization in `inicio_partida_multijugador.dart`.
- Create a multiplayer room state management system in `sala_multijugador.dart`, including user registration, selection, and session validation.
- Develop a UI screen for displaying player words sequentially in `pantalla_palabras_cliente.dart`.
- Implement unit tests for the multiplayer session management and player assignment logic in `inicio_partida_multijugador_test.dart` and `sala_multijugador_test.dart`.
2026-04-27 14:02:33 +02:00

182 lines
5.8 KiB
Dart

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/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<Jugador> jugadores;
final List<JugadorInicioPartida> jugadoresControlados;
final Function(Map<String, String> votos) onVotos;
const PantallaVotacionCliente({
super.key,
required this.jugadores,
this.jugadoresControlados = const [],
required this.onVotos,
});
@override
State<PantallaVotacionCliente> createState() => _PantallaVotacionClienteState();
}
class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
final Map<String, String> _votosPorVotante = {};
List<JugadorInicioPartida> get _votantes => widget.jugadoresControlados;
bool get _modoMultiVotante => _votantes.length > 1;
bool get _votacionCompleta {
if (_votantes.isEmpty) return _votosPorVotante.containsKey('_legacy');
return _votantes.every((votante) => _votosPorVotante[votante.jugadorId] != null);
}
@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,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.whoDoYouThinkIsTheImpostor,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(
_modoMultiVotante
? 'Emití un voto por cada jugador que manejás.'
: l10n.selectOnePlayer,
style: TextStyle(color: TemaApp.colorTextoSecundario),
),
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),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton.icon(
onPressed: _votacionCompleta
? () => widget.onVotos(Map.unmodifiable(_votosPorVotante))
: null,
icon: const Icon(Icons.how_to_vote),
label: Text(l10n.votar),
style: ElevatedButton.styleFrom(
backgroundColor: TemaApp.colorAcento,
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 16),
),
),
),
],
),
),
);
}
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 Card(
color: TemaApp.colorSuperficie,
margin: const EdgeInsets.only(bottom: 16),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Voto de ${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 Card(
color: selected
? TemaApp.colorAcento.withValues(alpha: 0.3)
: TemaApp.colorTarjeta,
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: selected
? TemaApp.colorAcento
: TemaApp.colorAcento.withValues(alpha: 0.3),
child: Text(
'${index + 1}',
style: TextStyle(
color: selected ? Colors.white : TemaApp.colorTexto,
),
),
),
title: Text(jugador.nombre),
trailing: selected
? const Icon(Icons.check_circle, color: TemaApp.colorAcento)
: null,
onTap: onTap,
),
);
}
}