fix: multidispositivo - Random seguro + gestor host + reacción clientes
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

- Random.secure() para selección de impostores (no predecible)
- Random.secure() también en desempate de votación
- Nueva PantallaGestorHost para coordinación multi-device
- Navegación: host va a gestor tras iniciar, no a pantalla de palabra
- PantallaPalabraCliente: cada jugador ve su palabra en su móvil
- PantallaDebateCliente: debate con timer y botón solicitar votación
- PantallaVotacionCliente: voto desde el móvil del cliente
- PantallaUnirse: listener que reacciona a partidaInicio y cambia de fase
- Protocolo: listo/voto/solicitoVotacion via Nearby hacia el host
- Nuevas cadenas l10n ES
This commit is contained in:
ShanaiaBot
2026-04-15 02:09:05 +02:00
parent 302cdf6f1a
commit eb2662f561
27 changed files with 2282 additions and 60 deletions

View File

@@ -0,0 +1,169 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:farolero/l10n/generated/app_localizations.dart';
import 'package:farolero/tema/tema_app.dart';
/// Pantalla que ve cada jugador cuando recibe su palabra (modo multidispositivo).
/// El cliente recibe la palabra via ServicioNearby y se navega aquí.
/// NO es la pantalla del host.
class PantallaPalabraCliente extends StatefulWidget {
final String palabra;
final bool esImpostor;
final String? pistaCategoria;
final VoidCallback onVisto;
const PantallaPalabraCliente({
super.key,
required this.palabra,
required this.esImpostor,
this.pistaCategoria,
required this.onVisto,
});
@override
State<PantallaPalabraCliente> createState() => _PantallaPalabraClienteState();
}
class _PantallaPalabraClienteState extends State<PantallaPalabraCliente> {
bool _palabraVisible = false;
Timer? _timer;
void _togglePalabra() {
setState(() => _palabraVisible = !_palabraVisible);
_timer?.cancel();
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
backgroundColor: TemaApp.colorFondo,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const Spacer(),
// Tarjeta de palabra
GestureDetector(
onTap: _togglePalabra,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 48, horizontal: 24),
decoration: BoxDecoration(
color: _palabraVisible
? TemaApp.colorAcento
: TemaApp.colorTarjeta,
borderRadius: BorderRadius.circular(24),
boxShadow: _palabraVisible
? [
BoxShadow(
color: TemaApp.colorAcento.withValues(alpha: 0.4),
blurRadius: 24,
spreadRadius: 2,
),
]
: null,
),
child: Column(
children: [
Icon(
_palabraVisible ? Icons.visibility : Icons.visibility_off,
color: _palabraVisible
? Colors.white
: TemaApp.colorTextoSecundario,
size: 32,
),
const SizedBox(height: 16),
Text(
_palabraVisible ? widget.palabra : '???',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: _palabraVisible
? Colors.white
: TemaApp.colorTextoSecundario,
),
),
],
),
),
),
const SizedBox(height: 16),
// Pista para impostores
if (widget.esImpostor && widget.pistaCategoria != null) ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: TemaApp.colorAcento.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.lightbulb, color: TemaApp.colorAcento),
const SizedBox(width: 8),
Flexible(
child: Text(
'🎭 ${l10n.clueIs(widget.pistaCategoria!)}',
style: const TextStyle(color: TemaApp.colorAcento),
),
),
],
),
),
const SizedBox(height: 8),
],
// Instrucciones
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
_palabraVisible
? 'Mantén la pantalla oculta. No la enseñes a nadie.'
: 'Toca para ver tu palabra',
textAlign: TextAlign.center,
style: TextStyle(
color: TemaApp.colorTextoSecundario,
fontSize: 14,
),
),
),
const Spacer(),
// Botón confirmar
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton.icon(
onPressed: () {
widget.onVisto();
},
icon: const Icon(Icons.check),
label: Text(l10n.iveSeenIt),
style: ElevatedButton.styleFrom(
backgroundColor: TemaApp.colorAcento,
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 16),
),
),
),
],
),
),
),
);
}
}