fix: multidispositivo - Random seguro + gestor host + reacción clientes
- 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:
196
SPEC.md
Normal file
196
SPEC.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Farolero — SDD: Multidispositivo completo + Impostor seguro
|
||||||
|
|
||||||
|
## 1. Explore
|
||||||
|
|
||||||
|
### Hallazgos
|
||||||
|
|
||||||
|
**Bug 1: El host funciona como juego en un solo móvil**
|
||||||
|
|
||||||
|
- En `pantalla_crear_partida.dart` → `_iniciarPartidaMulti()`, al pulsar "Iniciar":
|
||||||
|
1. Crea la partida localmente ✅
|
||||||
|
2. Envía palabras a clientes via Nearby ✅
|
||||||
|
3. Navega a `PantallaVerPalabra` ❌
|
||||||
|
- `PantallaVerPalabra` está diseñada para juego en un solo móvil (muestra TODOS los jugadores en lista)
|
||||||
|
- El host no necesita ver palabras — es el gestor, no un jugador
|
||||||
|
|
||||||
|
**Bug 2: Clientes no se enteran de que el juego ha started**
|
||||||
|
|
||||||
|
- `ServicioNearby` procesa `partidaInicio` y actualiza `_palabraRecibida` / `_soyImpostor` ✅
|
||||||
|
- Llama a `notifyListeners()` ✅
|
||||||
|
- Pero `PantallaUnirse` → `_buildPantallaEspera` no escucha cambios de `ServicioNearby`
|
||||||
|
- Se queda infinamente en "Esperando al host..." aunque la partida ya haya started
|
||||||
|
|
||||||
|
**Bug 3: Random de impostores predecible**
|
||||||
|
|
||||||
|
- `EstadoJuego.crearPartida()` usa `Random()` con seed del sistema (determinista)
|
||||||
|
- `indices.shuffle(rng)` mezcla un array [0,1,2,...,n-1] — la mezcla depende del estado inicial
|
||||||
|
- Si un atacante conoce el número de jugadores y el momento de creación, puede reconstruir la seed
|
||||||
|
- Solución: usar `Random.secure()` (CSPRNG) o al menos `Random.fromSecureRandom()`
|
||||||
|
|
||||||
|
### Restricciones
|
||||||
|
|
||||||
|
- Nearby Connections tiene límite de payloads (4KB por mensaje)
|
||||||
|
- Google Nearby Connections requiere Bluetooth + Location permissions en Android/iOS
|
||||||
|
- La palabra secreta NO debe enviarse en texto plano a la red ( Nearby es inseguro en teoría — cualquier dispositivo puede sniffear)
|
||||||
|
- Compatibilidad hacia atrás: modo un solo móvil no debe romperse
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Propose
|
||||||
|
|
||||||
|
### Cambio 1: Pantalla Gestor Host (multidispositivo)
|
||||||
|
|
||||||
|
El host en modo multi-device necesita una pantalla que:
|
||||||
|
- Muestre la lista de jugadores conectados y sus estados (listo para debate/votación)
|
||||||
|
- Permita avanzar de fase (debate → votación → resultado)
|
||||||
|
- NO muestre palabras a nadie
|
||||||
|
- Gestione la coordinación de la partida completa
|
||||||
|
|
||||||
|
**Pantallas afectadas/creadas:**
|
||||||
|
- Nueva: `pantalla_gestor_host.dart`
|
||||||
|
- Modificar: `pantalla_crear_partida.dart` (onIniciar → navegar a gestor)
|
||||||
|
- Modificar: `pantalla_lobby_host.dart` (el QR se queda ahí)
|
||||||
|
|
||||||
|
### Cambio 2: Reacción de clientes al inicio de partida
|
||||||
|
|
||||||
|
Los clientes deben:
|
||||||
|
- Escuchar cambios en `ServicioNearby` (registrar listener)
|
||||||
|
- Cuando llega `partidaInicio`, navegar automáticamente a la pantalla de ver su palabra
|
||||||
|
- Crear pantalla dedicada para cliente: `pantalla_palabra_cliente.dart`
|
||||||
|
|
||||||
|
**Pantallas afectadas/creadas:**
|
||||||
|
- Nueva: `pantalla_palabra_cliente.dart`
|
||||||
|
- Nueva: `pantalla_debate_cliente.dart`
|
||||||
|
- Nueva: `pantalla_votacion_cliente.dart`
|
||||||
|
- Modificar: `pantalla_unirse.dart` (registrar listener y reaccionar)
|
||||||
|
|
||||||
|
### Cambio 3: Impostor con Random seguro
|
||||||
|
|
||||||
|
Reemplazar `Random()` por `Random.secure()` en `EstadoJuego.crearPartida()`.
|
||||||
|
|
||||||
|
**Archivos afectados:**
|
||||||
|
- `lib/estado/estado_juego.dart`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Spec
|
||||||
|
|
||||||
|
### 3.1 PantallaGestorHost
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
```dart
|
||||||
|
class PantallaGestorHost extends StatelessWidget {
|
||||||
|
final VoidCallback onPartidaFin;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Responsabilidades:**
|
||||||
|
- Escuchar cambios de `EstadoJuego` (fase actual)
|
||||||
|
- Escuchar cambios de `ServicioNearby` (desconexiones)
|
||||||
|
- Botones para avanzar de fase
|
||||||
|
- Mostrar estado de cada jugador (en debate, votando, eliminado)
|
||||||
|
- NO mostrar palabras a nadie
|
||||||
|
|
||||||
|
**Estados:**
|
||||||
|
- `debate`: muestra timer si hay tiempo configurado, botón "Iniciar votación"
|
||||||
|
- `votacion`: muestra quién ha votado, botón "Ver resultados" cuando todos voted
|
||||||
|
- `resultado`: muestra quién fue eliminado y si era impostor
|
||||||
|
- `adivinanza`: el impostor剩余 intenta adivinar
|
||||||
|
- `finPartida`: muestra ganador
|
||||||
|
|
||||||
|
### 3.2 PantallaPalabraCliente
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
```dart
|
||||||
|
class PantallaPalabraCliente extends StatelessWidget {
|
||||||
|
final String palabra;
|
||||||
|
final bool esImpostor;
|
||||||
|
final String? pistaCategoria;
|
||||||
|
final VoidCallback onVisto;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Responsabilidades:**
|
||||||
|
- Mostrar la palabra solo al jugador correspondiente
|
||||||
|
- Si es impostor: mostrar pista de categoría (si está activa)
|
||||||
|
- Mantener pulsado para revelar (mismo UX que `_PantallaRevelarPalabra`)
|
||||||
|
- Al pulsar "Ya la he visto" → enviar `listo` al host via Nearby
|
||||||
|
- Esperar al host que inicie el debate
|
||||||
|
|
||||||
|
### 3.3 Sincronización de fases
|
||||||
|
|
||||||
|
**Protocolo de mensajes Via Nearby:**
|
||||||
|
|
||||||
|
```
|
||||||
|
HOST → CLIENTE:
|
||||||
|
partidaInicio → { palabra, esImpostor, categoria, numJugadores }
|
||||||
|
fase → { fase: "debate"|"votacion"|"resultado"|"adivinanza"|"fin" }
|
||||||
|
votacionResultado → { eliminadoId, eliminadoNombre, eraImpostor, votos }
|
||||||
|
impostorAdivina → { acierto: bool }
|
||||||
|
partidaFin → { ganador: "jugadores"|"impostores" }
|
||||||
|
|
||||||
|
CLIENTE → HOST:
|
||||||
|
listo → {} (el cliente ha visto su palabra y está listo)
|
||||||
|
voto → { votoporId }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Random seguro para impostores
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// ANTES (predecible):
|
||||||
|
final rng = Random();
|
||||||
|
final indices = List.generate(jugadores.length, (i) => i);
|
||||||
|
indices.shuffle(rng);
|
||||||
|
|
||||||
|
// DESPUÉS (seguro):
|
||||||
|
final rng = Random.secure();
|
||||||
|
final impostoresElegidos = <int>{};
|
||||||
|
while (impostoresElegidos.length < numImpostores) {
|
||||||
|
impostoresElegidos.add(rng.nextInt(jugadores.length));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Tasks
|
||||||
|
|
||||||
|
### Epic 1: Impostor seguro
|
||||||
|
- [ ] **Task 1.1**: Cambiar `Random()` por `Random.secure()` en `EstadoJuego.crearPartida()`
|
||||||
|
|
||||||
|
### Epic 2: PantallaGestorHost
|
||||||
|
- [ ] **Task 2.1**: Crear `pantalla_gestor_host.dart` con estructura de fases
|
||||||
|
- [ ] **Task 2.2**: Implementar transición `lobby → gestor` en `pantalla_crear_partida.dart`
|
||||||
|
- [ ] **Task 2.3**: Implementar envío de cambio de fase a clientes
|
||||||
|
- [ ] **Task 2.4**: Gestionar desconexiones de jugadores durante la partida
|
||||||
|
|
||||||
|
### Epic 3: Cliente recibe inicio de partida
|
||||||
|
- [ ] **Task 3.1**: Registrar listener en `PantallaUnirse` para `partidaInicio`
|
||||||
|
- [ ] **Task 3.2**: Crear `pantalla_palabra_cliente.dart` (similar a `_PantallaRevelarPalabra` pero StatelessWidget)
|
||||||
|
- [ ] **Task 3.3**: Enviar mensaje `listo` al host cuando el cliente confirma que ha visto la palabra
|
||||||
|
|
||||||
|
### Epic 4: Cliente en fases de juego
|
||||||
|
- [ ] **Task 4.1**: Crear `pantalla_debate_cliente.dart` (muestra countdown, botón para solicitar votación)
|
||||||
|
- [ ] **Task 4.2**: Crear `pantalla_votacion_cliente.dart` (votar desde el móvil del cliente)
|
||||||
|
- [ ] **Task 4.3**: Cliente reacciona a `fase: votacion` y muestra pantalla de voto
|
||||||
|
- [ ] **Task 4.4**: Cliente reacciona a `fase: resultado` y muestra quién fue eliminado
|
||||||
|
- [ ] **Task 4.5**: Si el cliente es impostor剩余, mostrar pantalla de adivinanza
|
||||||
|
|
||||||
|
### Epic 5: Testing
|
||||||
|
- [ ] **Task 5.1**: Probar flujo completo multi-device (3+ jugadores)
|
||||||
|
- [ ] **Task 5.2**: Probar desconexión de un jugador durante la partida
|
||||||
|
- [ ] **Task 5.3**: Probar que en modo un solo móvil no se rompe nada
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Apply
|
||||||
|
|
||||||
|
*(Ejecución por agente OpenCode)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Verify
|
||||||
|
|
||||||
|
- [ ] Flujo multi-device: host inicia → clientes ven sus palabras → debate → votan → resultado
|
||||||
|
- [ ] Modo un solo móvil sigue funcionando exactamente igual que antes
|
||||||
|
- [ ] Impostores se seleccionan con Random.secure() — no reproducibles
|
||||||
|
- [ ] Sin regresiones en localized strings
|
||||||
@@ -33,8 +33,6 @@ class EstadoJuego extends ChangeNotifier {
|
|||||||
if (_banco == null) return;
|
if (_banco == null) return;
|
||||||
if (nombresJugadores.length < 3) return;
|
if (nombresJugadores.length < 3) return;
|
||||||
|
|
||||||
final rng = Random();
|
|
||||||
|
|
||||||
// Seleccionar palabra
|
// Seleccionar palabra
|
||||||
final palabra = _banco!.palabraAleatoria(config.categoria);
|
final palabra = _banco!.palabraAleatoria(config.categoria);
|
||||||
final categoriaReal =
|
final categoriaReal =
|
||||||
@@ -42,19 +40,18 @@ class EstadoJuego extends ChangeNotifier {
|
|||||||
|
|
||||||
// Crear jugadores
|
// Crear jugadores
|
||||||
final jugadores = nombresJugadores.asMap().entries.map((e) {
|
final jugadores = nombresJugadores.asMap().entries.map((e) {
|
||||||
return Jugador(
|
return Jugador(id: 'j${e.key}', nombre: e.value);
|
||||||
id: 'j${e.key}',
|
|
||||||
nombre: e.value,
|
|
||||||
);
|
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
// Asignar impostores aleatoriamente
|
// Asignar impostores usando Random seguro (no predecible)
|
||||||
final indices = List.generate(jugadores.length, (i) => i);
|
final rng = Random.secure();
|
||||||
indices.shuffle(rng);
|
final numImpostores = config.numImpostores.clamp(1, jugadores.length ~/ 3);
|
||||||
final numImpostores =
|
final impostoresElegidos = <int>{};
|
||||||
config.numImpostores.clamp(1, jugadores.length ~/ 3);
|
while (impostoresElegidos.length < numImpostores) {
|
||||||
for (int i = 0; i < numImpostores; i++) {
|
impostoresElegidos.add(rng.nextInt(jugadores.length));
|
||||||
jugadores[indices[i]].esImpostor = true;
|
}
|
||||||
|
for (final i in impostoresElegidos) {
|
||||||
|
jugadores[i].esImpostor = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asignar palabras
|
// Asignar palabras
|
||||||
@@ -124,15 +121,17 @@ class EstadoJuego extends ChangeNotifier {
|
|||||||
|
|
||||||
// Encontrar máximo
|
// Encontrar máximo
|
||||||
final maxVotos = conteo.values.reduce(max);
|
final maxVotos = conteo.values.reduce(max);
|
||||||
final masVotados =
|
final masVotados = conteo.entries
|
||||||
conteo.entries.where((e) => e.value == maxVotos).toList();
|
.where((e) => e.value == maxVotos)
|
||||||
|
.toList();
|
||||||
|
|
||||||
// En caso de empate, elegir aleatoriamente
|
// En caso de empate, elegir aleatoriamente (usar Random.secure para consistencia)
|
||||||
final rng = Random();
|
final rng = Random.secure();
|
||||||
final eliminadoId =
|
final eliminadoId = masVotados[rng.nextInt(masVotados.length)].key;
|
||||||
masVotados[rng.nextInt(masVotados.length)].key;
|
|
||||||
|
|
||||||
final eliminado = _partida!.jugadores.firstWhere((j) => j.id == eliminadoId);
|
final eliminado = _partida!.jugadores.firstWhere(
|
||||||
|
(j) => j.id == eliminadoId,
|
||||||
|
);
|
||||||
eliminado.eliminado = true;
|
eliminado.eliminado = true;
|
||||||
|
|
||||||
final resultado = ResultadoVotacion(
|
final resultado = ResultadoVotacion(
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
"impostors": "🎭 Impostores",
|
"impostors": "🎭 Impostores",
|
||||||
"impostorClue": "🔍 Pista para impostor",
|
"impostorClue": "🔍 Pista para impostor",
|
||||||
"impostorClueDescription": "El impostor conoce la categoría",
|
"impostorClueDescription": "El impostor conoce la categoría",
|
||||||
|
"debate": "🗣️ Debate",
|
||||||
"debateTime": "⏱️ Tiempo de debate",
|
"debateTime": "⏱️ Tiempo de debate",
|
||||||
"noLimit": "Sin límite",
|
"noLimit": "Sin límite",
|
||||||
"oneMin": "1 min",
|
"oneMin": "1 min",
|
||||||
@@ -232,6 +233,11 @@
|
|||||||
"licenses": "Licencias",
|
"licenses": "Licencias",
|
||||||
"scanToJoin": "Escanea el QR para unirte",
|
"scanToJoin": "Escanea el QR para unirte",
|
||||||
"connectedPlayers": "Jugadores conectados",
|
"connectedPlayers": "Jugadores conectados",
|
||||||
|
"hostGame": "Gestor de partida",
|
||||||
|
"waitingPlayersSeeWord": "Esperando que todos vean su palabra...",
|
||||||
|
"activePlayers": "Jugadores activos",
|
||||||
|
"playersVoted": "Han votado",
|
||||||
|
"waitingVoting": "Esperando que voten...",
|
||||||
"waitingForPlayers": "Esperando jugadores...",
|
"waitingForPlayers": "Esperando jugadores...",
|
||||||
"needMorePlayers": "Faltan {count} jugadores más",
|
"needMorePlayers": "Faltan {count} jugadores más",
|
||||||
"@needMorePlayers": {
|
"@needMorePlayers": {
|
||||||
@@ -255,5 +261,21 @@
|
|||||||
"searchingGames": "Buscando partidas cercanas...",
|
"searchingGames": "Buscando partidas cercanas...",
|
||||||
"noGamesFound": "No se encontraron partidas",
|
"noGamesFound": "No se encontraron partidas",
|
||||||
"noGamesFoundHint": "Asegúrate de que el host tiene la sala abierta y estáis cerca",
|
"noGamesFoundHint": "Asegúrate de que el host tiene la sala abierta y estáis cerca",
|
||||||
"orScanQR": "¿No aparece? Escanea el QR del host"
|
"orScanQR": "¿No aparece? Escanea el QR del host",
|
||||||
|
"iveSeenIt": "Ya la he visto",
|
||||||
|
"clueIs": "La pista es: {category}",
|
||||||
|
"@clueIs": {
|
||||||
|
"placeholders": {
|
||||||
|
"category": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debatePhaseActive": "Fase de debate activa",
|
||||||
|
"debateInstructions": "Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.",
|
||||||
|
"solicitarVotacion": "Solicitar votación",
|
||||||
|
"votacionSolicitada": "Votación solicitada",
|
||||||
|
"whoDoYouThinkIsTheImpostor": "¿Quién es el impostor?",
|
||||||
|
"selectOnePlayer": "Selecciona a un jugador para votar",
|
||||||
|
"votar": "Votar"
|
||||||
}
|
}
|
||||||
@@ -327,6 +327,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'El impostor conoce la categoría'**
|
/// **'El impostor conoce la categoría'**
|
||||||
String get impostorClueDescription;
|
String get impostorClueDescription;
|
||||||
|
|
||||||
|
/// No description provided for @debate.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'🗣️ Debate'**
|
||||||
|
String get debate;
|
||||||
|
|
||||||
/// No description provided for @debateTime.
|
/// No description provided for @debateTime.
|
||||||
///
|
///
|
||||||
/// In es, this message translates to:
|
/// In es, this message translates to:
|
||||||
@@ -1005,6 +1011,36 @@ abstract class AppLocalizations {
|
|||||||
/// **'Jugadores conectados'**
|
/// **'Jugadores conectados'**
|
||||||
String get connectedPlayers;
|
String get connectedPlayers;
|
||||||
|
|
||||||
|
/// No description provided for @hostGame.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Gestor de partida'**
|
||||||
|
String get hostGame;
|
||||||
|
|
||||||
|
/// No description provided for @waitingPlayersSeeWord.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Esperando que todos vean su palabra...'**
|
||||||
|
String get waitingPlayersSeeWord;
|
||||||
|
|
||||||
|
/// No description provided for @activePlayers.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Jugadores activos'**
|
||||||
|
String get activePlayers;
|
||||||
|
|
||||||
|
/// No description provided for @playersVoted.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Han votado'**
|
||||||
|
String get playersVoted;
|
||||||
|
|
||||||
|
/// No description provided for @waitingVoting.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Esperando que voten...'**
|
||||||
|
String get waitingVoting;
|
||||||
|
|
||||||
/// No description provided for @waitingForPlayers.
|
/// No description provided for @waitingForPlayers.
|
||||||
///
|
///
|
||||||
/// In es, this message translates to:
|
/// In es, this message translates to:
|
||||||
@@ -1106,6 +1142,60 @@ abstract class AppLocalizations {
|
|||||||
/// In es, this message translates to:
|
/// In es, this message translates to:
|
||||||
/// **'¿No aparece? Escanea el QR del host'**
|
/// **'¿No aparece? Escanea el QR del host'**
|
||||||
String get orScanQR;
|
String get orScanQR;
|
||||||
|
|
||||||
|
/// No description provided for @iveSeenIt.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Ya la he visto'**
|
||||||
|
String get iveSeenIt;
|
||||||
|
|
||||||
|
/// No description provided for @clueIs.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'La pista es: {category}'**
|
||||||
|
String clueIs(String category);
|
||||||
|
|
||||||
|
/// No description provided for @debatePhaseActive.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Fase de debate activa'**
|
||||||
|
String get debatePhaseActive;
|
||||||
|
|
||||||
|
/// No description provided for @debateInstructions.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.'**
|
||||||
|
String get debateInstructions;
|
||||||
|
|
||||||
|
/// No description provided for @solicitarVotacion.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Solicitar votación'**
|
||||||
|
String get solicitarVotacion;
|
||||||
|
|
||||||
|
/// No description provided for @votacionSolicitada.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Votación solicitada'**
|
||||||
|
String get votacionSolicitada;
|
||||||
|
|
||||||
|
/// No description provided for @whoDoYouThinkIsTheImpostor.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'¿Quién es el impostor?'**
|
||||||
|
String get whoDoYouThinkIsTheImpostor;
|
||||||
|
|
||||||
|
/// No description provided for @selectOnePlayer.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Selecciona a un jugador para votar'**
|
||||||
|
String get selectOnePlayer;
|
||||||
|
|
||||||
|
/// No description provided for @votar.
|
||||||
|
///
|
||||||
|
/// In es, this message translates to:
|
||||||
|
/// **'Votar'**
|
||||||
|
String get votar;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'المنتحل يعرف الفئة';
|
String get impostorClueDescription => 'المنتحل يعرف الفئة';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ وقت النقاش';
|
String get debateTime => '⏱️ وقت النقاش';
|
||||||
|
|
||||||
@@ -477,6 +480,21 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -531,4 +549,34 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsCa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'L\'impostor coneix la categoria';
|
String get impostorClueDescription => 'L\'impostor coneix la categoria';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Temps de debat';
|
String get debateTime => '⏱️ Temps de debat';
|
||||||
|
|
||||||
@@ -480,6 +483,21 @@ class AppLocalizationsCa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -534,4 +552,34 @@ class AppLocalizationsCa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'Der Hochstapler kennt die Kategorie';
|
String get impostorClueDescription => 'Der Hochstapler kennt die Kategorie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Diskussionszeit';
|
String get debateTime => '⏱️ Diskussionszeit';
|
||||||
|
|
||||||
@@ -483,6 +486,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -537,4 +555,34 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'The impostor knows the category';
|
String get impostorClueDescription => 'The impostor knows the category';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Discussion time';
|
String get debateTime => '⏱️ Discussion time';
|
||||||
|
|
||||||
@@ -478,6 +481,21 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Connected players';
|
String get connectedPlayers => 'Connected players';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Waiting for players...';
|
String get waitingForPlayers => 'Waiting for players...';
|
||||||
|
|
||||||
@@ -531,4 +549,34 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => 'Not showing up? Scan the host\'s QR code';
|
String get orScanQR => 'Not showing up? Scan the host\'s QR code';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'El impostor conoce la categoría';
|
String get impostorClueDescription => 'El impostor conoce la categoría';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Tiempo de debate';
|
String get debateTime => '⏱️ Tiempo de debate';
|
||||||
|
|
||||||
@@ -479,6 +482,21 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -533,4 +551,34 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ class AppLocalizationsEu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'Inpostoreak kategoria ezagutzen du';
|
String get impostorClueDescription => 'Inpostoreak kategoria ezagutzen du';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Eztabaida-denbora';
|
String get debateTime => '⏱️ Eztabaida-denbora';
|
||||||
|
|
||||||
@@ -482,6 +485,21 @@ class AppLocalizationsEu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -536,4 +554,34 @@ class AppLocalizationsEu extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'L\'imposteur connaît la catégorie';
|
String get impostorClueDescription => 'L\'imposteur connaît la catégorie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Temps de débat';
|
String get debateTime => '⏱️ Temps de débat';
|
||||||
|
|
||||||
@@ -480,6 +483,21 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -534,4 +552,34 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'धोखेबाज़ को श्रेणी पता होगी';
|
String get impostorClueDescription => 'धोखेबाज़ को श्रेणी पता होगी';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ बहस का समय';
|
String get debateTime => '⏱️ बहस का समय';
|
||||||
|
|
||||||
@@ -479,6 +482,21 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -533,4 +551,34 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'L\'impostore conosce la categoria';
|
String get impostorClueDescription => 'L\'impostore conosce la categoria';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Tempo di discussione';
|
String get debateTime => '⏱️ Tempo di discussione';
|
||||||
|
|
||||||
@@ -480,6 +483,21 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -534,4 +552,34 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'インポスターにカテゴリーが表示されます';
|
String get impostorClueDescription => 'インポスターにカテゴリーが表示されます';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ 議論の時間';
|
String get debateTime => '⏱️ 議論の時間';
|
||||||
|
|
||||||
@@ -477,6 +480,21 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -531,4 +549,34 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => '임포스터가 카테고리를 알 수 있습니다';
|
String get impostorClueDescription => '임포스터가 카테고리를 알 수 있습니다';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ 토론 시간';
|
String get debateTime => '⏱️ 토론 시간';
|
||||||
|
|
||||||
@@ -477,6 +480,21 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -531,4 +549,34 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'De bedrieger kent de categorie';
|
String get impostorClueDescription => 'De bedrieger kent de categorie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Debattijd';
|
String get debateTime => '⏱️ Debattijd';
|
||||||
|
|
||||||
@@ -480,6 +483,21 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -534,4 +552,34 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'Oszust zna kategorię';
|
String get impostorClueDescription => 'Oszust zna kategorię';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Czas debaty';
|
String get debateTime => '⏱️ Czas debaty';
|
||||||
|
|
||||||
@@ -480,6 +483,21 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -534,4 +552,34 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'O impostor conhece a categoria';
|
String get impostorClueDescription => 'O impostor conhece a categoria';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Tempo de debate';
|
String get debateTime => '⏱️ Tempo de debate';
|
||||||
|
|
||||||
@@ -481,6 +484,21 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -535,4 +553,34 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'Самозванец знает категорию';
|
String get impostorClueDescription => 'Самозванец знает категорию';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Время обсуждения';
|
String get debateTime => '⏱️ Время обсуждения';
|
||||||
|
|
||||||
@@ -480,6 +483,21 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -534,4 +552,34 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => 'Sahtekar kategoriyi bilir';
|
String get impostorClueDescription => 'Sahtekar kategoriyi bilir';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ Tartışma süresi';
|
String get debateTime => '⏱️ Tartışma süresi';
|
||||||
|
|
||||||
@@ -479,6 +482,21 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -533,4 +551,34 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get impostorClueDescription => '冒牌者可以知道分类';
|
String get impostorClueDescription => '冒牌者可以知道分类';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debate => '🗣️ Debate';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get debateTime => '⏱️ 讨论时间';
|
String get debateTime => '⏱️ 讨论时间';
|
||||||
|
|
||||||
@@ -476,6 +479,21 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get connectedPlayers => 'Jugadores conectados';
|
String get connectedPlayers => 'Jugadores conectados';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hostGame => 'Gestor de partida';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingPlayersSeeWord => 'Esperando que todos vean su palabra...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get activePlayers => 'Jugadores activos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get playersVoted => 'Han votado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingVoting => 'Esperando que voten...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get waitingForPlayers => 'Esperando jugadores...';
|
String get waitingForPlayers => 'Esperando jugadores...';
|
||||||
|
|
||||||
@@ -530,6 +548,36 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
String get orScanQR => '¿No aparece? Escanea el QR del host';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get iveSeenIt => 'Ya la he visto';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String clueIs(String category) {
|
||||||
|
return 'La pista es: $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debatePhaseActive => 'Fase de debate activa';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get debateInstructions =>
|
||||||
|
'Hablad entre vosotros y decid quién creéis que es el impostor. Cuando estéis listos, solicitad la votación.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get solicitarVotacion => 'Solicitar votación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votacionSolicitada => 'Votación solicitada';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get whoDoYouThinkIsTheImpostor => '¿Quién es el impostor?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selectOnePlayer => 'Selecciona a un jugador para votar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get votar => 'Votar';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
|
/// The translations for Chinese, as used in Taiwan (`zh_TW`).
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import '../modelos/partida.dart';
|
|||||||
import '../servicios/servicio_nearby.dart';
|
import '../servicios/servicio_nearby.dart';
|
||||||
import '../servicios/servicio_permisos.dart';
|
import '../servicios/servicio_permisos.dart';
|
||||||
import '../tema/tema_app.dart';
|
import '../tema/tema_app.dart';
|
||||||
|
import 'pantalla_gestor_host.dart';
|
||||||
import 'pantalla_lobby_host.dart';
|
import 'pantalla_lobby_host.dart';
|
||||||
|
import 'pantalla_principal.dart';
|
||||||
import 'pantalla_ver_palabra.dart';
|
import 'pantalla_ver_palabra.dart';
|
||||||
|
|
||||||
class PantallaCrearPartida extends StatefulWidget {
|
class PantallaCrearPartida extends StatefulWidget {
|
||||||
@@ -30,23 +32,28 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
|
|
||||||
int get _maxImpostores => (_jugadores.length / 3).floor().clamp(1, 4);
|
int get _maxImpostores => (_jugadores.length / 3).floor().clamp(1, 4);
|
||||||
|
|
||||||
List<String> _etiquetasTiempo(AppLocalizations l10n) =>
|
List<String> _etiquetasTiempo(AppLocalizations l10n) => [
|
||||||
[l10n.noLimit, l10n.oneMin, l10n.twoMin, l10n.threeMin, l10n.fiveMin];
|
l10n.noLimit,
|
||||||
|
l10n.oneMin,
|
||||||
|
l10n.twoMin,
|
||||||
|
l10n.threeMin,
|
||||||
|
l10n.fiveMin,
|
||||||
|
];
|
||||||
|
|
||||||
void _agregarJugador() {
|
void _agregarJugador() {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final nombre = _controladorNombre.text.trim();
|
final nombre = _controladorNombre.text.trim();
|
||||||
if (nombre.isEmpty) return;
|
if (nombre.isEmpty) return;
|
||||||
if (_jugadores.contains(nombre)) {
|
if (_jugadores.contains(nombre)) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(l10n.playerAlreadyExists)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.playerAlreadyExists)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_jugadores.length >= 20) {
|
if (_jugadores.length >= 20) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(l10n.maxPlayersReached)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.maxPlayersReached)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -76,9 +83,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_jugadores.length < 3) {
|
if (_jugadores.length < 3) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(
|
||||||
SnackBar(content: Text(l10n.minPlayersRequired)),
|
context,
|
||||||
);
|
).showSnackBar(SnackBar(content: Text(l10n.minPlayersRequired)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +113,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
if (!permisosOk) {
|
if (!permisosOk) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Se necesitan permisos de Bluetooth y ubicación')),
|
const SnackBar(
|
||||||
|
content: Text('Se necesitan permisos de Bluetooth y ubicación'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -125,7 +134,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
if (!ok) {
|
if (!ok) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('No se pudo crear la sala. Verifica Bluetooth.')),
|
const SnackBar(
|
||||||
|
content: Text('No se pudo crear la sala. Verifica Bluetooth.'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -163,7 +174,8 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
final jugadorNearby = nearby.jugadores[i];
|
final jugadorNearby = nearby.jugadores[i];
|
||||||
// El jugador [0] es el host, los de nearby son [1..n]
|
// El jugador [0] es el host, los de nearby son [1..n]
|
||||||
final jugadorPartida = partida.jugadores[i + 1];
|
final jugadorPartida = partida.jugadores[i + 1];
|
||||||
impostores[jugadorNearby.endpointId] = jugadorPartida.esImpostor;
|
impostores[jugadorNearby.endpointId] =
|
||||||
|
jugadorPartida.esImpostor;
|
||||||
}
|
}
|
||||||
|
|
||||||
nearby.enviarInicioPartida(
|
nearby.enviarInicioPartida(
|
||||||
@@ -174,7 +186,19 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
|
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (_) => const PantallaVerPalabra()),
|
MaterialPageRoute(
|
||||||
|
builder: (_) => PantallaGestorHost(
|
||||||
|
onPartidaFin: () {
|
||||||
|
estado.limpiar();
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => const PantallaPrincipal(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -241,8 +265,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.gameMode,
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleLarge),
|
l10n.gameMode,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
SegmentedButton<bool>(
|
SegmentedButton<bool>(
|
||||||
segments: [
|
segments: [
|
||||||
@@ -275,8 +301,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.category,
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleLarge),
|
l10n.category,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
@@ -288,7 +316,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
items: categorias.map((c) {
|
items: categorias.map((c) {
|
||||||
return DropdownMenuItem(
|
return DropdownMenuItem(
|
||||||
value: c,
|
value: c,
|
||||||
child: Text(BancoPalabras.nombreBonitoCategoria(c, l10n)),
|
child: Text(
|
||||||
|
BancoPalabras.nombreBonitoCategoria(c, l10n),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (v) => setState(() => _categoria = v!),
|
onChanged: (v) => setState(() => _categoria = v!),
|
||||||
@@ -310,10 +340,14 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.playersCount(_jugadores.length),
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleLarge),
|
l10n.playersCount(_jugadores.length),
|
||||||
Text(l10n.playersRangeHint,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
style: Theme.of(context).textTheme.bodyMedium),
|
),
|
||||||
|
Text(
|
||||||
|
l10n.playersRangeHint,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -342,13 +376,17 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundColor: TemaApp.colorTarjeta,
|
backgroundColor: TemaApp.colorTarjeta,
|
||||||
child: Text('${e.key + 1}',
|
child: Text(
|
||||||
style:
|
'${e.key + 1}',
|
||||||
const TextStyle(color: TemaApp.colorTexto)),
|
style: const TextStyle(color: TemaApp.colorTexto),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
title: Text(e.value),
|
title: Text(e.value),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.close, color: TemaApp.colorAcento),
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: TemaApp.colorAcento,
|
||||||
|
),
|
||||||
onPressed: () => _eliminarJugador(e.key),
|
onPressed: () => _eliminarJugador(e.key),
|
||||||
),
|
),
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -367,8 +405,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.configuration,
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleLarge),
|
l10n.configuration,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// Número de impostores
|
// Número de impostores
|
||||||
@@ -384,10 +424,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
: null,
|
: null,
|
||||||
icon: const Icon(Icons.remove_circle_outline),
|
icon: const Icon(Icons.remove_circle_outline),
|
||||||
),
|
),
|
||||||
Text('$_numImpostores',
|
Text(
|
||||||
style: Theme.of(context)
|
'$_numImpostores',
|
||||||
.textTheme
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
.titleLarge),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: _numImpostores < _maxImpostores
|
onPressed: _numImpostores < _maxImpostores
|
||||||
? () => setState(() => _numImpostores++)
|
? () => setState(() => _numImpostores++)
|
||||||
@@ -404,8 +444,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
title: Text(l10n.impostorClue),
|
title: Text(l10n.impostorClue),
|
||||||
subtitle: Text(l10n.impostorClueDescription),
|
subtitle: Text(l10n.impostorClueDescription),
|
||||||
value: _pistaImpostor,
|
value: _pistaImpostor,
|
||||||
onChanged: (v) =>
|
onChanged: (v) => setState(() => _pistaImpostor = v),
|
||||||
setState(() => _pistaImpostor = v),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -423,8 +462,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
child: Text(etiquetas[i]),
|
child: Text(etiquetas[i]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (v) =>
|
onChanged: (v) => setState(() => _tiempoDebate = v),
|
||||||
setState(() => _tiempoDebate = v),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -439,7 +477,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 56,
|
height: 56,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: (_modoMultimovil || _jugadores.length >= 3) ? _iniciarPartida : null,
|
onPressed: (_modoMultimovil || _jugadores.length >= 3)
|
||||||
|
? _iniciarPartida
|
||||||
|
: null,
|
||||||
icon: const Icon(Icons.play_arrow),
|
icon: const Icon(Icons.play_arrow),
|
||||||
label: Text(l10n.startGame),
|
label: Text(l10n.startGame),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
|||||||
156
lib/pantallas/pantalla_debate_cliente.dart
Normal file
156
lib/pantallas/pantalla_debate_cliente.dart
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
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 el jugador durante la fase de debate (multidispositivo).
|
||||||
|
/// El cliente recibe el cambio de fase via Nearby y se navega aquí.
|
||||||
|
class PantallaDebateCliente extends StatefulWidget {
|
||||||
|
final int? tiempoDebateSegundos;
|
||||||
|
final VoidCallback onSolicitarVotacion;
|
||||||
|
|
||||||
|
const PantallaDebateCliente({
|
||||||
|
super.key,
|
||||||
|
this.tiempoDebateSegundos,
|
||||||
|
required this.onSolicitarVotacion,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PantallaDebateCliente> createState() => _PantallaDebateClienteState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
|
||||||
|
Timer? _timer;
|
||||||
|
int _segundosRestantes = 0;
|
||||||
|
bool _votacionSolicitada = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.tiempoDebateSegundos != null) {
|
||||||
|
_segundosRestantes = widget.tiempoDebateSegundos!;
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
if (_segundosRestantes > 0) {
|
||||||
|
setState(() => _segundosRestantes--);
|
||||||
|
} else {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatearTiempo(int segundos) {
|
||||||
|
final min = segundos ~/ 60;
|
||||||
|
final seg = segundos % 60;
|
||||||
|
return '${min.toString().padLeft(2, '0')}:${seg.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: TemaApp.colorFondo,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(l10n.debate),
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Spacer(),
|
||||||
|
|
||||||
|
// Timer
|
||||||
|
if (widget.tiempoDebateSegundos != null) ...[
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _segundosRestantes == 0
|
||||||
|
? TemaApp.colorAcento.withValues(alpha: 0.3)
|
||||||
|
: TemaApp.colorTarjeta,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_segundosRestantes == 0
|
||||||
|
? l10n.timeUp
|
||||||
|
: l10n.timeRemaining,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
_formatearTiempo(_segundosRestantes),
|
||||||
|
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: _segundosRestantes == 0
|
||||||
|
? TemaApp.colorAcento
|
||||||
|
: TemaApp.colorTexto,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
] else ...[
|
||||||
|
Text(
|
||||||
|
l10n.debatePhaseActive,
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Instrucciones
|
||||||
|
Text(
|
||||||
|
l10n.debateInstructions,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: TemaApp.colorTextoSecundario,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const Spacer(),
|
||||||
|
|
||||||
|
// Botón solicitar votación
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: _votacionSolicitada
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
setState(() => _votacionSolicitada = true);
|
||||||
|
widget.onSolicitarVotacion();
|
||||||
|
},
|
||||||
|
icon: Icon(_votacionSolicitada ? Icons.hourglass_empty : Icons.how_to_vote),
|
||||||
|
label: Text(
|
||||||
|
_votacionSolicitada
|
||||||
|
? l10n.votacionSolicitada
|
||||||
|
: l10n.solicitarVotacion,
|
||||||
|
),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: _votacionSolicitada
|
||||||
|
? TemaApp.colorTarjeta
|
||||||
|
: TemaApp.colorAcento,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
textStyle: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
514
lib/pantallas/pantalla_gestor_host.dart
Normal file
514
lib/pantallas/pantalla_gestor_host.dart
Normal file
@@ -0,0 +1,514 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:farolero/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../estado/estado_juego.dart';
|
||||||
|
import '../modelos/partida.dart';
|
||||||
|
import '../servicios/servicio_nearby.dart';
|
||||||
|
import '../tema/tema_app.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PantallaGestorHost extends StatefulWidget {
|
||||||
|
final VoidCallback onPartidaFin;
|
||||||
|
|
||||||
|
const PantallaGestorHost({super.key, required this.onPartidaFin});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PantallaGestorHost> createState() => _PantallaGestorHostState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PantallaGestorHostState extends State<PantallaGestorHost> {
|
||||||
|
Timer? _timer;
|
||||||
|
int _segundosRestantes = 0;
|
||||||
|
final Map<String, bool> _clientesListos = {};
|
||||||
|
final Map<String, String> _votosRecibidos = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_iniciarTemporizador();
|
||||||
|
_registrarListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _iniciarTemporizador() {
|
||||||
|
final estado = context.read<EstadoJuego>();
|
||||||
|
final tiempo = estado.partida?.config.tiempoDebateSegundos;
|
||||||
|
if (tiempo != null) {
|
||||||
|
_segundosRestantes = tiempo;
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||||
|
if (_segundosRestantes > 0) {
|
||||||
|
setState(() => _segundosRestantes--);
|
||||||
|
} else {
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registrarListeners() {
|
||||||
|
final nearby = context.read<ServicioNearby>();
|
||||||
|
nearby.onMensaje((endpointId, mensaje) {
|
||||||
|
if (mensaje.tipo == TipoMensaje.listo) {
|
||||||
|
setState(() => _clientesListos[endpointId] = true);
|
||||||
|
} else if (mensaje.tipo == TipoMensaje.voto) {
|
||||||
|
final votanteId = mensaje.datos['votanteId'] as String?;
|
||||||
|
final votoId = mensaje.datos['votoporId'] as String?;
|
||||||
|
if (votanteId != null && votoId != null) {
|
||||||
|
setState(() => _votosRecibidos[votanteId] = votoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatearTiempo(int segundos) {
|
||||||
|
final min = segundos ~/ 60;
|
||||||
|
final seg = segundos % 60;
|
||||||
|
return '${min.toString().padLeft(2, '0')}:${seg.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
void _avanzarAFase(FaseJuego fase) {
|
||||||
|
final estado = context.read<EstadoJuego>();
|
||||||
|
final nearby = context.read<ServicioNearby>();
|
||||||
|
|
||||||
|
switch (fase) {
|
||||||
|
case FaseJuego.debate:
|
||||||
|
estado.iniciarDebate();
|
||||||
|
nearby.enviarCambioFase('debate');
|
||||||
|
_iniciarTemporizador();
|
||||||
|
break;
|
||||||
|
case FaseJuego.votacion:
|
||||||
|
estado.iniciarVotacion();
|
||||||
|
nearby.enviarCambioFase('votacion');
|
||||||
|
_votosRecibidos.clear();
|
||||||
|
break;
|
||||||
|
case FaseJuego.resultado:
|
||||||
|
final resultado = estado.procesarVotacion();
|
||||||
|
if (resultado != null) {
|
||||||
|
nearby.enviarResultadoVotacion({
|
||||||
|
'eliminadoId': resultado.eliminadoId,
|
||||||
|
'eliminadoNombre': resultado.eliminadoNombre,
|
||||||
|
'eraImpostor': resultado.eraImpostor,
|
||||||
|
'votos': resultado.votos,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
final estado = context.watch<EstadoJuego>();
|
||||||
|
final nearby = context.watch<ServicioNearby>();
|
||||||
|
final partida = estado.partida;
|
||||||
|
|
||||||
|
if (partida == null) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(l10n.hostGame)),
|
||||||
|
body: const Center(child: Text('Error: Sin partida')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final numJugadores = partida.jugadores.length + 1;
|
||||||
|
final todosListos = _clientesListos.length >= numJugadores - 1;
|
||||||
|
final todosVotaron = _votosRecibidos.length >= numJugadores - 1;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(l10n.hostGame),
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () async {
|
||||||
|
await nearby.desconectar();
|
||||||
|
widget.onPartidaFin();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildFaseIndicator(context, partida.fase, l10n),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Expanded(
|
||||||
|
child: _buildContenidoFase(
|
||||||
|
context,
|
||||||
|
partida.fase,
|
||||||
|
l10n,
|
||||||
|
todosListos,
|
||||||
|
todosVotaron,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildBotonAccion(
|
||||||
|
context,
|
||||||
|
partida.fase,
|
||||||
|
l10n,
|
||||||
|
todosListos,
|
||||||
|
todosVotaron,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFaseIndicator(
|
||||||
|
BuildContext context,
|
||||||
|
FaseJuego fase,
|
||||||
|
AppLocalizations l10n,
|
||||||
|
) {
|
||||||
|
final fases = [
|
||||||
|
(FaseJuego.verPalabra, l10n.seeYourWord),
|
||||||
|
(FaseJuego.debate, l10n.debate),
|
||||||
|
(FaseJuego.votacion, l10n.voting),
|
||||||
|
(FaseJuego.resultado, l10n.result),
|
||||||
|
];
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: fases.map((e) {
|
||||||
|
final esActiva = fase == e.$1 || fase.index > e.$1.index;
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(right: 8),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: esActiva ? TemaApp.colorAcento : TemaApp.colorSuperficie,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
e.$2,
|
||||||
|
style: TextStyle(
|
||||||
|
color: esActiva ? Colors.white : TemaApp.colorTextoSecundario,
|
||||||
|
fontWeight: esActiva ? FontWeight.bold : FontWeight.normal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContenidoFase(
|
||||||
|
BuildContext context,
|
||||||
|
FaseJuego fase,
|
||||||
|
AppLocalizations l10n,
|
||||||
|
bool todosListos,
|
||||||
|
bool todosVotaron,
|
||||||
|
) {
|
||||||
|
final nearby = context.watch<ServicioNearby>();
|
||||||
|
|
||||||
|
switch (fase) {
|
||||||
|
case FaseJuego.verPalabra:
|
||||||
|
return _buildFaseVerPalabra(context, l10n, todosListos, nearby);
|
||||||
|
case FaseJuego.debate:
|
||||||
|
return _buildFaseDebate(context, l10n, nearby);
|
||||||
|
case FaseJuego.votacion:
|
||||||
|
return _buildFaseVotacion(context, l10n, todosVotaron, nearby);
|
||||||
|
default:
|
||||||
|
return const Center(child: Text('Fin de la partida'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFaseVerPalabra(
|
||||||
|
BuildContext context,
|
||||||
|
AppLocalizations l10n,
|
||||||
|
bool todosListos,
|
||||||
|
ServicioNearby nearby,
|
||||||
|
) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
l10n.waitingPlayersSeeWord,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
l10n.connectedPlayers,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildJugadorTile(nearby.miNombre ?? 'Host', true, false),
|
||||||
|
...nearby.jugadores.map(
|
||||||
|
(j) => _buildJugadorTile(
|
||||||
|
j.nombre,
|
||||||
|
false,
|
||||||
|
_clientesListos[j.endpointId] ?? false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (todosListos)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: TemaApp.colorVerde.withValues(alpha: 0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check_circle, color: TemaApp.colorVerde),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
l10n.allSeenStartDebate,
|
||||||
|
style: const TextStyle(color: TemaApp.colorVerde),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFaseDebate(
|
||||||
|
BuildContext context,
|
||||||
|
AppLocalizations l10n,
|
||||||
|
ServicioNearby nearby,
|
||||||
|
) {
|
||||||
|
final estado = context.read<EstadoJuego>();
|
||||||
|
final tiempo = estado.partida?.config.tiempoDebateSegundos;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (tiempo != null) ...[
|
||||||
|
Text(l10n.debate, style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _segundosRestantes == 0
|
||||||
|
? TemaApp.colorAcento.withValues(alpha: 0.3)
|
||||||
|
: TemaApp.colorTarjeta,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_segundosRestantes == 0
|
||||||
|
? l10n.timeUp
|
||||||
|
: l10n.timeRemaining,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
_formatearTiempo(_segundosRestantes),
|
||||||
|
style: Theme.of(context).textTheme.headlineLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
l10n.activePlayers,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: nearby.jugadores.length + 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return _buildJugadorTile(
|
||||||
|
nearby.miNombre ?? 'Host',
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final j = nearby.jugadores[index - 1];
|
||||||
|
return _buildJugadorTile(j.nombre, false, true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFaseVotacion(
|
||||||
|
BuildContext context,
|
||||||
|
AppLocalizations l10n,
|
||||||
|
bool todosVotaron,
|
||||||
|
ServicioNearby nearby,
|
||||||
|
) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(l10n.voting, style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: TemaApp.colorTarjeta,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
l10n.votesProgress(
|
||||||
|
_votosRecibidos.length,
|
||||||
|
nearby.jugadores.length + 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value:
|
||||||
|
_votosRecibidos.length /
|
||||||
|
(nearby.jugadores.length + 1),
|
||||||
|
backgroundColor: TemaApp.colorSuperficie,
|
||||||
|
valueColor: const AlwaysStoppedAnimation(
|
||||||
|
TemaApp.colorAcento,
|
||||||
|
),
|
||||||
|
minHeight: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
l10n.playersVoted,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: nearby.jugadores.length + 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final esHost = index == 0;
|
||||||
|
final nombre = esHost
|
||||||
|
? (nearby.miNombre ?? 'Host')
|
||||||
|
: nearby.jugadores[index - 1].nombre;
|
||||||
|
final haVotado =
|
||||||
|
esHost || _votosRecibidos.containsKey(nombre);
|
||||||
|
return _buildJugadorTile(nombre, esHost, haVotado);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (todosVotaron)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: TemaApp.colorVerde.withValues(alpha: 0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check_circle, color: TemaApp.colorVerde),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
l10n.allVoted,
|
||||||
|
style: const TextStyle(color: TemaApp.colorVerde),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildJugadorTile(String nombre, bool esHost, bool listo) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: listo
|
||||||
|
? TemaApp.colorVerde.withValues(alpha: 0.2)
|
||||||
|
: TemaApp.colorTarjeta,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(esHost ? '👑' : '🎭', style: const TextStyle(fontSize: 18)),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(child: Text(nombre)),
|
||||||
|
if (listo)
|
||||||
|
const Icon(Icons.check_circle, color: TemaApp.colorVerde, size: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBotonAccion(
|
||||||
|
BuildContext context,
|
||||||
|
FaseJuego fase,
|
||||||
|
AppLocalizations l10n,
|
||||||
|
bool todosListos,
|
||||||
|
bool todosVotaron,
|
||||||
|
) {
|
||||||
|
switch (fase) {
|
||||||
|
case FaseJuego.verPalabra:
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: todosListos
|
||||||
|
? () => _avanzarAFase(FaseJuego.debate)
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.forum),
|
||||||
|
label: Text(
|
||||||
|
todosListos
|
||||||
|
? l10n.allSeenStartDebate
|
||||||
|
: l10n.waitingPlayersSeeWord,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case FaseJuego.debate:
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () => _avanzarAFase(FaseJuego.votacion),
|
||||||
|
icon: const Icon(Icons.how_to_vote),
|
||||||
|
label: Text(l10n.goToVoting),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
case FaseJuego.votacion:
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: todosVotaron
|
||||||
|
? () => _avanzarAFase(FaseJuego.resultado)
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.visibility),
|
||||||
|
label: Text(todosVotaron ? l10n.revealResult : l10n.waitingVoting),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
169
lib/pantallas/pantalla_palabra_cliente.dart
Normal file
169
lib/pantallas/pantalla_palabra_cliente.dart
Normal 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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:farolero/l10n/generated/app_localizations.dart';
|
import 'package:farolero/l10n/generated/app_localizations.dart';
|
||||||
|
import '../modelos/jugador.dart';
|
||||||
import '../servicios/servicio_nearby.dart';
|
import '../servicios/servicio_nearby.dart';
|
||||||
import '../servicios/servicio_permisos.dart';
|
import '../servicios/servicio_permisos.dart';
|
||||||
import '../tema/tema_app.dart';
|
import '../tema/tema_app.dart';
|
||||||
|
import 'pantalla_palabra_cliente.dart';
|
||||||
|
import 'pantalla_debate_cliente.dart';
|
||||||
|
import 'pantalla_votacion_cliente.dart';
|
||||||
|
|
||||||
/// Pantalla para unirse a una partida multidispositivo.
|
/// Pantalla para unirse a una partida multidispositivo.
|
||||||
/// Flujo: nombre → discovery automático (lista de salas) → fallback QR
|
/// Flujo: nombre → discovery automático (lista de salas) → fallback QR
|
||||||
@@ -26,6 +30,110 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
|
|||||||
String? _error;
|
String? _error;
|
||||||
String? _salaSeleccionada;
|
String? _salaSeleccionada;
|
||||||
|
|
||||||
|
// Estado del juego recibido del host
|
||||||
|
String? _palabraRecibida;
|
||||||
|
bool _esImpostor = false;
|
||||||
|
String? _pistaCategoria;
|
||||||
|
final List<Jugador> _jugadores = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Registrar listener ANTES del primer build
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_registrarListenerPartida();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registrarListenerPartida() {
|
||||||
|
final nearby = context.read<ServicioNearby>();
|
||||||
|
nearby.onMensaje((endpointId, mensaje) {
|
||||||
|
if (mensaje.tipo == TipoMensaje.partidaInicio) {
|
||||||
|
// El host ha iniciado la partida — nos ha enviado nuestra palabra
|
||||||
|
setState(() {
|
||||||
|
_palabraRecibida = mensaje.datos['palabra'] as String?;
|
||||||
|
_esImpostor = mensaje.datos['esImpostor'] as bool? ?? false;
|
||||||
|
_pistaCategoria = mensaje.datos['categoria'] as String?;
|
||||||
|
});
|
||||||
|
// Navegar a pantalla de palabra del cliente
|
||||||
|
if (mounted && _palabraRecibida != null) {
|
||||||
|
_navegarAPalabra();
|
||||||
|
}
|
||||||
|
} else if (mensaje.tipo == TipoMensaje.fase) {
|
||||||
|
// El host cambia de fase — navegar a la pantalla correspondiente
|
||||||
|
final fase = mensaje.datos['fase'] as String?;
|
||||||
|
if (mounted && fase != null) {
|
||||||
|
_navegarSegunFase(fase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navegarAPalabra() {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => PantallaPalabraCliente(
|
||||||
|
palabra: _palabraRecibida ?? '',
|
||||||
|
esImpostor: _esImpostor,
|
||||||
|
pistaCategoria: _pistaCategoria,
|
||||||
|
onVisto: () {
|
||||||
|
// Enviar "listo" al host y volver a la espera
|
||||||
|
final nearby = context.read<ServicioNearby>();
|
||||||
|
if (nearby.hostEndpointId != null) {
|
||||||
|
nearby.enviarMensaje(
|
||||||
|
nearby.hostEndpointId!,
|
||||||
|
MensajeP2P(tipo: TipoMensaje.listo, datos: {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navegarSegunFase(String fase) {
|
||||||
|
switch (fase) {
|
||||||
|
case 'debate':
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => PantallaDebateCliente(
|
||||||
|
tiempoDebateSegundos: null,
|
||||||
|
onSolicitarVotacion: () {
|
||||||
|
final nearby = context.read<ServicioNearby>();
|
||||||
|
if (nearby.hostEndpointId != null) {
|
||||||
|
nearby.enviarMensaje(
|
||||||
|
nearby.hostEndpointId!,
|
||||||
|
MensajeP2P(tipo: TipoMensaje.ping, datos: {'solicitoVotacion': true}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'votacion':
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => PantallaVotacionCliente(
|
||||||
|
jugadores: _jugadores,
|
||||||
|
onVoto: (votoporId) {
|
||||||
|
final nearby = context.read<ServicioNearby>();
|
||||||
|
if (nearby.hostEndpointId != null) {
|
||||||
|
nearby.enviarMensaje(
|
||||||
|
nearby.hostEndpointId!,
|
||||||
|
MensajeP2P(tipo: TipoMensaje.voto, datos: {'votoporId': votoporId}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_nombreController.dispose();
|
_nombreController.dispose();
|
||||||
|
|||||||
112
lib/pantallas/pantalla_votacion_cliente.dart
Normal file
112
lib/pantallas/pantalla_votacion_cliente.dart
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:farolero/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:farolero/modelos/jugador.dart';
|
||||||
|
import 'package:farolero/tema/tema_app.dart';
|
||||||
|
|
||||||
|
/// Pantalla de votación para el cliente (multidispositivo).
|
||||||
|
/// El cliente recibe fase=votacion y ve esta pantalla para elegir a quién votar.
|
||||||
|
class PantallaVotacionCliente extends StatefulWidget {
|
||||||
|
final List<Jugador> jugadores;
|
||||||
|
final Function(String votoporId) onVoto;
|
||||||
|
|
||||||
|
const PantallaVotacionCliente({
|
||||||
|
super.key,
|
||||||
|
required this.jugadores,
|
||||||
|
required this.onVoto,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PantallaVotacionCliente> createState() => _PantallaVotacionClienteState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
|
||||||
|
String? _votoSeleccionado;
|
||||||
|
|
||||||
|
@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(
|
||||||
|
l10n.selectOnePlayer,
|
||||||
|
style: TextStyle(color: TemaApp.colorTextoSecundario),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: widget.jugadores.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final jugador = widget.jugadores[index];
|
||||||
|
final selected = _votoSeleccionado == jugador.id;
|
||||||
|
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: () {
|
||||||
|
setState(() => _votoSeleccionado = jugador.id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: _votoSeleccionado == null
|
||||||
|
? null
|
||||||
|
: () => widget.onVoto(_votoSeleccionado!),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user