Implementado:
No se puede marcar “vista” sin revelar la palabra antes. Se puede volver a ver la palabra durante debate/votación/resultado. Notas online privadas por partida y jugador. Tests añadidos para notas scoped. Ajusté roomId en el payload de inicio para que las notas no se mezclen entre partidas.
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import 'dart:async';
|
||||
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/pantallas/pantalla_notas_online.dart';
|
||||
import 'package:farolero/pantallas/pantalla_revision_palabra.dart';
|
||||
import 'package:farolero/tema/tema_app.dart';
|
||||
|
||||
/// Pantalla que ve el jugador durante la fase de debate (multidispositivo).
|
||||
@@ -8,12 +12,20 @@ import 'package:farolero/tema/tema_app.dart';
|
||||
class PantallaDebateCliente extends StatefulWidget {
|
||||
final int? tiempoDebateSegundos;
|
||||
final String? primerTurnoNombre;
|
||||
final String? partidaId;
|
||||
final String? pistaCategoria;
|
||||
final List<Jugador> jugadores;
|
||||
final List<JugadorInicioPartida> jugadoresControlados;
|
||||
final VoidCallback onSolicitarVotacion;
|
||||
|
||||
const PantallaDebateCliente({
|
||||
super.key,
|
||||
this.tiempoDebateSegundos,
|
||||
this.primerTurnoNombre,
|
||||
this.partidaId,
|
||||
this.pistaCategoria,
|
||||
this.jugadores = const [],
|
||||
this.jugadoresControlados = const [],
|
||||
required this.onSolicitarVotacion,
|
||||
});
|
||||
|
||||
@@ -64,6 +76,35 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
|
||||
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: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
@@ -185,4 +226,10 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool get _puedeAbrirNotas {
|
||||
return widget.partidaId != null &&
|
||||
widget.jugadores.isNotEmpty &&
|
||||
widget.jugadoresControlados.isNotEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import '../modelos/partida.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';
|
||||
|
||||
@@ -145,6 +147,42 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
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 {
|
||||
@@ -316,13 +354,14 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
);
|
||||
}
|
||||
|
||||
void _mostrarPalabraHost(BuildContext context) {
|
||||
final estado = context.read<EstadoJuego>();
|
||||
final sala = context.read<ServicioNearby>().estadoSala;
|
||||
final partida = estado.partida;
|
||||
if (partida == null || sala == null) return;
|
||||
List<JugadorInicioPartida> _jugadoresHostControlados(
|
||||
Partida partida,
|
||||
ServicioNearby nearby,
|
||||
) {
|
||||
final sala = nearby.estadoSala;
|
||||
if (sala == null) return const [];
|
||||
|
||||
final jugadoresHost = sala
|
||||
return sala
|
||||
.usuariosPorCliente(sala.hostClientId)
|
||||
.where((usuario) => partida.jugadores.any((j) => j.id == usuario.id))
|
||||
.map((usuario) {
|
||||
@@ -333,10 +372,19 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
jugadorId: jugador.id,
|
||||
nombre: jugador.nombre,
|
||||
esImpostor: jugador.esImpostor,
|
||||
palabra: jugador.palabra,
|
||||
palabra: jugador.palabra ?? partida.palabraSecreta,
|
||||
);
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
void _mostrarPalabraHost(BuildContext context) {
|
||||
final estado = context.read<EstadoJuego>();
|
||||
final nearby = context.read<ServicioNearby>();
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return;
|
||||
|
||||
final jugadoresHost = _jugadoresHostControlados(partida, nearby);
|
||||
|
||||
if (jugadoresHost.length > 1) {
|
||||
Navigator.push(
|
||||
@@ -759,6 +807,10 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||
builder: (_) => PantallaVotacionCliente(
|
||||
jugadores: partida.jugadoresActivos,
|
||||
jugadoresControlados: jugadoresHost,
|
||||
partidaId: context.read<ServicioNearby>().roomId,
|
||||
pistaCategoria: partida.config.pistaImpostor
|
||||
? partida.categoriaReal
|
||||
: null,
|
||||
onVotos: (votos) {
|
||||
for (final entry in votos.entries) {
|
||||
estado.registrarVoto(entry.key, entry.value);
|
||||
@@ -874,6 +926,7 @@ class _PantallaRevelarPalabraHost extends StatefulWidget {
|
||||
class _PantallaRevelarPalabraHostState
|
||||
extends State<_PantallaRevelarPalabraHost> {
|
||||
bool _manteniendo = false;
|
||||
bool _haRevelado = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -966,7 +1019,10 @@ class _PantallaRevelarPalabraHostState
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
GestureDetector(
|
||||
onLongPressStart: (_) => setState(() => _manteniendo = true),
|
||||
onLongPressStart: (_) => setState(() {
|
||||
_manteniendo = true;
|
||||
_haRevelado = true;
|
||||
}),
|
||||
onLongPressEnd: (_) => setState(() => _manteniendo = false),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
@@ -996,12 +1052,16 @@ class _PantallaRevelarPalabraHostState
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
widget.onVisto();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onPressed: _haRevelado
|
||||
? () {
|
||||
widget.onVisto();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.check),
|
||||
label: Text(l10n.iveSeenIt),
|
||||
label: Text(
|
||||
_haRevelado ? l10n.iveSeenIt : l10n.tapToSee,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
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/servicios/servicio_notas.dart';
|
||||
import 'package:farolero/tema/tema_app.dart';
|
||||
|
||||
class PantallaNotasOnline extends StatefulWidget {
|
||||
final String partidaId;
|
||||
final List<Jugador> jugadores;
|
||||
final List<JugadorInicioPartida> autoresControlados;
|
||||
|
||||
const PantallaNotasOnline({
|
||||
super.key,
|
||||
required this.partidaId,
|
||||
required this.jugadores,
|
||||
required this.autoresControlados,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PantallaNotasOnline> createState() => _PantallaNotasOnlineState();
|
||||
}
|
||||
|
||||
class _PantallaNotasOnlineState extends State<PantallaNotasOnline> {
|
||||
JugadorInicioPartida? _autor;
|
||||
final Map<String, TextEditingController> _controladores = {};
|
||||
final TextEditingController _notaLibreController = TextEditingController();
|
||||
bool _cargando = false;
|
||||
|
||||
List<Jugador> get _jugadoresActivos =>
|
||||
widget.jugadores.where((jugador) => !jugador.eliminado).toList();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
for (final jugador in _jugadoresActivos) {
|
||||
_controladores[jugador.id] = TextEditingController();
|
||||
}
|
||||
if (widget.autoresControlados.length == 1) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) _seleccionarAutor(widget.autoresControlados.first);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (final controller in _controladores.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
_notaLibreController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _seleccionarAutor(JugadorInicioPartida autor) async {
|
||||
setState(() {
|
||||
_autor = autor;
|
||||
_cargando = true;
|
||||
for (final controller in _controladores.values) {
|
||||
controller.clear();
|
||||
}
|
||||
_notaLibreController.clear();
|
||||
});
|
||||
|
||||
final datos = await ServicioNotas.cargarNotasPartida(
|
||||
partidaId: widget.partidaId,
|
||||
autorJugadorId: autor.jugadorId,
|
||||
);
|
||||
if (!mounted) return;
|
||||
final notas = datos['notas'] as Map<String, String>;
|
||||
for (final entry in notas.entries) {
|
||||
_controladores[entry.key]?.text = entry.value;
|
||||
}
|
||||
_notaLibreController.text = datos['notaLibre'] as String;
|
||||
setState(() => _cargando = false);
|
||||
}
|
||||
|
||||
Future<void> _guardarNotas() async {
|
||||
final autor = _autor;
|
||||
if (autor == null) return;
|
||||
final notas = <String, String>{};
|
||||
for (final entry in _controladores.entries) {
|
||||
final texto = entry.value.text.trim();
|
||||
if (texto.isNotEmpty) notas[entry.key] = texto;
|
||||
}
|
||||
await ServicioNotas.guardarNotasPartida(
|
||||
partidaId: widget.partidaId,
|
||||
autorJugadorId: autor.jugadorId,
|
||||
notasPorJugador: notas,
|
||||
notaLibre: _notaLibreController.text,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return PopScope(
|
||||
canPop: true,
|
||||
onPopInvokedWithResult: (_, __) => _guardarNotas(),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.notesTitle),
|
||||
actions: [
|
||||
if (_autor != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: () async {
|
||||
await _guardarNotas();
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.notesSaved)),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _autor == null ? _buildSelector(context) : _buildNotas(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSelector(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.whoAreYou,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: widget.autoresControlados
|
||||
.map(
|
||||
(autor) => Card(
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.edit_note),
|
||||
title: Text(autor.nombre),
|
||||
onTap: () => _seleccionarAutor(autor),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNotas(BuildContext context) {
|
||||
if (_cargando) return const Center(child: CircularProgressIndicator());
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final autor = _autor!;
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (widget.autoresControlados.length > 1)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () async {
|
||||
await _guardarNotas();
|
||||
if (!mounted) return;
|
||||
setState(() => _autor = null);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.notesOf(autor.nombre),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.notesAboutPlayers,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: TemaApp.colorTextoSecundario,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
..._jugadoresActivos
|
||||
.where((jugador) => jugador.id != autor.jugadorId)
|
||||
.map(
|
||||
(jugador) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: TextField(
|
||||
controller: _controladores[jugador.id],
|
||||
decoration: InputDecoration(
|
||||
labelText: jugador.nombre,
|
||||
prefixIcon: const Icon(Icons.person, size: 20),
|
||||
hintText: l10n.playerNoteHint,
|
||||
),
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.freeNote,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: TemaApp.colorTextoSecundario,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _notaLibreController,
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.freeNoteHint,
|
||||
prefixIcon: const Icon(Icons.note, size: 20),
|
||||
),
|
||||
maxLines: 5,
|
||||
minLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,14 @@ class PantallaPalabraCliente extends StatefulWidget {
|
||||
|
||||
class _PantallaPalabraClienteState extends State<PantallaPalabraCliente> {
|
||||
bool _palabraVisible = false;
|
||||
bool _haRevelado = false;
|
||||
Timer? _timer;
|
||||
|
||||
void _togglePalabra() {
|
||||
setState(() => _palabraVisible = !_palabraVisible);
|
||||
setState(() {
|
||||
_palabraVisible = !_palabraVisible;
|
||||
if (_palabraVisible) _haRevelado = true;
|
||||
});
|
||||
_timer?.cancel();
|
||||
}
|
||||
|
||||
@@ -88,17 +92,28 @@ class _PantallaPalabraClienteState extends State<PantallaPalabraCliente> {
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_palabraVisible
|
||||
? TarjetaPalabraFarolero(palabra: widget.palabra)
|
||||
: const Text(
|
||||
'???',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: TemaApp.colorTextoSecundario,
|
||||
),
|
||||
),
|
||||
if (_palabraVisible && widget.esImpostor)
|
||||
Text(
|
||||
l10n.youAreImpostor,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: TemaApp.colorDorado,
|
||||
),
|
||||
)
|
||||
else if (_palabraVisible)
|
||||
TarjetaPalabraFarolero(palabra: widget.palabra)
|
||||
else
|
||||
const Text(
|
||||
'???',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: TemaApp.colorTextoSecundario,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -137,7 +152,9 @@ class _PantallaPalabraClienteState extends State<PantallaPalabraCliente> {
|
||||
child: Text(
|
||||
_palabraVisible
|
||||
? 'Mantén la pantalla oculta. No la enseñes a nadie.'
|
||||
: 'Toca para ver tu palabra',
|
||||
: _haRevelado
|
||||
? l10n.seeYourWord
|
||||
: l10n.tapToSee,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: TemaApp.colorTextoSecundario,
|
||||
@@ -153,11 +170,11 @@ class _PantallaPalabraClienteState extends State<PantallaPalabraCliente> {
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
widget.onVisto();
|
||||
},
|
||||
onPressed: _haRevelado ? widget.onVisto : null,
|
||||
icon: const Icon(Icons.check),
|
||||
label: Text(l10n.iveSeenIt),
|
||||
label: Text(
|
||||
_haRevelado ? l10n.iveSeenIt : l10n.tapToSee,
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: TemaApp.colorAcento,
|
||||
foregroundColor: Colors.white,
|
||||
|
||||
@@ -24,11 +24,14 @@ class PantallaPalabrasCliente extends StatefulWidget {
|
||||
class _PantallaPalabrasClienteState extends State<PantallaPalabrasCliente> {
|
||||
int _indice = 0;
|
||||
bool _visible = false;
|
||||
final Set<String> _jugadoresRevelados = {};
|
||||
|
||||
JugadorInicioPartida get _actual => widget.jugadores[_indice];
|
||||
bool get _esUltimo => _indice == widget.jugadores.length - 1;
|
||||
bool get _actualRevelado => _jugadoresRevelados.contains(_actual.jugadorId);
|
||||
|
||||
void _continuar() {
|
||||
if (!_actualRevelado) return;
|
||||
if (_esUltimo) {
|
||||
widget.onTodosVistos();
|
||||
return;
|
||||
@@ -64,7 +67,10 @@ class _PantallaPalabrasClienteState extends State<PantallaPalabrasCliente> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
GestureDetector(
|
||||
onTap: () => setState(() => _visible = !_visible),
|
||||
onTap: () => setState(() {
|
||||
_visible = !_visible;
|
||||
if (_visible) _jugadoresRevelados.add(actual.jugadorId);
|
||||
}),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
width: double.infinity,
|
||||
@@ -113,14 +119,26 @@ class _PantallaPalabrasClienteState extends State<PantallaPalabrasCliente> {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
_actualRevelado
|
||||
? l10n.seeYourWord
|
||||
: l10n.tapToSee,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: TemaApp.colorTextoSecundario),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _continuar,
|
||||
onPressed: _actualRevelado ? _continuar : null,
|
||||
icon: Icon(_esUltimo ? Icons.check : Icons.arrow_forward),
|
||||
label: Text(_esUltimo ? l10n.iveSeenIt : 'Siguiente jugador'),
|
||||
label: Text(
|
||||
_actualRevelado
|
||||
? (_esUltimo ? l10n.iveSeenIt : l10n.next)
|
||||
: l10n.tapToSee,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:farolero/l10n/generated/app_localizations.dart';
|
||||
import 'package:farolero/modelos/inicio_partida_multijugador.dart';
|
||||
import 'package:farolero/tema/componentes_farolero.dart';
|
||||
import 'package:farolero/tema/tema_app.dart';
|
||||
|
||||
Future<void> mostrarRevisionPalabraOnline({
|
||||
required BuildContext context,
|
||||
required List<JugadorInicioPartida> jugadoresControlados,
|
||||
String? pistaCategoria,
|
||||
}) async {
|
||||
if (jugadoresControlados.isEmpty) return;
|
||||
|
||||
final jugador = jugadoresControlados.length == 1
|
||||
? jugadoresControlados.first
|
||||
: await showModalBottomSheet<JugadorInicioPartida>(
|
||||
context: context,
|
||||
backgroundColor: TemaApp.colorSuperficie,
|
||||
builder: (sheetContext) => SafeArea(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(sheetContext)!.seeYourWord,
|
||||
style: Theme.of(sheetContext).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...jugadoresControlados.map(
|
||||
(jugador) => Card(
|
||||
color: TemaApp.colorTarjeta,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.visibility),
|
||||
title: Text(jugador.nombre),
|
||||
onTap: () => Navigator.pop(sheetContext, jugador),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (jugador == null || !context.mounted) return;
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => _DialogoRevisionPalabra(
|
||||
jugador: jugador,
|
||||
pistaCategoria: pistaCategoria,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _DialogoRevisionPalabra extends StatelessWidget {
|
||||
final JugadorInicioPartida jugador;
|
||||
final String? pistaCategoria;
|
||||
|
||||
const _DialogoRevisionPalabra({
|
||||
required this.jugador,
|
||||
required this.pistaCategoria,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: TemaApp.colorSuperficie,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
jugador.nombre,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: TemaApp.decoracionPanel(
|
||||
color: TemaApp.colorTarjeta,
|
||||
borderColor: jugador.esImpostor
|
||||
? TemaApp.colorAcento
|
||||
: TemaApp.colorNaranja,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
jugador.esImpostor ? Icons.theater_comedy : Icons.key,
|
||||
color: jugador.esImpostor
|
||||
? TemaApp.colorAcento
|
||||
: TemaApp.colorNaranja,
|
||||
size: 36,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (jugador.esImpostor)
|
||||
Text(
|
||||
l10n.youAreImpostor,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
else
|
||||
TarjetaPalabraFarolero(palabra: jugador.palabra ?? ''),
|
||||
if (jugador.esImpostor && pistaCategoria != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.clueIs(pistaCategoria!),
|
||||
style: const TextStyle(color: TemaApp.colorNaranja),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.check),
|
||||
label: Text(l10n.back),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
String? _palabraRecibida;
|
||||
bool _esImpostor = false;
|
||||
String? _pistaCategoria;
|
||||
String? _partidaId;
|
||||
final List<Jugador> _jugadores = [];
|
||||
final List<JugadorInicioPartida> _jugadoresControlados = [];
|
||||
|
||||
@@ -87,8 +88,24 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
} else {
|
||||
_palabraRecibida = mensaje.datos['palabra'] as String?;
|
||||
_esImpostor = mensaje.datos['esImpostor'] as bool? ?? false;
|
||||
if (_palabraRecibida != null) {
|
||||
_jugadoresControlados.add(
|
||||
JugadorInicioPartida(
|
||||
jugadorId: nearby.miClientId ?? '_legacy',
|
||||
nombre: _nombreController.text.trim().isEmpty
|
||||
? 'Jugador'
|
||||
: _nombreController.text.trim(),
|
||||
esImpostor: _esImpostor,
|
||||
palabra: _palabraRecibida,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
_pistaCategoria = mensaje.datos['categoria'] as String?;
|
||||
_partidaId = (mensaje.datos['roomId'] as String?) ??
|
||||
nearby.roomId ??
|
||||
(mensaje.datos['clientId'] as String?) ??
|
||||
DateTime.now().microsecondsSinceEpoch.toString();
|
||||
});
|
||||
// Navegar a pantalla de palabra del cliente
|
||||
if (mounted && (_jugadoresControlados.isNotEmpty || _palabraRecibida != null)) {
|
||||
@@ -160,6 +177,10 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
datosFase?['tiempoDebateSegundos'] as int?,
|
||||
primerTurnoNombre:
|
||||
datosFase?['primerTurnoNombre'] as String?,
|
||||
partidaId: _partidaId ?? context.read<ServicioNearby>().roomId,
|
||||
pistaCategoria: _pistaCategoria,
|
||||
jugadores: List.unmodifiable(_jugadores),
|
||||
jugadoresControlados: List.unmodifiable(_jugadoresControlados),
|
||||
onSolicitarVotacion: () {
|
||||
final nearby = context.read<ServicioNearby>();
|
||||
if (nearby.hostEndpointId != null) {
|
||||
@@ -182,6 +203,8 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
||||
builder: (_) => PantallaVotacionCliente(
|
||||
jugadores: _jugadores,
|
||||
jugadoresControlados: List.unmodifiable(_jugadoresControlados),
|
||||
partidaId: _partidaId ?? context.read<ServicioNearby>().roomId,
|
||||
pistaCategoria: _pistaCategoria,
|
||||
onVotos: (votos) {
|
||||
final nearby = context.read<ServicioNearby>();
|
||||
if (nearby.hostEndpointId != null) {
|
||||
|
||||
@@ -2,6 +2,8 @@ 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/pantallas/pantalla_notas_online.dart';
|
||||
import 'package:farolero/pantallas/pantalla_revision_palabra.dart';
|
||||
import 'package:farolero/servicios/servicio_nearby.dart';
|
||||
import 'package:farolero/tema/tema_app.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -12,12 +14,16 @@ import 'package:provider/provider.dart';
|
||||
class PantallaVotacionCliente extends StatefulWidget {
|
||||
final List<Jugador> jugadores;
|
||||
final List<JugadorInicioPartida> jugadoresControlados;
|
||||
final String? partidaId;
|
||||
final String? pistaCategoria;
|
||||
final Function(Map<String, String> votos) onVotos;
|
||||
|
||||
const PantallaVotacionCliente({
|
||||
super.key,
|
||||
required this.jugadores,
|
||||
this.jugadoresControlados = const [],
|
||||
this.partidaId,
|
||||
this.pistaCategoria,
|
||||
required this.onVotos,
|
||||
});
|
||||
|
||||
@@ -75,6 +81,35 @@ class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
|
||||
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: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -127,7 +162,14 @@ class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
|
||||
);
|
||||
}
|
||||
|
||||
bool get _puedeAbrirNotas {
|
||||
return widget.partidaId != null &&
|
||||
widget.jugadores.isNotEmpty &&
|
||||
widget.jugadoresControlados.isNotEmpty;
|
||||
}
|
||||
|
||||
Widget _buildResultado(BuildContext context, Map<String, dynamic> resultado) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final eliminadoId = resultado['eliminadoId'] as String?;
|
||||
final eliminadoNombre = resultado['eliminadoNombre'] as String? ?? '?';
|
||||
final eraImpostor = resultado['eraImpostor'] as bool? ?? false;
|
||||
@@ -153,6 +195,35 @@ class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
|
||||
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: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
|
||||
Reference in New Issue
Block a user