Compare commits

..

21 Commits

Author SHA1 Message Date
ShanaiaBot 553456836f chore: bump version to 1.1.41+46 [ci skip] 2026-05-12 21:51:20 +02:00
FreeTLab cee8c7c2ce Merge branch 'main' of https://git.freetimelab.es/FreeTLab/farolero
Build & Deploy Farolero / Análisis de código (push) Successful in 18s
Build & Deploy Farolero / Build APK + AAB release (push) Successful in 2m17s
2026-05-12 21:50:18 +02:00
FreeTLab 8f78652ac6 Firma de la aplicación como pide play store 2026-05-12 21:50:16 +02:00
ShanaiaBot bb4359656f chore: bump version to 1.1.40+45 [ci skip] 2026-05-12 01:37:36 +02:00
FreeTLab 90ada9099f Multitud de iconos más
Build & Deploy Farolero / Análisis de código (push) Successful in 14s
Build & Deploy Farolero / Build APK + AAB release (push) Successful in 1m46s
2026-05-12 01:36:41 +02:00
FreeTLab 158a7ae6a8 Iconografía básica 2026-05-12 01:20:19 +02:00
FreeTLab 5697c2a5a1 A ver si compila y publica en main
Build & Deploy Farolero / Análisis de código (push) Failing after 15s
Build & Deploy Farolero / Build APK + AAB release (push) Has been skipped
2026-05-12 01:11:59 +02:00
FreeTLab 7ee8c6ae2f Merge branch 'main' of https://git.freetimelab.es/FreeTLab/farolero 2026-05-12 01:11:02 +02:00
FreeTLab 1885c85de9 uso de imágenes y eliminación de innecesarias 2026-05-12 01:09:05 +02:00
FreeTLab a055ed808c más iconos 2026-05-12 00:44:33 +02:00
FreeTLab 4334e06b04 multiples iconografías 2026-05-12 00:40:40 +02:00
FreeTLab be255d5ea3 algunos iconos PRO 2026-05-12 00:11:43 +02:00
ShanaiaBot 50cf83826c chore: bump version to 1.1.39+44 [ci skip] 2026-05-11 23:22:04 +02:00
FreeTLab 532e3b84f1 correcciones
Build & Deploy Farolero / Análisis de código (push) Successful in 14s
Build & Deploy Farolero / Build APK + AAB release (push) Successful in 1m34s
2026-05-11 23:21:13 +02:00
FreeTLab e60fc27d6f Merge branch 'main' of https://git.freetimelab.es/FreeTLab/farolero
Build & Deploy Farolero / Análisis de código (push) Failing after 17s
Build & Deploy Farolero / Build APK + AAB release (push) Has been skipped
2026-05-11 23:16:49 +02:00
FreeTLab 4599678e77 refactorización de pantallas 2026-05-11 23:16:38 +02:00
ShanaiaBot 0ba1038170 chore: bump version to 1.1.38+43 [ci skip] 2026-05-11 22:06:44 +02:00
FreeTLab 1929d86689 corrección a un error
Build & Deploy Farolero / Análisis de código (push) Successful in 22s
Build & Deploy Farolero / Build APK + AAB release (push) Successful in 2m13s
2026-05-11 22:05:07 +02:00
FreeTLab 1bad40cbf5 Merge branch 'main' of https://git.freetimelab.es/FreeTLab/farolero
Build & Deploy Farolero / Análisis de código (push) Failing after 24s
Build & Deploy Farolero / Build APK + AAB release (push) Has been skipped
2026-05-11 21:58:19 +02:00
FreeTLab 3c5d98d6dd literales y unificación de resultados en los dos modos de juego 2026-05-11 21:57:46 +02:00
ShanaiaBot 82ebb71ff6 chore: bump version to 1.1.37+42 [ci skip] 2026-05-11 21:12:16 +02:00
126 changed files with 4038 additions and 2352 deletions
@@ -0,0 +1 @@
CYSATD23LTB4SAAAAAAAAAAAAA
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

+84
View File
@@ -0,0 +1,84 @@
# Inventario de iconos/assets premium pendientes — Farolero
Criterio: sustituir iconos Material/emoji/texto que tengan peso visual por ilustraciones premium transparentes, manteniendo texto/estado como Flutter real. Objetivo: assets WebP/PNG con alpha, tamaño ajustado al render real y peso controlado.
## Resumen técnico
- Assets totales de imagen: 122.
- Peso por carpetas: `assets/avatars` ~9.16 MB, `assets/ui` ~2.07 MB, `assets/app_icon` ~1.54 MB, `assets/medals` ~0.35 MB, `assets/rewards` ~0.18 MB.
- Assets no referenciados literalmente en código/pubspec: `assets/app_icon/farolero_app_icon.png` y `assets/ui/premium/*`.
- `assets/ui/premium/*` debe eliminarse del runtime si no se usa: son glows/overlays antiguos y de baja calidad frente al sistema `assets/ui/generated/*`.
- Se detectan ~190 usos de `Icons.*`; no todos deben sustituirse. Los de navegación/cierre/campos pueden seguir vectoriales. Los de acciones principales, fases, resultados y notas sí deberían ser assets generados.
## Reglas de tamaño/peso propuestas
| Tipo | Tamaño fuente generado | Tamaño runtime | Peso objetivo |
| --- | ---: | ---: | ---: |
| Icono de botón/acción | 512x512 | 128x128 o 160x160 WebP alpha | 12-35 KB |
| Icono de fase/tarjeta | 768x768 | 192x192 o 256x256 WebP alpha | 35-90 KB |
| Hero de pantalla | 1024x1024 | 512x512 WebP alpha | 70-160 KB |
| Fondo pantalla | 1440x2560 fuente | 900x1599 WebP/JPEG | 80-180 KB |
| Marco QR | 512x512 fuente | 192x192/256x256 WebP alpha | 40-100 KB |
| App icon | 1024x1024 PNG fuente | mantener fuente + generar mipmaps | fuente puede pesar 1-2 MB |
## Prioridad A — reemplazar ya
| Uso visual | Iconos/líneas detectadas | Asset recomendado | Runtime recomendado | Peso objetivo | Motivo |
| --- | --- | --- | --- | --- | --- |
| Notas | `Icons.edit_note`, `Icons.note`, `Icons.save` en `pantalla_debate.dart`, `pantalla_notas*.dart`, `pantalla_gestor_host.dart`, `pantalla_resultado_online.dart`, `pantalla_fin_partida_online.dart` | `assets/ui/generated/actions/action_notes_quill.webp` | 160x160 WebP alpha | 20-45 KB | El botón de notas se ve pobre; debe ser cuaderno/pergamino/pluma/farol premium. |
| Votación | `Icons.how_to_vote`, `Icons.person_search`, `Icons.gps_fixed` en voto/resultado/reglas | `assets/ui/generated/actions/action_vote_mask.webp` | 160x160 o 192x192 WebP alpha | 25-60 KB | Acción central de juego, ahora parece Material. |
| Ver palabra / revelar | `Icons.visibility`, `Icons.visibility_off`, `Icons.key`, `Icons.lock`, `Icons.search` | `assets/ui/generated/actions/action_reveal_word.webp` | 160x160 WebP alpha | 20-45 KB | Fase clave; debe verse como secreto/farol/carta sellada. |
| Impostor | `Icons.theater_comedy`, `Icons.psychology` | `assets/ui/generated/actions/action_impostor_mask.webp` | 192x192 WebP alpha | 35-75 KB | Se usa mucho y define identidad del juego. |
| Resultado/victoria | `Icons.emoji_events`, `Icons.celebration`, `Icons.mood`, `Icons.sentiment_dissatisfied` | `assets/ui/generated/actions/action_result_trophy.webp` | 192x192 WebP alpha | 35-75 KB | Pantallas de resultado necesitan impacto/gamificación. |
| Fuego/progreso | `Icons.local_fire_department`, emojis de fuego en medallas como fallback | `assets/ui/generated/actions/action_fire_badge.webp` | 160x160 WebP alpha | 20-45 KB | Mantener estética de farol/fuego sin emoji. |
## Prioridad B — reutilizar/generar si queda feo en pantalla
| Uso visual | Iconos/líneas detectadas | Asset recomendado | Runtime recomendado | Peso objetivo | Motivo |
| --- | --- | --- | --- | --- | --- |
| Debate | `Icons.forum`, `Icons.record_voice_over`, `Icons.chat_bubble` | `assets/ui/generated/actions/action_debate_voice.webp` | 160x160 WebP alpha | 20-45 KB | Fase frecuente; puede reutilizarse en reglas y host. |
| Multidispositivo | `Icons.devices`, `Icons.phone_android`, `Icons.bluetooth_searching`, `Icons.wifi_tethering`, `Icons.radar` | reutilizar/mejorar `join_lobby/signal_art.webp` o nuevo `action_multidevice_signal.webp` | 192x192 WebP alpha | 35-80 KB | Hay asset de señal, pero varios botones siguen con iconos Material. |
| Jugadores | `Icons.person`, `Icons.person_add`, `Icons.groups`, `Icons.person_off` | `action_players_token.webp` | 128x128 WebP alpha | 12-35 KB | En listas pequeñas puede bastar vectorial; en CTAs/tarjetas debería ser asset. |
| Historial | `Icons.history_rounded`, `Icons.arrow_forward_ios` | reutilizar `meta/history_ledger_art.webp` | 160x160 WebP alpha | ya existe 130 KB; quizá recomprimir a 70-90 KB | Ya hay asset, pero botones siguen con icono. |
| Ajustes/perfil | `Icons.settings_rounded`, `Icons.edit`, `Icons.alternate_email` | reutilizar `meta/settings_profile_art.webp` | 160x160 WebP alpha | ya existe 124 KB; quizá recomprimir a 60-90 KB | Útil en perfil/configuración. |
| Crear partida | `Icons.category`, `Icons.add`, `Icons.remove_circle_outline`, `Icons.add_circle_outline` | `action_create_game.webp` / reutilizar `create_game_header_art.webp` | 128-192 WebP alpha | 20-70 KB | Pantalla aún básica en varias zonas. |
## Prioridad C — mantener vectorial salvo que sea protagonista
- `Icons.close`, `Icons.arrow_back`, `Icons.chevron_right_rounded`, `Icons.arrow_forward`, `Icons.check`, `Icons.cancel` como controles pequeños pueden seguir vectoriales.
- En botones premium principales, si el icono ocupa mucho protagonismo, conviene usar asset ilustrado.
- En `TextField.prefixIcon`, usar vectorial es aceptable salvo pantallas premium donde el icono sea grande o repetido.
## Assets existentes a reutilizar antes de generar
| Asset | Tamaño | Peso | Estado |
| --- | ---: | ---: | --- |
| `assets/ui/generated/gameplay/notes_strategy_art.webp` | 256x256 | ~126 KB | Bueno como hero de notas; pesado para botón. Generar derivado `action_notes_quill.webp` 128/160 px. |
| `assets/ui/generated/meta/result_verdict_art.webp` | 256x256 | ~129 KB | Bueno para resultado; pesado como icono de botón. Crear derivado 160 px. |
| `assets/ui/generated/gameplay/gameplay_phase_emblem.webp` | 256x256 | ~113 KB | Reutilizable como fase genérica; no sustituye iconos específicos. |
| `assets/ui/generated/join_lobby/signal_art.webp` | 176x88 | ~129 KB | Calidad correcta pero peso alto para tamaño; recomprimir objetivo 45-80 KB. |
| `assets/ui/generated/meta/history_ledger_art.webp` | 256x256 | ~131 KB | Reutilizable; recomprimir si se usa como icono pequeño. |
| `assets/ui/generated/meta/settings_profile_art.webp` | 256x256 | ~124 KB | Reutilizable; recomprimir si se usa como icono pequeño. |
| `assets/ui/generated/create_game/create_game_header_art.webp` | 176x88 | ~122 KB | Peso alto para tamaño; recomprimir o generar iconos derivados. |
## Assets a retirar o no usar como runtime
| Asset/carpeta | Motivo |
| --- | --- |
| `assets/ui/premium/lantern_radial_glow.png` | 1024x1024, 285 KB, no referenciado; glow procedural/antiguo. |
| `assets/ui/premium/vote_danger_glow.png` | no referenciado; reemplazar por asset premium real si hace falta. |
| `assets/ui/premium/word_reveal_glow.png` | no referenciado; reemplazar por asset premium real si hace falta. |
| `assets/ui/premium/corner_orange_glow.png` | no referenciado; placeholder procedural. |
| `assets/ui/premium/timer_ring_glow.png` | no referenciado; si se quiere temporizador premium, generar ilustración específica. |
| `assets/ui/premium/card_sheen_overlay.png` | no referenciado; solo conservar si se integra conscientemente. |
| `assets/ui/premium/qr_frame_overlay.png` | no referenciado; el QR debe mantener zona blanca limpia. |
| `assets/ui/premium/sparks_overlay.png` | no referenciado; mejor usar efectos controlados o asset optimizado. |
## Plan de generación recomendado
1. Generar un kit pequeño `assets/ui/generated/actions/` con 8-10 iconos ilustrados transparentes.
2. Redimensionar/comprimir cada icono a 128/160/192 px según uso.
3. Añadir soporte en `BotonFarolero` y `TarjetaFaseFarolero` para `assetIconPath`, manteniendo `IconData` solo como fallback.
4. Migrar primero acciones visibles: notas, votar, ver palabra, impostor, resultado, fuego.
5. Reutilizar los mismos assets en host/cliente/local para no multiplicar peso.
6. Eliminar `assets/ui/premium/` del runtime si sigue sin referencias reales.
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "المنتحل", "appTitle": "المنتحل",
"subtitle": "لعبة تخمين اجتماعية", "subtitle": "لعبة تخمين اجتماعية",
"loadingWords": "جارٍ تحميل الكلمات...", "loadingWords": "جارٍ تحميل الكلمات...",
"matchRewards": "مكافآت المباراة",
"newMedals": "ميداليات جديدة",
"noNewMedalsKeepFire": "لا توجد ميداليات جديدة هذه المرة. واصل إشعال حماسك.",
"calculatingRewards": "جارٍ حساب المكافآت...",
"fireLabel": "النار",
"playersRange": "3-20 لاعبًا • بدون إنترنت", "playersRange": "3-20 لاعبًا • بدون إنترنت",
"createGame": "إنشاء لعبة", "createGame": "إنشاء لعبة",
"joinGame": "الانضمام إلى لعبة", "joinGame": "الانضمام إلى لعبة",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "تصويت {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "يبدأ {name} بقول كلمته.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "L'Impostor", "appTitle": "L'Impostor",
"subtitle": "Joc de deducció social", "subtitle": "Joc de deducció social",
"loadingWords": "Carregant paraules...", "loadingWords": "Carregant paraules...",
"matchRewards": "Recompenses de partida",
"newMedals": "Noves medalles",
"noNewMedalsKeepFire": "Sense medalles noves aquesta vegada. Continua acumulant foc.",
"calculatingRewards": "Calculant recompenses...",
"fireLabel": "Foc",
"playersRange": "3-20 jugadors • Sense internet", "playersRange": "3-20 jugadors • Sense internet",
"createGame": "Crear partida", "createGame": "Crear partida",
"joinGame": "Unir-se a partida", "joinGame": "Unir-se a partida",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Vot de {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "Comença {name} dient la seva paraula.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Farolero", "appTitle": "Farolero",
"subtitle": "Soziales Deduktionsspiel", "subtitle": "Soziales Deduktionsspiel",
"loadingWords": "Wörter werden geladen...", "loadingWords": "Wörter werden geladen...",
"matchRewards": "Spielbelohnungen",
"newMedals": "Neue Medaillen",
"noNewMedalsKeepFire": "Diesmal keine neuen Medaillen. Baue dein Feuer weiter aus.",
"calculatingRewards": "Belohnungen werden berechnet...",
"fireLabel": "Feuer",
"playersRange": "3-20 Spieler • Ohne Internet", "playersRange": "3-20 Spieler • Ohne Internet",
"createGame": "Spiel erstellen", "createGame": "Spiel erstellen",
"joinGame": "Spiel beitreten", "joinGame": "Spiel beitreten",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Stimme von {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} beginnt und sagt sein/ihr Wort.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Farolero", "appTitle": "Farolero",
"subtitle": "Social deduction game", "subtitle": "Social deduction game",
"loadingWords": "Loading words...", "loadingWords": "Loading words...",
"matchRewards": "Game rewards",
"newMedals": "New medals",
"noNewMedalsKeepFire": "No new medals this time. Keep building your fire.",
"calculatingRewards": "Calculating rewards...",
"fireLabel": "Fire",
"playersRange": "3-20 players • No internet needed", "playersRange": "3-20 players • No internet needed",
"createGame": "Create game", "createGame": "Create game",
"joinGame": "Join game", "joinGame": "Join game",
@@ -328,5 +333,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Vote from {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} starts by saying their word.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Farolero", "appTitle": "Farolero",
"subtitle": "Juego de deducción social", "subtitle": "Juego de deducción social",
"loadingWords": "Cargando palabras...", "loadingWords": "Cargando palabras...",
"matchRewards": "Recompensas de partida",
"newMedals": "Nuevas medallas",
"noNewMedalsKeepFire": "Sin medallas nuevas esta vez. Sigue acumulando fuego.",
"calculatingRewards": "Calculando recompensas...",
"fireLabel": "Fuego",
"playersRange": "3-20 jugadores • Sin internet", "playersRange": "3-20 jugadores • Sin internet",
"createGame": "Crear partida", "createGame": "Crear partida",
"joinGame": "Unirse a partida", "joinGame": "Unirse a partida",
@@ -364,5 +369,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Voto de {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "Empieza {name} diciendo su palabra.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Inpostorrea", "appTitle": "Inpostorrea",
"subtitle": "Dedukzio sozialeko jokoa", "subtitle": "Dedukzio sozialeko jokoa",
"loadingWords": "Hitzak kargatzen...", "loadingWords": "Hitzak kargatzen...",
"matchRewards": "Partidako sariak",
"newMedals": "Domina berriak",
"noNewMedalsKeepFire": "Oraingoan ez dago domina berririk. Jarraitu sua pilatzen.",
"calculatingRewards": "Sariak kalkulatzen...",
"fireLabel": "Sua",
"playersRange": "3-20 jokalari • Internetik gabe", "playersRange": "3-20 jokalari • Internetik gabe",
"createGame": "Partida sortu", "createGame": "Partida sortu",
"joinGame": "Partidara batu", "joinGame": "Partidara batu",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "{name}(r)en botoa",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} hasiko da bere hitza esanez.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Farolero", "appTitle": "Farolero",
"subtitle": "Jeu de déduction sociale", "subtitle": "Jeu de déduction sociale",
"loadingWords": "Chargement des mots...", "loadingWords": "Chargement des mots...",
"matchRewards": "Récompenses de partie",
"newMedals": "Nouvelles médailles",
"noNewMedalsKeepFire": "Pas de nouvelles médailles cette fois. Continue à entretenir ta flamme.",
"calculatingRewards": "Calcul des récompenses...",
"fireLabel": "Flamme",
"playersRange": "3-20 joueurs • Sans internet", "playersRange": "3-20 joueurs • Sans internet",
"createGame": "Créer une partie", "createGame": "Créer une partie",
"joinGame": "Rejoindre une partie", "joinGame": "Rejoindre une partie",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Vote de {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} commence en disant son mot.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "धोखेबाज़", "appTitle": "धोखेबाज़",
"subtitle": "सामाजिक अनुमान का खेल", "subtitle": "सामाजिक अनुमान का खेल",
"loadingWords": "शब्द लोड हो रहे हैं...", "loadingWords": "शब्द लोड हो रहे हैं...",
"matchRewards": "गेम पुरस्कार",
"newMedals": "नई पदक",
"noNewMedalsKeepFire": "इस बार कोई नया पदक नहीं। अपनी आग बढ़ाते रहें।",
"calculatingRewards": "पुरस्कार गिने जा रहे हैं...",
"fireLabel": "आग",
"playersRange": "3-20 खिलाड़ी • इंटरनेट की ज़रूरत नहीं", "playersRange": "3-20 खिलाड़ी • इंटरनेट की ज़रूरत नहीं",
"createGame": "गेम बनाएँ", "createGame": "गेम बनाएँ",
"joinGame": "गेम में शामिल हों", "joinGame": "गेम में शामिल हों",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "{name} का वोट",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} अपनी शब्द बोलकर शुरू करता है।",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Farolero", "appTitle": "Farolero",
"subtitle": "Gioco di deduzione sociale", "subtitle": "Gioco di deduzione sociale",
"loadingWords": "Caricamento parole...", "loadingWords": "Caricamento parole...",
"matchRewards": "Ricompense partita",
"newMedals": "Nuove medaglie",
"noNewMedalsKeepFire": "Nessuna nuova medaglia questa volta. Continua ad alimentare il fuoco.",
"calculatingRewards": "Calcolo ricompense...",
"fireLabel": "Fuoco",
"playersRange": "3-20 giocatori • Senza internet", "playersRange": "3-20 giocatori • Senza internet",
"createGame": "Crea partita", "createGame": "Crea partita",
"joinGame": "Unisciti alla partita", "joinGame": "Unisciti alla partita",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Voto di {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} inizia dicendo la sua parola.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "インポスター", "appTitle": "インポスター",
"subtitle": "正体推理ゲーム", "subtitle": "正体推理ゲーム",
"loadingWords": "ワードを読み込み中...", "loadingWords": "ワードを読み込み中...",
"matchRewards": "ゲーム報酬",
"newMedals": "新しいメダル",
"noNewMedalsKeepFire": "今回は新しいメダルはありません。炎を積み上げ続けましょう。",
"calculatingRewards": "報酬を計算中...",
"fireLabel": "炎",
"playersRange": "3-20人 • インターネット不要", "playersRange": "3-20人 • インターネット不要",
"createGame": "ゲームを作成", "createGame": "ゲームを作成",
"joinGame": "ゲームに参加", "joinGame": "ゲームに参加",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "{name} の投票",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} が自分のワードを言って始めます。",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "임포스터", "appTitle": "임포스터",
"subtitle": "사회적 추리 게임", "subtitle": "사회적 추리 게임",
"loadingWords": "단어 불러오는 중...", "loadingWords": "단어 불러오는 중...",
"matchRewards": "게임 보상",
"newMedals": "새 메달",
"noNewMedalsKeepFire": "이번에는 새 메달이 없습니다. 불꽃을 계속 키우세요.",
"calculatingRewards": "보상 계산 중...",
"fireLabel": "불꽃",
"playersRange": "3-20명 • 인터넷 불필요", "playersRange": "3-20명 • 인터넷 불필요",
"createGame": "게임 만들기", "createGame": "게임 만들기",
"joinGame": "게임 참가", "joinGame": "게임 참가",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "{name}의 투표",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name}님이 자신의 단어를 말하며 시작합니다.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "De Bedrieger", "appTitle": "De Bedrieger",
"subtitle": "Sociaal deductiespel", "subtitle": "Sociaal deductiespel",
"loadingWords": "Woorden laden...", "loadingWords": "Woorden laden...",
"matchRewards": "Spelbeloningen",
"newMedals": "Nieuwe medailles",
"noNewMedalsKeepFire": "Deze keer geen nieuwe medailles. Blijf je vuur opbouwen.",
"calculatingRewards": "Beloningen berekenen...",
"fireLabel": "Vuur",
"playersRange": "3-20 spelers • Zonder internet", "playersRange": "3-20 spelers • Zonder internet",
"createGame": "Spel aanmaken", "createGame": "Spel aanmaken",
"joinGame": "Deelnemen aan spel", "joinGame": "Deelnemen aan spel",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Stem van {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} begint door het woord te zeggen.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Oszust", "appTitle": "Oszust",
"subtitle": "Gra dedukcji społecznej", "subtitle": "Gra dedukcji społecznej",
"loadingWords": "Ładowanie słów...", "loadingWords": "Ładowanie słów...",
"matchRewards": "Nagrody za grę",
"newMedals": "Nowe medale",
"noNewMedalsKeepFire": "Tym razem bez nowych medali. Podtrzymuj swój ogień.",
"calculatingRewards": "Obliczanie nagród...",
"fireLabel": "Ogień",
"playersRange": "3-20 graczy • Bez internetu", "playersRange": "3-20 graczy • Bez internetu",
"createGame": "Utwórz grę", "createGame": "Utwórz grę",
"joinGame": "Dołącz do gry", "joinGame": "Dołącz do gry",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Głos gracza {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} zaczyna, mówiąc swoje słowo.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "O Impostor", "appTitle": "O Impostor",
"subtitle": "Jogo de dedução social", "subtitle": "Jogo de dedução social",
"loadingWords": "Carregando palavras...", "loadingWords": "Carregando palavras...",
"matchRewards": "Recompensas da partida",
"newMedals": "Novas medalhas",
"noNewMedalsKeepFire": "Sem medalhas novas desta vez. Continua a acumular fogo.",
"calculatingRewards": "Calculando recompensas...",
"fireLabel": "Fogo",
"playersRange": "3-20 jogadores • Sem internet", "playersRange": "3-20 jogadores • Sem internet",
"createGame": "Criar partida", "createGame": "Criar partida",
"joinGame": "Entrar na partida", "joinGame": "Entrar na partida",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Voto de {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} começa dizendo a sua palavra.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Самозванец", "appTitle": "Самозванец",
"subtitle": "Социальная игра на дедукцию", "subtitle": "Социальная игра на дедукцию",
"loadingWords": "Загрузка слов...", "loadingWords": "Загрузка слов...",
"matchRewards": "Награды за игру",
"newMedals": "Новые медали",
"noNewMedalsKeepFire": "В этот раз новых медалей нет. Продолжай разжигать огонь.",
"calculatingRewards": "Подсчёт наград...",
"fireLabel": "Огонь",
"playersRange": "3-20 игроков • Без интернета", "playersRange": "3-20 игроков • Без интернета",
"createGame": "Создать игру", "createGame": "Создать игру",
"joinGame": "Присоединиться к игре", "joinGame": "Присоединиться к игре",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "Голос игрока {name}",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} начинает, называя своё слово.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "Sahtekar", "appTitle": "Sahtekar",
"subtitle": "Sosyal çıkarım oyunu", "subtitle": "Sosyal çıkarım oyunu",
"loadingWords": "Kelimeler yükleniyor...", "loadingWords": "Kelimeler yükleniyor...",
"matchRewards": "Oyun ödülleri",
"newMedals": "Yeni madalyalar",
"noNewMedalsKeepFire": "Bu kez yeni madalya yok. Ateşini büyütmeye devam et.",
"calculatingRewards": "Ödüller hesaplanıyor...",
"fireLabel": "Ateş",
"playersRange": "3-20 oyuncu • İnternet gerektirmez", "playersRange": "3-20 oyuncu • İnternet gerektirmez",
"createGame": "Oyun oluştur", "createGame": "Oyun oluştur",
"joinGame": "Oyuna katıl", "joinGame": "Oyuna katıl",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "{name} için oy",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} kelimesini söyleyerek başlar.",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "冒牌者", "appTitle": "冒牌者",
"subtitle": "社交推理游戏", "subtitle": "社交推理游戏",
"loadingWords": "正在加载词汇...", "loadingWords": "正在加载词汇...",
"matchRewards": "游戏奖励",
"newMedals": "新奖牌",
"noNewMedalsKeepFire": "这次没有新奖牌。继续积累火焰。",
"calculatingRewards": "正在计算奖励...",
"fireLabel": "火焰",
"playersRange": "3-20名玩家 • 无需联网", "playersRange": "3-20名玩家 • 无需联网",
"createGame": "创建游戏", "createGame": "创建游戏",
"joinGame": "加入游戏", "joinGame": "加入游戏",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "{name} 的投票",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} 先说出自己的词。",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+21
View File
@@ -3,6 +3,11 @@
"appTitle": "冒牌者", "appTitle": "冒牌者",
"subtitle": "社交推理遊戲", "subtitle": "社交推理遊戲",
"loadingWords": "正在載入詞彙...", "loadingWords": "正在載入詞彙...",
"matchRewards": "遊戲獎勵",
"newMedals": "新獎牌",
"noNewMedalsKeepFire": "這次沒有新獎牌。繼續累積火焰。",
"calculatingRewards": "正在計算獎勵...",
"fireLabel": "火焰",
"playersRange": "3-20 位玩家 • 無需網路", "playersRange": "3-20 位玩家 • 無需網路",
"createGame": "建立遊戲", "createGame": "建立遊戲",
"joinGame": "加入遊戲", "joinGame": "加入遊戲",
@@ -296,5 +301,21 @@
"type": "String" "type": "String"
} }
} }
},
"voteOf": "{name} 的投票",
"@voteOf": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"firstTurnInstruction": "{name} 先說出自己的詞。",
"@firstTurnInstruction": {
"placeholders": {
"name": {
"type": "String"
}
}
} }
} }
+44
View File
@@ -147,6 +147,37 @@ abstract class AppLocalizations {
/// **'Cargando palabras...'** /// **'Cargando palabras...'**
String get loadingWords; String get loadingWords;
/// No description provided for @matchRewards.
///
/// In en, this message translates to:
/// **'Game rewards'**
String get matchRewards;
/// No description provided for @newMedals.
///
/// In en, this message translates to:
/// **'New medals'**
String get newMedals;
/// No description provided for @noNewMedalsKeepFire.
///
/// In en, this message translates to:
/// **'No new medals this time. Keep building your fire.'**
String get noNewMedalsKeepFire;
/// No description provided for @calculatingRewards.
///
/// In en, this message translates to:
/// **'Calculating rewards...'**
String get calculatingRewards;
/// No description provided for @fireLabel.
///
/// In en, this message translates to:
/// **'Fire'**
String get fireLabel;
/// No description provided for @playersRange. /// No description provided for @playersRange.
/// ///
/// In es, this message translates to: /// In es, this message translates to:
@@ -489,6 +520,19 @@ abstract class AppLocalizations {
/// **'Jugadores en debate'** /// **'Jugadores en debate'**
String get playersInDebate; String get playersInDebate;
/// No description provided for @voteOf.
///
/// In en, this message translates to:
/// **'Vote from {name}'**
String voteOf(String name);
/// No description provided for @firstTurnInstruction.
///
/// In en, this message translates to:
/// **'{name} starts by saying their word.'**
String firstTurnInstruction(String name);
/// No description provided for @activePlayersInfo. /// No description provided for @activePlayersInfo.
/// ///
/// In es, this message translates to: /// In es, this message translates to:
@@ -17,6 +17,21 @@ class AppLocalizationsAr extends AppLocalizations {
@override @override
String get loadingWords => 'جارٍ تحميل الكلمات...'; String get loadingWords => 'جارٍ تحميل الكلمات...';
@override
String get matchRewards => "مكافآت المباراة";
@override
String get newMedals => "ميداليات جديدة";
@override
String get noNewMedalsKeepFire => "لا توجد ميداليات جديدة هذه المرة. واصل إشعال حماسك.";
@override
String get calculatingRewards => "جارٍ حساب المكافآت...";
@override
String get fireLabel => "النار";
@override @override
String get playersRange => '3-20 لاعبًا • بدون إنترنت'; String get playersRange => '3-20 لاعبًا • بدون إنترنت';
@@ -198,6 +213,16 @@ class AppLocalizationsAr extends AppLocalizations {
@override @override
String get playersInDebate => 'اللاعبون في النقاش'; String get playersInDebate => 'اللاعبون في النقاش';
@override
String voteOf(String name) {
return "تصويت $name";
}
@override
String firstTurnInstruction(String name) {
return "يبدأ $name بقول كلمته.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active نشطون • $impostors منتحل(ون) مختبئون'; return '$active نشطون • $impostors منتحل(ون) مختبئون';
@@ -17,6 +17,21 @@ class AppLocalizationsCa extends AppLocalizations {
@override @override
String get loadingWords => 'Carregant paraules...'; String get loadingWords => 'Carregant paraules...';
@override
String get matchRewards => "Recompenses de partida";
@override
String get newMedals => "Noves medalles";
@override
String get noNewMedalsKeepFire => "Sense medalles noves aquesta vegada. Continua acumulant foc.";
@override
String get calculatingRewards => "Calculant recompenses...";
@override
String get fireLabel => "Foc";
@override @override
String get playersRange => '3-20 jugadors • Sense internet'; String get playersRange => '3-20 jugadors • Sense internet';
@@ -199,6 +214,16 @@ class AppLocalizationsCa extends AppLocalizations {
@override @override
String get playersInDebate => 'Jugadors en debat'; String get playersInDebate => 'Jugadors en debat';
@override
String voteOf(String name) {
return "Vot de $name";
}
@override
String firstTurnInstruction(String name) {
return "Comença $name dient la seva paraula.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active actius • $impostors impostor(s) ocults'; return '$active actius • $impostors impostor(s) ocults';
@@ -17,6 +17,21 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get loadingWords => 'Wörter werden geladen...'; String get loadingWords => 'Wörter werden geladen...';
@override
String get matchRewards => "Spielbelohnungen";
@override
String get newMedals => "Neue Medaillen";
@override
String get noNewMedalsKeepFire => "Diesmal keine neuen Medaillen. Baue dein Feuer weiter aus.";
@override
String get calculatingRewards => "Belohnungen werden berechnet...";
@override
String get fireLabel => "Feuer";
@override @override
String get playersRange => '3-20 Spieler • Ohne Internet'; String get playersRange => '3-20 Spieler • Ohne Internet';
@@ -201,6 +216,16 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get playersInDebate => 'Spieler in der Diskussion'; String get playersInDebate => 'Spieler in der Diskussion';
@override
String voteOf(String name) {
return "Stimme von $name";
}
@override
String firstTurnInstruction(String name) {
return "$name beginnt und sagt sein/ihr Wort.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active aktiv • $impostors versteckte(r) Hochstapler'; return '$active aktiv • $impostors versteckte(r) Hochstapler';
@@ -17,6 +17,21 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get loadingWords => 'Loading words...'; String get loadingWords => 'Loading words...';
@override
String get matchRewards => "Game rewards";
@override
String get newMedals => "New medals";
@override
String get noNewMedalsKeepFire => "No new medals this time. Keep building your fire.";
@override
String get calculatingRewards => "Calculating rewards...";
@override
String get fireLabel => "Fire";
@override @override
String get playersRange => '3-20 players • No internet needed'; String get playersRange => '3-20 players • No internet needed';
@@ -198,6 +213,16 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get playersInDebate => 'Players in discussion'; String get playersInDebate => 'Players in discussion';
@override
String voteOf(String name) {
return "Vote from $name";
}
@override
String firstTurnInstruction(String name) {
return "$name starts by saying their word.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active active • $impostors hidden impostor(s)'; return '$active active • $impostors hidden impostor(s)';
@@ -17,6 +17,21 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get loadingWords => 'Cargando palabras...'; String get loadingWords => 'Cargando palabras...';
@override
String get matchRewards => "Recompensas de partida";
@override
String get newMedals => "Nuevas medallas";
@override
String get noNewMedalsKeepFire => "Sin medallas nuevas esta vez. Sigue acumulando fuego.";
@override
String get calculatingRewards => "Calculando recompensas...";
@override
String get fireLabel => "Fuego";
@override @override
String get playersRange => '3-20 jugadores • Sin internet'; String get playersRange => '3-20 jugadores • Sin internet';
@@ -198,6 +213,16 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get playersInDebate => 'Jugadores en debate'; String get playersInDebate => 'Jugadores en debate';
@override
String voteOf(String name) {
return "Voto de $name";
}
@override
String firstTurnInstruction(String name) {
return "Empieza $name diciendo su palabra.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active activos • $impostors impostor(es) ocultos'; return '$active activos • $impostors impostor(es) ocultos';
@@ -17,6 +17,21 @@ class AppLocalizationsEu extends AppLocalizations {
@override @override
String get loadingWords => 'Hitzak kargatzen...'; String get loadingWords => 'Hitzak kargatzen...';
@override
String get matchRewards => "Partidako sariak";
@override
String get newMedals => "Domina berriak";
@override
String get noNewMedalsKeepFire => "Oraingoan ez dago domina berririk. Jarraitu sua pilatzen.";
@override
String get calculatingRewards => "Sariak kalkulatzen...";
@override
String get fireLabel => "Sua";
@override @override
String get playersRange => '3-20 jokalari • Internetik gabe'; String get playersRange => '3-20 jokalari • Internetik gabe';
@@ -201,6 +216,16 @@ class AppLocalizationsEu extends AppLocalizations {
@override @override
String get playersInDebate => 'Eztabaidan diren jokalariak'; String get playersInDebate => 'Eztabaidan diren jokalariak';
@override
String voteOf(String name) {
return "$name(r)en botoa";
}
@override
String firstTurnInstruction(String name) {
return "$name hasiko da bere hitza esanez.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active aktibo • $impostors inpostore ezkutu'; return '$active aktibo • $impostors inpostore ezkutu';
@@ -17,6 +17,21 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get loadingWords => 'Chargement des mots...'; String get loadingWords => 'Chargement des mots...';
@override
String get matchRewards => "Récompenses de partie";
@override
String get newMedals => "Nouvelles médailles";
@override
String get noNewMedalsKeepFire => "Pas de nouvelles médailles cette fois. Continue à entretenir ta flamme.";
@override
String get calculatingRewards => "Calcul des récompenses...";
@override
String get fireLabel => "Flamme";
@override @override
String get playersRange => '3-20 joueurs • Sans internet'; String get playersRange => '3-20 joueurs • Sans internet';
@@ -199,6 +214,16 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get playersInDebate => 'Joueurs en débat'; String get playersInDebate => 'Joueurs en débat';
@override
String voteOf(String name) {
return "Vote de $name";
}
@override
String firstTurnInstruction(String name) {
return "$name commence en disant son mot.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active actifs • $impostors imposteur(s) caché(s)'; return '$active actifs • $impostors imposteur(s) caché(s)';
@@ -17,6 +17,21 @@ class AppLocalizationsHi extends AppLocalizations {
@override @override
String get loadingWords => 'शब्द लोड हो रहे हैं...'; String get loadingWords => 'शब्द लोड हो रहे हैं...';
@override
String get matchRewards => "गेम पुरस्कार";
@override
String get newMedals => "नई पदक";
@override
String get noNewMedalsKeepFire => "इस बार कोई नया पदक नहीं। अपनी आग बढ़ाते रहें।";
@override
String get calculatingRewards => "पुरस्कार गिने जा रहे हैं...";
@override
String get fireLabel => "आग";
@override @override
String get playersRange => '3-20 खिलाड़ी • इंटरनेट की ज़रूरत नहीं'; String get playersRange => '3-20 खिलाड़ी • इंटरनेट की ज़रूरत नहीं';
@@ -198,6 +213,16 @@ class AppLocalizationsHi extends AppLocalizations {
@override @override
String get playersInDebate => 'बहस में खिलाड़ी'; String get playersInDebate => 'बहस में खिलाड़ी';
@override
String voteOf(String name) {
return "$name का वोट";
}
@override
String firstTurnInstruction(String name) {
return "$name अपनी शब्द बोलकर शुरू करता है।";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active सक्रिय • $impostors धोखेबाज़ छिपे हुए'; return '$active सक्रिय • $impostors धोखेबाज़ छिपे हुए';
@@ -17,6 +17,21 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get loadingWords => 'Caricamento parole...'; String get loadingWords => 'Caricamento parole...';
@override
String get matchRewards => "Ricompense partita";
@override
String get newMedals => "Nuove medaglie";
@override
String get noNewMedalsKeepFire => "Nessuna nuova medaglia questa volta. Continua ad alimentare il fuoco.";
@override
String get calculatingRewards => "Calcolo ricompense...";
@override
String get fireLabel => "Fuoco";
@override @override
String get playersRange => '3-20 giocatori • Senza internet'; String get playersRange => '3-20 giocatori • Senza internet';
@@ -199,6 +214,16 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get playersInDebate => 'Giocatori in discussione'; String get playersInDebate => 'Giocatori in discussione';
@override
String voteOf(String name) {
return "Voto di $name";
}
@override
String firstTurnInstruction(String name) {
return "$name inizia dicendo la sua parola.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active attivi • $impostors impostore/i nascosti'; return '$active attivi • $impostors impostore/i nascosti';
@@ -17,6 +17,21 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get loadingWords => 'ワードを読み込み中...'; String get loadingWords => 'ワードを読み込み中...';
@override
String get matchRewards => "ゲーム報酬";
@override
String get newMedals => "新しいメダル";
@override
String get noNewMedalsKeepFire => "今回は新しいメダルはありません。炎を積み上げ続けましょう。";
@override
String get calculatingRewards => "報酬を計算中...";
@override
String get fireLabel => "";
@override @override
String get playersRange => '3-20人 • インターネット不要'; String get playersRange => '3-20人 • インターネット不要';
@@ -198,6 +213,16 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get playersInDebate => '議論中のプレイヤー'; String get playersInDebate => '議論中のプレイヤー';
@override
String voteOf(String name) {
return "$name の投票";
}
@override
String firstTurnInstruction(String name) {
return "$name が自分のワードを言って始めます。";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active 人参加中 • $impostors 人のインポスターが潜伏中'; return '$active 人参加中 • $impostors 人のインポスターが潜伏中';
@@ -17,6 +17,21 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get loadingWords => '단어 불러오는 중...'; String get loadingWords => '단어 불러오는 중...';
@override
String get matchRewards => "게임 보상";
@override
String get newMedals => "새 메달";
@override
String get noNewMedalsKeepFire => "이번에는 새 메달이 없습니다. 불꽃을 계속 키우세요.";
@override
String get calculatingRewards => "보상 계산 중...";
@override
String get fireLabel => "불꽃";
@override @override
String get playersRange => '3-20명 • 인터넷 불필요'; String get playersRange => '3-20명 • 인터넷 불필요';
@@ -198,6 +213,16 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get playersInDebate => '토론 중인 플레이어'; String get playersInDebate => '토론 중인 플레이어';
@override
String voteOf(String name) {
return "$name의 투표";
}
@override
String firstTurnInstruction(String name) {
return "$name님이 자신의 단어를 말하며 시작합니다.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active명 참여 중 • $impostors명의 임포스터 잠복 중'; return '$active명 참여 중 • $impostors명의 임포스터 잠복 중';
@@ -17,6 +17,21 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get loadingWords => 'Woorden laden...'; String get loadingWords => 'Woorden laden...';
@override
String get matchRewards => "Spelbeloningen";
@override
String get newMedals => "Nieuwe medailles";
@override
String get noNewMedalsKeepFire => "Deze keer geen nieuwe medailles. Blijf je vuur opbouwen.";
@override
String get calculatingRewards => "Beloningen berekenen...";
@override
String get fireLabel => "Vuur";
@override @override
String get playersRange => '3-20 spelers • Zonder internet'; String get playersRange => '3-20 spelers • Zonder internet';
@@ -199,6 +214,16 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get playersInDebate => 'Spelers in debat'; String get playersInDebate => 'Spelers in debat';
@override
String voteOf(String name) {
return "Stem van $name";
}
@override
String firstTurnInstruction(String name) {
return "$name begint door het woord te zeggen.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active actief • $impostors verborgen bedrieger(s)'; return '$active actief • $impostors verborgen bedrieger(s)';
@@ -17,6 +17,21 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get loadingWords => 'Ładowanie słów...'; String get loadingWords => 'Ładowanie słów...';
@override
String get matchRewards => "Nagrody za grę";
@override
String get newMedals => "Nowe medale";
@override
String get noNewMedalsKeepFire => "Tym razem bez nowych medali. Podtrzymuj swój ogień.";
@override
String get calculatingRewards => "Obliczanie nagród...";
@override
String get fireLabel => "Ogień";
@override @override
String get playersRange => '3-20 graczy • Bez internetu'; String get playersRange => '3-20 graczy • Bez internetu';
@@ -199,6 +214,16 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get playersInDebate => 'Gracze w debacie'; String get playersInDebate => 'Gracze w debacie';
@override
String voteOf(String name) {
return "Głos gracza $name";
}
@override
String firstTurnInstruction(String name) {
return "$name zaczyna, mówiąc swoje słowo.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active aktywnych • $impostors ukrytych oszustów'; return '$active aktywnych • $impostors ukrytych oszustów';
@@ -17,6 +17,21 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get loadingWords => 'Carregando palavras...'; String get loadingWords => 'Carregando palavras...';
@override
String get matchRewards => "Recompensas da partida";
@override
String get newMedals => "Novas medalhas";
@override
String get noNewMedalsKeepFire => "Sem medalhas novas desta vez. Continua a acumular fogo.";
@override
String get calculatingRewards => "Calculando recompensas...";
@override
String get fireLabel => "Fogo";
@override @override
String get playersRange => '3-20 jogadores • Sem internet'; String get playersRange => '3-20 jogadores • Sem internet';
@@ -200,6 +215,16 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get playersInDebate => 'Jogadores no debate'; String get playersInDebate => 'Jogadores no debate';
@override
String voteOf(String name) {
return "Voto de $name";
}
@override
String firstTurnInstruction(String name) {
return "$name começa dizendo a sua palavra.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active ativos • $impostors impostor(es) ocultos'; return '$active ativos • $impostors impostor(es) ocultos';
@@ -17,6 +17,21 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get loadingWords => 'Загрузка слов...'; String get loadingWords => 'Загрузка слов...';
@override
String get matchRewards => "Награды за игру";
@override
String get newMedals => "Новые медали";
@override
String get noNewMedalsKeepFire => "В этот раз новых медалей нет. Продолжай разжигать огонь.";
@override
String get calculatingRewards => "Подсчёт наград...";
@override
String get fireLabel => "Огонь";
@override @override
String get playersRange => '3-20 игроков • Без интернета'; String get playersRange => '3-20 игроков • Без интернета';
@@ -199,6 +214,16 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get playersInDebate => 'Игроки в обсуждении'; String get playersInDebate => 'Игроки в обсуждении';
@override
String voteOf(String name) {
return "Голос игрока $name";
}
@override
String firstTurnInstruction(String name) {
return "$name начинает, называя своё слово.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active активных • $impostors скрытый(-х) самозванец(-ев)'; return '$active активных • $impostors скрытый(-х) самозванец(-ев)';
@@ -17,6 +17,21 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get loadingWords => 'Kelimeler yükleniyor...'; String get loadingWords => 'Kelimeler yükleniyor...';
@override
String get matchRewards => "Oyun ödülleri";
@override
String get newMedals => "Yeni madalyalar";
@override
String get noNewMedalsKeepFire => "Bu kez yeni madalya yok. Ateşini büyütmeye devam et.";
@override
String get calculatingRewards => "Ödüller hesaplanıyor...";
@override
String get fireLabel => "Ateş";
@override @override
String get playersRange => '3-20 oyuncu • İnternet gerektirmez'; String get playersRange => '3-20 oyuncu • İnternet gerektirmez';
@@ -198,6 +213,16 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get playersInDebate => 'Tartışmadaki oyuncular'; String get playersInDebate => 'Tartışmadaki oyuncular';
@override
String voteOf(String name) {
return "$name için oy";
}
@override
String firstTurnInstruction(String name) {
return "$name kelimesini söyleyerek başlar.";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active aktif • $impostors gizli sahtekar'; return '$active aktif • $impostors gizli sahtekar';
@@ -17,6 +17,21 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get loadingWords => '正在加载词汇...'; String get loadingWords => '正在加载词汇...';
@override
String get matchRewards => "游戏奖励";
@override
String get newMedals => "新奖牌";
@override
String get noNewMedalsKeepFire => "这次没有新奖牌。继续积累火焰。";
@override
String get calculatingRewards => "正在计算奖励...";
@override
String get fireLabel => "火焰";
@override @override
String get playersRange => '3-20名玩家 • 无需联网'; String get playersRange => '3-20名玩家 • 无需联网';
@@ -198,6 +213,16 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get playersInDebate => '参与讨论的玩家'; String get playersInDebate => '参与讨论的玩家';
@override
String voteOf(String name) {
return "$name 的投票";
}
@override
String firstTurnInstruction(String name) {
return "$name 先说出自己的词。";
}
@override @override
String activePlayersInfo(int active, int impostors) { String activePlayersInfo(int active, int impostors) {
return '$active 名在场 • $impostors 名冒牌者潜伏中'; return '$active 名在场 • $impostors 名冒牌者潜伏中';
+80 -131
View File
@@ -30,6 +30,22 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
setState(() => _acierto = resultado); setState(() => _acierto = resultado);
} }
void _continuarTrasNoAdivinar(EstadoJuego estado) {
final fin = estado.comprobarFinPartida();
if (fin) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const PantallaFinPartida()),
);
} else {
estado.siguienteRonda();
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const PantallaDebate()),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
@@ -44,199 +60,132 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true, intenso: true,
child: SafeArea(
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(24),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const ArteGameplayFarolero.fase(height: 132), const ArteGameplayFarolero.fase(height: 132),
const SizedBox(height: 12), const SizedBox(height: 12),
EncabezadoFarolero( TarjetaFaseFarolero(
icono: Icons.theater_comedy, icono: Icons.theater_comedy,
assetIconPath: 'assets/ui/generated/actions/action_impostor_mask.webp',
titulo: l10n.impostorCanGuess, titulo: l10n.impostorCanGuess,
subtitulo: l10n.ifCorrectImpostorsWin, subtitulo: l10n.ifCorrectImpostorsWin,
color: TemaApp.colorAcento, color: TemaApp.colorAcento,
trailing: Image.asset( child: _buildContenido(context, l10n, estado, partida.palabraSecreta),
'assets/ui/generated/meta/result_verdict_art.webp', ),
width: 42, ],
height: 42,
opacity: const AlwaysStoppedAnimation(0.64),
), ),
), ),
const SizedBox(height: 32), ),
),
),
);
}
if (_acierto == null) ...[ Widget _buildContenido(
BuildContext context,
AppLocalizations l10n,
EstadoJuego estado,
String palabraSecreta,
) {
if (_acierto == null) {
return Column(
children: [
TextField( TextField(
controller: _controlador, controller: _controlador,
decoration: InputDecoration( decoration: InputDecoration(
hintText: l10n.guessWordHint, hintText: l10n.guessWordHint,
prefixIcon: const Icon(Icons.search), prefixIcon: IconoFarolero(Icons.search),
), ),
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20), style: const TextStyle(fontSize: 20),
onSubmitted: (_) => _intentarAdivinar(), onChanged: (value) => setState(() {}),
onSubmitted: (value) => _intentarAdivinar(),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: OutlinedButton( child: BotonFarolero.oscuro(
onPressed: () { texto: l10n.dontGuess,
// No intenta adivinar, siguiente ronda icono: Icons.skip_next,
final fin = estado.comprobarFinPartida(); onPressed: () => _continuarTrasNoAdivinar(estado),
if (fin) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaFinPartida(),
),
);
} else {
estado.siguienteRonda();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaDebate(),
),
);
}
},
child: Text(l10n.dontGuess),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
flex: 2, flex: 2,
child: ElevatedButton.icon( child: BotonFarolero(
onPressed: _controlador.text.trim().isNotEmpty texto: l10n.guess,
? _intentarAdivinar icono: Icons.send,
: null, assetIconPath: 'assets/ui/generated/actions/action_impostor_mask.webp',
icon: const Icon(Icons.send), onPressed: _controlador.text.trim().isNotEmpty ? _intentarAdivinar : null,
label: Text(l10n.guess),
), ),
), ),
], ],
), ),
], ],
);
}
if (_acierto == true) ...[ final acierto = _acierto == true;
const SizedBox(height: 16), final color = acierto ? TemaApp.colorAcento : TemaApp.colorVerde;
Container( return Column(
children: [
PanelFarolero(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
decoration: BoxDecoration( borderColor: color,
color: TemaApp.colorAcento.withValues(alpha: 0.3), color: color.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: TemaApp.colorAcento),
),
child: Column( child: Column(
children: [ children: [
const Text('🎭🎉', style: TextStyle(fontSize: 48)), IconoFarolero(
acierto ? Icons.celebration : Icons.cancel,
color: color,
size: 52,
),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
l10n.correctGuess, acierto ? l10n.correctGuess : l10n.wrongGuess,
style: Theme.of(context) textAlign: TextAlign.center,
.textTheme style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: color),
.headlineMedium
?.copyWith(color: TemaApp.colorAcento),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
l10n.theWordWas(partida.palabraSecreta), acierto ? l10n.theWordWas(palabraSecreta) : l10n.gameContinues,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge, style: Theme.of(context).textTheme.titleLarge,
), ),
if (acierto) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
l10n.impostorsWin, l10n.impostorsWin,
style: Theme.of(context).textTheme.bodyLarge?.copyWith( textAlign: TextAlign.center,
color: TemaApp.colorNaranja, style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: TemaApp.colorNaranja),
),
), ),
], ],
],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
SizedBox( BotonFarolero(
width: double.infinity, texto: acierto ? l10n.seeEndResult : l10n.nextRound,
height: 56, icono: acierto ? Icons.emoji_events : Icons.skip_next,
child: ElevatedButton.icon( assetIconPath: acierto ? 'assets/ui/generated/actions/action_result_trophy.webp' : null,
onPressed: () { onPressed: acierto
? () {
Navigator.pushReplacement( Navigator.pushReplacement(
context, context,
MaterialPageRoute( MaterialPageRoute(builder: (_) => const PantallaFinPartida()),
builder: (_) => const PantallaFinPartida(),
),
);
},
icon: const Icon(Icons.emoji_events),
label: Text(l10n.seeEndResult),
),
),
],
if (_acierto == false) ...[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: TemaApp.colorVerde.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: TemaApp.colorVerde),
),
child: Column(
children: [
const Text('', style: TextStyle(fontSize: 48)),
const SizedBox(height: 12),
Text(
l10n.wrongGuess,
style: Theme.of(context)
.textTheme
.headlineMedium
?.copyWith(color: TemaApp.colorVerde),
),
const SizedBox(height: 8),
Text(
l10n.gameContinues,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton.icon(
onPressed: () {
final fin = estado.comprobarFinPartida();
if (fin) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaFinPartida(),
),
);
} else {
estado.siguienteRonda();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const PantallaDebate(),
),
); );
} }
}, : () => _continuarTrasNoAdivinar(estado),
icon: const Icon(Icons.skip_next),
label: Text(l10n.nextRound),
),
), ),
], ],
],
),
),
),
),
); );
} }
} }
+9 -4
View File
@@ -69,7 +69,12 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
const Icon(Icons.edit), Image.asset(
'assets/ui/generated/actions/action_settings_gear.webp',
width: 38,
height: 38,
fit: BoxFit.contain,
),
], ],
), ),
), ),
@@ -130,7 +135,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
leading: Text(bandera, style: const TextStyle(fontSize: 24)), leading: Text(bandera, style: const TextStyle(fontSize: 24)),
title: Text(nombre), title: Text(nombre),
trailing: seleccionado trailing: seleccionado
? const Icon(Icons.check_circle, color: TemaApp.colorAcento) ? IconoFarolero(Icons.check_circle, color: TemaApp.colorAcento)
: null, : null,
onTap: onTap, onTap: onTap,
dense: true, dense: true,
@@ -167,7 +172,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
onChanged: (_) => setDialogState(() {}), onChanged: (_) => setDialogState(() {}),
decoration: InputDecoration( decoration: InputDecoration(
labelText: l10n.profileName, labelText: l10n.profileName,
prefixIcon: const Icon(Icons.person), prefixIcon: IconoFarolero(Icons.person),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -175,7 +180,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
controller: nickController, controller: nickController,
decoration: InputDecoration( decoration: InputDecoration(
labelText: l10n.profileNick, labelText: l10n.profileNick,
prefixIcon: const Icon(Icons.alternate_email), prefixIcon: IconoFarolero(Icons.alternate_email),
), ),
), ),
const SizedBox(height: 18), const SizedBox(height: 18),
+33 -16
View File
@@ -151,7 +151,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
Future<void> _iniciarPartidaMulti() async { Future<void> _iniciarPartidaMulti() async {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
// 1. Pedir permisos automáticamente // 1. Pedir permisos automáticamente
final permisosOk = await ServicioPermisos.solicitarPermisosNearby(context); final permisosOk = await ServicioPermisos.solicitarPermisosNearby(context);
if (!permisosOk) { if (!permisosOk) {
if (mounted) { if (mounted) {
@@ -353,12 +353,12 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
ButtonSegment( ButtonSegment(
value: false, value: false,
label: Text(l10n.singleDevice), label: Text(l10n.singleDevice),
icon: const Icon(Icons.phone_android), icon: IconoFarolero(Icons.phone_android),
), ),
ButtonSegment( ButtonSegment(
value: true, value: true,
label: Text(l10n.multiDevice), label: Text(l10n.multiDevice),
icon: const Icon(Icons.devices), icon: IconoFarolero(Icons.devices),
), ),
], ],
selected: {_modoMultimovil}, selected: {_modoMultimovil},
@@ -382,7 +382,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row( child: Row(
children: [ children: [
Icon( IconoFarolero(
_modoMultimovil ? Icons.devices : Icons.phone_android, _modoMultimovil ? Icons.devices : Icons.phone_android,
color: TemaApp.colorNaranja, color: TemaApp.colorNaranja,
), ),
@@ -400,7 +400,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
], ],
// Categoría // Categoría
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(18, 18, 18, 28), padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
@@ -416,8 +416,16 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
width: double.infinity, width: double.infinity,
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
initialValue: _categoria, initialValue: _categoria,
decoration: const InputDecoration( decoration: InputDecoration(
prefixIcon: Icon(Icons.category), prefixIcon: Padding(
padding: EdgeInsets.all(10),
child: Image.asset(
'assets/ui/generated/actions/action_category_cards.webp',
width: 24,
height: 24,
fit: BoxFit.contain,
),
),
), ),
items: categorias.map((c) { items: categorias.map((c) {
return DropdownMenuItem( return DropdownMenuItem(
@@ -465,7 +473,15 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
controller: _controladorNombre, controller: _controladorNombre,
decoration: InputDecoration( decoration: InputDecoration(
hintText: l10n.playerNameHint, hintText: l10n.playerNameHint,
prefixIcon: const Icon(Icons.person_add), prefixIcon: Padding(
padding: EdgeInsets.all(10),
child: Image.asset(
'assets/ui/generated/actions/action_add_player.webp',
width: 24,
height: 24,
fit: BoxFit.contain,
),
),
), ),
textCapitalization: TextCapitalization.words, textCapitalization: TextCapitalization.words,
onSubmitted: (_) => _agregarJugador(), onSubmitted: (_) => _agregarJugador(),
@@ -474,7 +490,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
const SizedBox(width: 8), const SizedBox(width: 8),
IconButton.filled( IconButton.filled(
onPressed: _agregarJugador, onPressed: _agregarJugador,
icon: const Icon(Icons.add), icon: IconoFarolero(Icons.add),
), ),
], ],
), ),
@@ -513,9 +529,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
? Text(l10n.mainDeviceUser) ? Text(l10n.mainDeviceUser)
: null, : null,
trailing: esPerfilLocal trailing: esPerfilLocal
? const Icon(Icons.lock, color: TemaApp.colorDorado) ? IconoFarolero(Icons.lock, color: TemaApp.colorDorado)
: IconButton( : IconButton(
icon: const Icon( icon: IconoFarolero(
Icons.close, Icons.close,
color: TemaApp.colorAcento, color: TemaApp.colorAcento,
), ),
@@ -530,7 +546,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
const SizedBox(height: 12), const SizedBox(height: 12),
], ],
// Configuración de partida // Configuración de partida
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(18, 18, 18, 28), padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
@@ -543,7 +559,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// Número de impostores // Número de impostores
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -554,7 +570,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
onPressed: _numImpostores > 1 onPressed: _numImpostores > 1
? () => setState(() => _numImpostores--) ? () => setState(() => _numImpostores--)
: null, : null,
icon: const Icon(Icons.remove_circle_outline), icon: IconoFarolero(Icons.remove_circle_outline),
), ),
Text( Text(
'$_numImpostores', '$_numImpostores',
@@ -564,7 +580,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
onPressed: _numImpostores < _maxImpostores onPressed: _numImpostores < _maxImpostores
? () => setState(() => _numImpostores++) ? () => setState(() => _numImpostores++)
: null, : null,
icon: const Icon(Icons.add_circle_outline), icon: IconoFarolero(Icons.add_circle_outline),
), ),
], ],
), ),
@@ -604,10 +620,11 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Botón iniciar // Botón iniciar
BotonFarolero( BotonFarolero(
texto: l10n.startGame, texto: l10n.startGame,
icono: Icons.play_arrow, icono: Icons.play_arrow,
assetIconPath: 'assets/ui/generated/actions/action_create_game.webp',
onPressed: (_modoMultimovil || _jugadores.length >= 3) onPressed: (_modoMultimovil || _jugadores.length >= 3)
? _iniciarPartida ? _iniciarPartida
: null, : null,
+33 -130
View File
@@ -4,7 +4,6 @@ import 'package:farolero/l10n/generated/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../estado/estado_juego.dart'; import '../estado/estado_juego.dart';
import '../tema/componentes_farolero.dart'; import '../tema/componentes_farolero.dart';
import '../tema/tema_app.dart';
import 'pantalla_notas.dart'; import 'pantalla_notas.dart';
import 'pantalla_votacion.dart'; import 'pantalla_votacion.dart';
@@ -67,9 +66,6 @@ class _PantallaDebateState extends State<PantallaDebate> {
if (partida == null) return const SizedBox.shrink(); if (partida == null) return const SizedBox.shrink();
final tieneTemporizador = partida.config.tiempoDebateSegundos != null; final tieneTemporizador = partida.config.tiempoDebateSegundos != null;
final progreso = tieneTemporizador
? _segundosRestantes / partida.config.tiempoDebateSegundos!
: 0.0;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -78,167 +74,73 @@ class _PantallaDebateState extends State<PantallaDebate> {
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true, intenso: true,
child: SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ children: [
const ArteGameplayFarolero.fase(height: 110), const ArteGameplayFarolero.fase(height: 110),
const SizedBox(height: 10), const SizedBox(height: 10),
// Temporizador
if (tieneTemporizador) ...[ if (tieneTemporizador) ...[
Container( TemporizadorFarolero(
width: double.infinity, etiqueta: _tiempoAgotado ? l10n.timeUp : l10n.timeRemaining,
padding: const EdgeInsets.all(20), tiempo: _formatearTiempo(_segundosRestantes),
decoration: BoxDecoration( agotado: _tiempoAgotado,
color: _tiempoAgotado
? TemaApp.colorAcento.withValues(alpha: 0.3)
: TemaApp.colorTarjeta,
borderRadius: BorderRadius.circular(16),
border: _tiempoAgotado
? Border.all(color: TemaApp.colorAcento, width: 2)
: null,
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: Image.asset(
'assets/ui/generated/gameplay/gameplay_phase_emblem.webp',
fit: BoxFit.contain,
opacity: const AlwaysStoppedAnimation(0.36),
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_tiempoAgotado ? l10n.timeUp : l10n.timeRemaining,
style:
Theme.of(context).textTheme.titleMedium?.copyWith(
color: _tiempoAgotado
? TemaApp.colorAcento
: TemaApp.colorTextoSecundario,
),
),
const SizedBox(height: 8),
Text(
_formatearTiempo(_segundosRestantes),
style:
Theme.of(context).textTheme.headlineLarge?.copyWith(
fontSize: 48,
fontWeight: FontWeight.bold,
color: _segundosRestantes < 10 &&
!_tiempoAgotado
? TemaApp.colorAcento
: TemaApp.colorTexto,
),
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: progreso,
backgroundColor: TemaApp.colorSuperficie,
valueColor: AlwaysStoppedAnimation(
_segundosRestantes < 10
? TemaApp.colorAcento
: TemaApp.colorVerde,
),
minHeight: 6,
),
),
],
),
],
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
], ],
// Jugadores activos
Expanded( Expanded(
child: Card( child: TarjetaFaseFarolero(
child: Padding( icono: Icons.forum,
padding: const EdgeInsets.all(16), assetIconPath: 'assets/ui/generated/actions/action_rules_book.webp',
child: Column( titulo: l10n.playersInDebate,
crossAxisAlignment: CrossAxisAlignment.start, subtitulo: l10n.activePlayersInfo(
children: [ partida.jugadoresActivos.length,
Text( partida.impostoresActivos.length,
l10n.playersInDebate,
style: Theme.of(context).textTheme.titleLarge,
), ),
const SizedBox(height: 4), child: Expanded(
Text( child: ListView.separated(
l10n.activePlayersInfo(partida.jugadoresActivos.length, partida.impostoresActivos.length),
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 12),
Expanded(
child: ListView.builder(
itemCount: partida.jugadores.length, itemCount: partida.jugadores.length,
separatorBuilder: (context, index) => const SizedBox(height: 8),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final j = partida.jugadores[index]; final jugador = partida.jugadores[index];
return ListTile( return EstadoJugadorFarolero(
leading: CircleAvatar( nombre: '${index + 1}. ${jugador.nombre}',
backgroundColor: j.eliminado subtitulo: jugador.eliminado ? l10n.eliminated : null,
? Colors.grey icono: jugador.eliminado ? Icons.person_off : Icons.record_voice_over,
: TemaApp.colorAcento, assetIconPath: jugador.eliminado ? null : 'assets/ui/generated/actions/action_players_group.webp',
child: Text( destacado: !jugador.eliminado,
j.eliminado ? '💀' : '${index + 1}', completado: !jugador.eliminado,
style: const TextStyle(
color: Colors.white, fontSize: 14),
),
),
title: Text(
j.nombre,
style: TextStyle(
decoration: j.eliminado
? TextDecoration.lineThrough
: null,
color: j.eliminado
? TemaApp.colorTextoSecundario
: TemaApp.colorTexto,
),
),
subtitle: j.eliminado
? Text(l10n.eliminated)
: null,
dense: true,
); );
}, },
), ),
), ),
],
),
),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Botones
Row( Row(
children: [ children: [
Expanded( Expanded(
child: OutlinedButton.icon( child: BotonFarolero.oscuro(
texto: l10n.notes,
icono: Icons.edit_note,
assetIconPath: 'assets/ui/generated/actions/action_notes_quill.webp',
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(builder: (_) => const PantallaNotas()),
builder: (_) => const PantallaNotas(),
),
); );
}, },
icon: const Text('📝', style: TextStyle(fontSize: 18)),
label: Text(l10n.notes),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
flex: 2, flex: 2,
child: ElevatedButton.icon( child: BotonFarolero(
texto: l10n.goToVoting,
icono: Icons.how_to_vote,
assetIconPath: 'assets/ui/generated/actions/action_vote_mask.webp',
onPressed: _irAVotacion, onPressed: _irAVotacion,
icon: const Text('🗳️', style: TextStyle(fontSize: 18)),
label: Text(l10n.goToVoting),
), ),
), ),
], ],
@@ -247,6 +149,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
), ),
), ),
), ),
),
); );
} }
} }
+39 -109
View File
@@ -11,8 +11,6 @@ import 'package:farolero/tema/componentes_farolero.dart';
import 'package:farolero/tema/tema_app.dart'; import 'package:farolero/tema/tema_app.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.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 { class PantallaDebateCliente extends StatefulWidget {
final int? tiempoDebateSegundos; final int? tiempoDebateSegundos;
final String? primerTurnoNombre; final String? primerTurnoNombre;
@@ -87,9 +85,7 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
void dispose() { void dispose() {
_timer?.cancel(); _timer?.cancel();
final listener = _listener; final listener = _listener;
if (listener != null) { if (listener != null) _nearby?.removeMensajeListener(listener);
_nearby?.removeMensajeListener(listener);
}
super.dispose(); super.dispose();
} }
@@ -131,7 +127,7 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
actions: [ actions: [
IconButton( IconButton(
tooltip: l10n.seeYourWord, tooltip: l10n.seeYourWord,
icon: const Icon(Icons.visibility), icon: IconoFarolero(Icons.visibility),
onPressed: widget.jugadoresControlados.isEmpty onPressed: widget.jugadoresControlados.isEmpty
? null ? null
: () => mostrarRevisionPalabraOnline( : () => mostrarRevisionPalabraOnline(
@@ -142,7 +138,7 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
), ),
IconButton( IconButton(
tooltip: l10n.notesTitle, tooltip: l10n.notesTitle,
icon: const Icon(Icons.edit_note), icon: IconoFarolero(Icons.edit_note),
onPressed: _puedeAbrirNotas onPressed: _puedeAbrirNotas
? () => Navigator.push( ? () => Navigator.push(
context, context,
@@ -160,141 +156,75 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true, intenso: true,
child: Padding( child: SafeArea(
top: false,
child: SingleChildScrollView(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
children: [ children: [
const ArteGameplayFarolero.fase(height: 124), const ArteGameplayFarolero.fase(height: 124),
const SizedBox(height: 10), const SizedBox(height: 12),
const Spacer(), TarjetaFaseFarolero(
icono: Icons.forum,
// Timer assetIconPath: 'assets/ui/generated/actions/action_rules_book.webp',
titulo: l10n.debate,
subtitulo: l10n.debateInstructions,
child: Column(
children: [
if (widget.tiempoDebateSegundos != null) ...[ if (widget.tiempoDebateSegundos != null) ...[
Container( TemporizadorFarolero(
padding: const EdgeInsets.all(32), etiqueta: _segundosRestantes == 0
decoration: BoxDecoration(
color: _segundosRestantes == 0
? TemaApp.colorAcento.withValues(alpha: 0.3)
: TemaApp.colorTarjeta,
borderRadius: BorderRadius.circular(24),
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: Image.asset(
'assets/ui/generated/gameplay/gameplay_phase_emblem.webp',
fit: BoxFit.contain,
opacity: const AlwaysStoppedAnimation(0.42),
),
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_segundosRestantes == 0
? l10n.timeUp ? l10n.timeUp
: l10n.timeRemaining, : l10n.timeRemaining,
style: Theme.of(context).textTheme.titleMedium, tiempo: _formatearTiempo(_segundosRestantes),
agotado: _segundosRestantes == 0,
), ),
const SizedBox(height: 8), const SizedBox(height: 16),
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 ...[ ] else ...[
Text( Text(
l10n.debatePhaseActive, l10n.debatePhaseActive,
style: Theme.of(context).textTheme.headlineMedium, style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
], ],
// Instrucciones
if (widget.primerTurnoNombre != null) ...[ if (widget.primerTurnoNombre != null) ...[
Container( EstadoJugadorFarolero(
width: double.infinity, nombre: l10n.firstTurnInstruction(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: TemaApp.colorNaranja.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: TemaApp.colorNaranja.withValues(alpha: 0.65),
),
),
child: Row(
children: [
const Icon(
Icons.record_voice_over,
color: TemaApp.colorNaranja,
),
const SizedBox(width: 12),
Expanded(
child: Text(
widget.primerTurnoNombre!, widget.primerTurnoNombre!,
style: Theme.of(context).textTheme.titleMedium,
), ),
destacado: true,
completado: true,
icono: Icons.record_voice_over,
assetIconPath: 'assets/ui/generated/actions/action_players_group.webp',
), ),
const SizedBox(height: 12),
], ],
), BotonFarolero.secundario(
), texto: _votacionSolicitada
const SizedBox(height: 16), ? l10n.votacionSolicitada
], : l10n.solicitarVotacion,
icono: _votacionSolicitada
Text( ? Icons.hourglass_empty
l10n.debateInstructions, : Icons.how_to_vote,
textAlign: TextAlign.center, assetIconPath: _votacionSolicitada
style: TextStyle( ? null
color: TemaApp.colorTextoSecundario, : 'assets/ui/generated/actions/action_vote_mask.webp',
fontSize: 16,
),
),
const Spacer(),
// Botón solicitar votación
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton.icon(
onPressed: _votacionSolicitada onPressed: _votacionSolicitada
? null ? null
: () { : () {
setState(() => _votacionSolicitada = true); setState(() => _votacionSolicitada = true);
widget.onSolicitarVotacion(); 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),
), ),
],
), ),
), ),
], ],
), ),
), ),
), ),
),
); );
} }
+27 -17
View File
@@ -341,7 +341,7 @@ class _IconoResultadoPremium extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (icono != Icons.theater_comedy) { if (icono != Icons.theater_comedy) {
return Icon(icono, size: 82, color: TemaApp.colorDorado); return IconoFarolero(icono, size: 82, color: TemaApp.colorDorado);
} }
return Stack( return Stack(
@@ -351,7 +351,7 @@ class _IconoResultadoPremium extends StatelessWidget {
offset: const Offset(-18, 12), offset: const Offset(-18, 12),
child: Transform.rotate( child: Transform.rotate(
angle: -0.10, angle: -0.10,
child: Icon( child: IconoFarolero(
Icons.mood, Icons.mood,
size: 66, size: 66,
color: TemaApp.colorDorado.withValues(alpha: 0.98), color: TemaApp.colorDorado.withValues(alpha: 0.98),
@@ -362,7 +362,7 @@ class _IconoResultadoPremium extends StatelessWidget {
offset: const Offset(20, -13), offset: const Offset(20, -13),
child: Transform.rotate( child: Transform.rotate(
angle: 0.12, angle: 0.12,
child: Icon( child: IconoFarolero(
Icons.sentiment_dissatisfied, Icons.sentiment_dissatisfied,
size: 70, size: 70,
color: TemaApp.colorDorado.withValues(alpha: 0.98), color: TemaApp.colorDorado.withValues(alpha: 0.98),
@@ -384,6 +384,7 @@ class _TarjetaProgresoGamificacion extends StatelessWidget {
final nuevas = progreso.nuevasMedallas; final nuevas = progreso.nuevasMedallas;
final antes = progreso.antes.fuego.clamp(0, 100); final antes = progreso.antes.fuego.clamp(0, 100);
final despues = progreso.despues.fuego.clamp(0, 100); final despues = progreso.despues.fuego.clamp(0, 100);
final l10n = AppLocalizations.of(context)!;
return _PanelRecompensa( return _PanelRecompensa(
child: Column( child: Column(
@@ -411,16 +412,19 @@ class _TarjetaProgresoGamificacion extends StatelessWidget {
), ),
], ],
), ),
child: const Icon( child: Padding(
Icons.local_fire_department, padding: const EdgeInsets.all(7),
color: Color(0xFF1B1010), child: Image.asset(
size: 30, 'assets/ui/generated/actions/action_fire_badge.webp',
fit: BoxFit.contain,
filterQuality: FilterQuality.medium,
),
), ),
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
Expanded( Expanded(
child: Text( child: Text(
'RECOMPENSAS DE PARTIDA', l10n.matchRewards.toUpperCase(),
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: TemaApp.colorDorado, color: TemaApp.colorDorado,
fontSize: 20, fontSize: 20,
@@ -438,7 +442,7 @@ class _TarjetaProgresoGamificacion extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
if (nuevas.isEmpty) if (nuevas.isEmpty)
Text( Text(
'Sin medallas nuevas esta vez. Seguí acumulando fuego.', l10n.noNewMedalsKeepFire,
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: TemaApp.colorTextoSecundario, color: TemaApp.colorTextoSecundario,
height: 1.35, height: 1.35,
@@ -446,7 +450,7 @@ class _TarjetaProgresoGamificacion extends StatelessWidget {
) )
else ...[ else ...[
Text( Text(
'NUEVAS MEDALLAS', l10n.newMedals.toUpperCase(),
style: Theme.of(context).textTheme.labelLarge?.copyWith( style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: TemaApp.colorDorado, color: TemaApp.colorDorado,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
@@ -486,7 +490,7 @@ class _TarjetaRecompensaCargando extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
'Calculando recompensas...', AppLocalizations.of(context)!.calculatingRewards,
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: TemaApp.colorDorado, color: TemaApp.colorDorado,
), ),
@@ -581,8 +585,13 @@ class _DeltaFuego extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Icon(Icons.local_fire_department, Image.asset(
color: TemaApp.colorNaranja, size: 24), 'assets/ui/generated/actions/action_fire_badge.webp',
width: 26,
height: 26,
fit: BoxFit.contain,
filterQuality: FilterQuality.medium,
),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
texto, texto,
@@ -617,7 +626,7 @@ class _BarraFuegoPremium extends StatelessWidget {
Row( Row(
children: [ children: [
Text( Text(
'Fuego', AppLocalizations.of(context)!.fireLabel,
style: Theme.of(context).textTheme.bodyMedium?.copyWith( style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
), ),
@@ -837,7 +846,7 @@ class _TarjetaImpostores extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.theater_comedy, color: TemaApp.colorAcento), IconoFarolero(Icons.theater_comedy, color: TemaApp.colorAcento),
const SizedBox(width: 8), const SizedBox(width: 8),
Flexible( Flexible(
child: Text( child: Text(
@@ -858,7 +867,7 @@ class _TarjetaImpostores extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(Icons.theater_comedy, IconoFarolero(Icons.theater_comedy,
size: 20, color: TemaApp.colorAcento), size: 20, color: TemaApp.colorAcento),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
@@ -870,7 +879,7 @@ class _TarjetaImpostores extends StatelessWidget {
), ),
if (j.eliminado) ...[ if (j.eliminado) ...[
const SizedBox(width: 8), const SizedBox(width: 8),
const Icon(Icons.close, IconoFarolero(Icons.close,
size: 16, color: TemaApp.colorTextoSecundario), size: 16, color: TemaApp.colorTextoSecundario),
], ],
], ],
@@ -949,6 +958,7 @@ class _BotonesFinPartida extends StatelessWidget {
BotonFarolero( BotonFarolero(
texto: l10n.rematch, texto: l10n.rematch,
icono: Icons.replay, icono: Icons.replay,
assetIconPath: 'assets/ui/generated/actions/action_create_game.webp',
onPressed: () { onPressed: () {
estado.revancha(); estado.revancha();
Navigator.pushReplacement( Navigator.pushReplacement(
+171 -299
View File
@@ -1,15 +1,18 @@
import 'package:confetti/confetti.dart';
import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:farolero/l10n/generated/app_localizations.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../modelos/inicio_partida_multijugador.dart';
import '../modelos/gamificacion_usuario.dart'; import '../modelos/gamificacion_usuario.dart';
import '../modelos/inicio_partida_multijugador.dart';
import '../modelos/jugador.dart';
import '../modelos/palabra.dart'; import '../modelos/palabra.dart';
import '../modelos/snapshot_partida_online.dart'; import '../modelos/snapshot_partida_online.dart';
import '../servicios/servicio_historial_partidas.dart'; import '../servicios/servicio_historial_partidas.dart';
import '../servicios/servicio_nearby.dart'; import '../servicios/servicio_nearby.dart';
import '../servicios/servicio_perfil_usuario.dart'; import '../servicios/servicio_perfil_usuario.dart';
import '../tema/componentes_farolero.dart'; import '../tema/componentes_farolero.dart';
import '../tema/componentes_resultado_farolero.dart';
import '../tema/tema_app.dart'; import '../tema/tema_app.dart';
import 'pantalla_notas_online.dart'; import 'pantalla_notas_online.dart';
import 'pantalla_principal.dart'; import 'pantalla_principal.dart';
@@ -35,183 +38,133 @@ class PantallaFinPartidaOnline extends StatefulWidget {
class _PantallaFinPartidaOnlineState extends State<PantallaFinPartidaOnline> { class _PantallaFinPartidaOnlineState extends State<PantallaFinPartidaOnline> {
bool _guardada = false; bool _guardada = false;
ProgresoGamificacionUsuario? _progreso; ProgresoGamificacionUsuario? _progreso;
late final ConfettiController _confetti;
@override
void initState() {
super.initState();
_confetti = ConfettiController(duration: const Duration(seconds: 5));
Future.delayed(const Duration(milliseconds: 450), () {
if (mounted) _confetti.play();
});
}
@override
void dispose() {
_confetti.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final snapshot = widget.snapshot; final snapshot = widget.snapshot;
final jugadoresControlados = widget.jugadoresControlados;
final pistaCategoria = widget.pistaCategoria;
final ganaronJugadores = snapshot.ganador == 'jugadores'; final ganaronJugadores = snapshot.ganador == 'jugadores';
final color = ganaronJugadores ? TemaApp.colorVerde : TemaApp.colorAcento;
if (!_guardada && snapshot.ganador != null) { _registrarResultadoSiHaceFalta(context, snapshot);
_guardada = true;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (mounted) {
final historial = context.read<ServicioHistorialPartidas>();
final perfil = context.read<ServicioPerfilUsuario>();
await historial.guardarSnapshotOnline(snapshot);
final progreso = await perfil.registrarPartidaCompletada(
victoria: _perfilLocalGano(snapshot, jugadoresControlados),
comoImpostor: jugadoresControlados.any((j) => j.esImpostor),
victoriaComoImpostor: snapshot.ganador == 'impostores' &&
jugadoresControlados.any((j) => j.esImpostor),
);
if (mounted) setState(() => _progreso = progreso);
}
});
}
return Scaffold( return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: const Color(0xFF05070D),
appBar: AppBar( appBar: AppBar(
title: Text(l10n.gameOver), title: Text(l10n.gameOver),
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ backgroundColor: Colors.transparent,
IconButton( elevation: 0,
tooltip: l10n.seeYourWord, actions: _acciones(context, l10n, snapshot),
icon: const Icon(Icons.visibility),
onPressed: jugadoresControlados.isEmpty
? null
: () => mostrarRevisionPalabraOnline(
context: context,
jugadoresControlados: jugadoresControlados,
pistaCategoria: pistaCategoria,
),
),
IconButton(
tooltip: l10n.notesTitle,
icon: const Icon(Icons.edit_note),
onPressed: snapshot.roomId == null || jugadoresControlados.isEmpty
? null
: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PantallaNotasOnline(
partidaId: snapshot.roomId!,
jugadores: snapshot.jugadores,
autoresControlados: jugadoresControlados,
),
),
),
),
],
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true, intenso: true,
child: SingleChildScrollView( child: Stack(
padding: const EdgeInsets.all(24),
child: Column(
children: [ children: [
_ResultadoOnlineHero( Positioned.fill(
ganaronJugadores: ganaronJugadores, child: IgnorePointer(
titulo: ganaronJugadores ? l10n.playersWin : l10n.impostorsWin, child: CustomPaint(
painter: EscenarioFinPartidaFaroleroPainter(color: color),
), ),
const SizedBox(height: 24), ),
if (_progreso != null) ...[ ),
_TarjetaProgresoGamificacion(progreso: _progreso!), Align(
const SizedBox(height: 16), alignment: Alignment.topCenter,
child: ConfettiWidget(
confettiController: _confetti,
blastDirectionality: BlastDirectionality.explosive,
emissionFrequency: 0.09,
numberOfParticles: 28,
gravity: 0.18,
colors: const [
TemaApp.colorDorado,
TemaApp.colorNaranja,
TemaApp.colorAcento,
Color(0xFFFFECBE),
], ],
Card( ),
child: Padding( ),
padding: const EdgeInsets.all(20), Positioned.fill(
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.transparent,
Colors.black.withValues(alpha: 0.10),
Colors.black.withValues(alpha: 0.52),
],
stops: const [0.0, 0.54, 1.0],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
),
),
SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 72, 20, 28),
child: Column( child: Column(
children: [ children: [
Text( HeroFinalPartidaFarolero(
l10n.theSecretWordWas, encabezado: l10n.gameOver,
style: Theme.of(context).textTheme.titleMedium, titulo:
ganaronJugadores ? l10n.playersWin : l10n.impostorsWin,
icono: ganaronJugadores
? Icons.emoji_events
: Icons.theater_comedy,
color: color,
), ),
const SizedBox(height: 8), const SizedBox(height: 12),
Text( if (_progreso == null)
snapshot.palabraSecreta ?? '?', const TarjetaRecompensaCargandoPremium()
style: Theme.of(context).textTheme.headlineLarge?.copyWith( else
color: TemaApp.colorNaranja, TarjetaProgresoGamificacionPremium(progreso: _progreso!),
fontSize: 32, const SizedBox(height: 18),
), if (snapshot.palabraSecreta != null)
), TarjetaSecretoPremium(
const SizedBox(height: 4), palabra: snapshot.palabraSecreta!,
Text( categoria: BancoPalabras.nombreBonitoCategoria(
l10n.categoryLabel(
BancoPalabras.nombreBonitoCategoria(
snapshot.categoria, snapshot.categoria,
l10n, l10n,
), ),
), ),
style: Theme.of(context).textTheme.bodyMedium, const SizedBox(height: 18),
), TarjetaImpostoresPremium(
], titulo: snapshot.impostores.length == 1
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text(
snapshot.impostores.length == 1
? l10n.theImpostorWas ? l10n.theImpostorWas
: l10n.theImpostorsWere, : l10n.theImpostorsWere,
style: Theme.of(context).textTheme.titleMedium, impostores: _impostores(snapshot),
), ),
const SizedBox(height: 8), const SizedBox(height: 18),
...snapshot.impostores.map(
(nombre) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text(
'🎭 $nombre',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: TemaApp.colorAcento,
),
),
),
),
],
),
),
),
const SizedBox(height: 16),
if (snapshot.historialVotaciones.isNotEmpty) if (snapshot.historialVotaciones.isNotEmpty)
Card( TarjetaHistorialVotosPremium(
child: Padding( historial: snapshot.historialVotaciones,
padding: const EdgeInsets.all(20), jugadores: snapshot.jugadores,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.votingHistory,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
...snapshot.historialVotaciones.asMap().entries.map(
(entrada) {
final ronda = entrada.key + 1;
final resultado = entrada.value;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Text(
l10n.roundElimination(
ronda,
resultado.eliminadoNombre,
),
style: TextStyle(
fontWeight: FontWeight.bold,
color: resultado.eraImpostor
? TemaApp.colorVerde
: TemaApp.colorAcento,
),
),
);
},
),
],
),
),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
BotonFarolero.oscuro( BotonFarolero.oscuro(
texto: l10n.mainMenu, texto: l10n.mainMenu,
icono: Icons.home, icono: Icons.home,
assetIconPath: 'assets/ui/generated/actions/action_result_trophy.webp',
onPressed: () async { onPressed: () async {
await context.read<ServicioNearby>().desconectar(); await context.read<ServicioNearby>().desconectar();
if (!context.mounted) return; if (!context.mounted) return;
@@ -224,13 +177,78 @@ class _PantallaFinPartidaOnlineState extends State<PantallaFinPartidaOnline> {
); );
}, },
), ),
const SizedBox(height: 16),
], ],
), ),
), ),
), ),
],
),
),
); );
} }
List<Widget> _acciones(
BuildContext context,
AppLocalizations l10n,
SnapshotPartidaOnline snapshot,
) {
return [
IconButton(
tooltip: l10n.seeYourWord,
icon: IconoFarolero(Icons.visibility),
onPressed: widget.jugadoresControlados.isEmpty
? null
: () => mostrarRevisionPalabraOnline(
context: context,
jugadoresControlados: widget.jugadoresControlados,
pistaCategoria: widget.pistaCategoria,
),
),
IconButton(
tooltip: l10n.notesTitle,
icon: IconoFarolero(Icons.edit_note),
onPressed: snapshot.roomId == null || widget.jugadoresControlados.isEmpty
? null
: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PantallaNotasOnline(
partidaId: snapshot.roomId!,
jugadores: snapshot.jugadores,
autoresControlados: widget.jugadoresControlados,
),
),
),
),
];
}
void _registrarResultadoSiHaceFalta(
BuildContext context,
SnapshotPartidaOnline snapshot,
) {
if (_guardada || snapshot.ganador == null) return;
_guardada = true;
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!mounted) return;
final historial = context.read<ServicioHistorialPartidas>();
final perfil = context.read<ServicioPerfilUsuario>();
await historial.guardarSnapshotOnline(snapshot);
final progreso = await perfil.registrarPartidaCompletada(
victoria: _perfilLocalGano(snapshot, widget.jugadoresControlados),
comoImpostor: widget.jugadoresControlados.any((j) => j.esImpostor),
victoriaComoImpostor: snapshot.ganador == 'impostores' &&
widget.jugadoresControlados.any((j) => j.esImpostor),
);
if (!mounted) return;
setState(() => _progreso = progreso);
if (progreso.nuevasMedallas.isNotEmpty || progreso.incrementoFuego > 0) {
_confetti.play();
}
});
}
bool _perfilLocalGano( bool _perfilLocalGano(
SnapshotPartidaOnline snapshot, SnapshotPartidaOnline snapshot,
List<JugadorInicioPartida> jugadoresControlados, List<JugadorInicioPartida> jugadoresControlados,
@@ -241,161 +259,15 @@ class _PantallaFinPartidaOnlineState extends State<PantallaFinPartidaOnline> {
(jugador) => jugador.esImpostor ? ganaronImpostores : !ganaronImpostores, (jugador) => jugador.esImpostor ? ganaronImpostores : !ganaronImpostores,
); );
} }
}
class _ResultadoOnlineHero extends StatelessWidget { List<Jugador> _impostores(SnapshotPartidaOnline snapshot) {
final bool ganaronJugadores; final porNombre = {for (final jugador in snapshot.jugadores) jugador.nombre: jugador};
final String titulo; return snapshot.impostores
.map(
const _ResultadoOnlineHero({ (nombre) =>
required this.ganaronJugadores, porNombre[nombre] ??
required this.titulo, Jugador(id: nombre, nombre: nombre, esImpostor: true),
});
@override
Widget build(BuildContext context) {
final color = ganaronJugadores ? TemaApp.colorVerde : TemaApp.colorAcento;
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 18, 20, 24),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
color.withValues(alpha: 0.20),
const Color(0xFF101827).withValues(alpha: 0.68),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
borderRadius: BorderRadius.circular(28),
border: Border.all(color: color.withValues(alpha: 0.65)),
),
child: Column(
children: [
Image.asset(
'assets/ui/generated/final_rewards/cinematic_burst.webp',
height: 250,
fit: BoxFit.contain,
opacity: const AlwaysStoppedAnimation(0.95),
),
const SizedBox(height: 8),
Text(
titulo,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: color,
fontWeight: FontWeight.w900,
shadows: [
Shadow(color: color.withValues(alpha: 0.75), blurRadius: 18),
],
),
textAlign: TextAlign.center,
),
],
),
).animate().fadeIn(duration: 320.ms).slideY(begin: -0.08);
}
}
class _TarjetaProgresoGamificacion extends StatelessWidget {
final ProgresoGamificacionUsuario progreso;
const _TarjetaProgresoGamificacion({required this.progreso});
@override
Widget build(BuildContext context) {
final nuevas = progreso.nuevasMedallas;
return Container(
width: double.infinity,
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF111B28).withValues(alpha: 0.96),
const Color(0xFF180D22).withValues(alpha: 0.94),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(22),
border: Border.all(color: TemaApp.colorDorado.withValues(alpha: 0.52)),
boxShadow: [
BoxShadow(
color: TemaApp.colorNaranja.withValues(alpha: 0.18),
blurRadius: 32,
offset: const Offset(0, 16),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.local_fire_department,
color: TemaApp.colorNaranja),
const SizedBox(width: 8),
Expanded(
child: Text(
'RECOMPENSAS DE PARTIDA',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: TemaApp.colorDorado,
fontWeight: FontWeight.w900,
letterSpacing: 1,
),
),
),
Text(
progreso.incrementoFuego >= 0
? '+${progreso.incrementoFuego}'
: '${progreso.incrementoFuego}',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: TemaApp.colorDorado,
fontWeight: FontWeight.w900,
),
),
],
),
const SizedBox(height: 12),
TweenAnimationBuilder<double>(
tween: Tween(
begin: progreso.antes.fuego.clamp(0, 100) / 100,
end: progreso.despues.fuego.clamp(0, 100) / 100,
),
duration: const Duration(milliseconds: 1300),
curve: Curves.easeOutCubic,
builder: (context, value, _) => ClipRRect(
borderRadius: BorderRadius.circular(999),
child: LinearProgressIndicator(
value: value.clamp(0.0, 1.0).toDouble(),
minHeight: 18,
backgroundColor: Colors.black.withValues(alpha: 0.35),
valueColor: const AlwaysStoppedAnimation(TemaApp.colorNaranja),
),
),
),
const SizedBox(height: 8),
Text(
'${progreso.despues.fuego}% de fuego',
style: Theme.of(context).textTheme.bodySmall,
),
if (nuevas.isNotEmpty) ...[
const SizedBox(height: 12),
Text(
'NUEVAS MEDALLAS',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: TemaApp.colorDorado,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 6),
MedallasCompactasFarolero(ids: nuevas, max: nuevas.length),
],
],
),
) )
.animate() .toList();
.fadeIn(duration: 360.ms)
.slideY(begin: 0.12)
.shimmer(delay: 700.ms, duration: 1200.ms);
} }
} }
+175 -492
View File
@@ -7,12 +7,14 @@ import '../estado/estado_juego.dart';
import '../modelos/gamificacion_usuario.dart'; import '../modelos/gamificacion_usuario.dart';
import '../modelos/inicio_partida_multijugador.dart'; import '../modelos/inicio_partida_multijugador.dart';
import '../modelos/jugador.dart'; import '../modelos/jugador.dart';
import '../modelos/palabra.dart';
import '../modelos/partida.dart'; import '../modelos/partida.dart';
import '../modelos/snapshot_partida_online.dart'; import '../modelos/snapshot_partida_online.dart';
import '../servicios/servicio_historial_partidas.dart'; import '../servicios/servicio_historial_partidas.dart';
import '../servicios/servicio_nearby.dart'; import '../servicios/servicio_nearby.dart';
import '../servicios/servicio_perfil_usuario.dart'; import '../servicios/servicio_perfil_usuario.dart';
import '../tema/componentes_farolero.dart'; import '../tema/componentes_farolero.dart';
import '../tema/componentes_resultado_farolero.dart';
import '../tema/tema_app.dart'; import '../tema/tema_app.dart';
import 'pantalla_notas_online.dart'; import 'pantalla_notas_online.dart';
import 'pantalla_revision_palabra.dart'; import 'pantalla_revision_palabra.dart';
@@ -161,7 +163,7 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
actions: [ actions: [
IconButton( IconButton(
tooltip: l10n.seeYourWord, tooltip: l10n.seeYourWord,
icon: const Icon(Icons.visibility), icon: IconoFarolero(Icons.visibility),
onPressed: partida.fase.index <= FaseJuego.verPalabra.index onPressed: partida.fase.index <= FaseJuego.verPalabra.index
? null ? null
: () => mostrarRevisionPalabraOnline( : () => mostrarRevisionPalabraOnline(
@@ -177,7 +179,7 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
), ),
IconButton( IconButton(
tooltip: l10n.notesTitle, tooltip: l10n.notesTitle,
icon: const Icon(Icons.edit_note), icon: IconoFarolero(Icons.edit_note),
onPressed: partida.fase.index < FaseJuego.debate.index || onPressed: partida.fase.index < FaseJuego.debate.index ||
nearby.roomId == null nearby.roomId == null
? null ? null
@@ -196,7 +198,7 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
), ),
), ),
IconButton( IconButton(
icon: const Icon(Icons.close), icon: IconoFarolero(Icons.close),
onPressed: () async { onPressed: () async {
await nearby.desconectar(); await nearby.desconectar();
widget.onPartidaFin(); widget.onPartidaFin();
@@ -261,7 +263,7 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
children: [ children: [
Row( Row(
children: [ children: [
const Icon(Icons.link_off, color: TemaApp.colorAcento), IconoFarolero(Icons.link_off, color: TemaApp.colorAcento),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
@@ -279,10 +281,14 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
const SizedBox(height: 8), const SizedBox(height: 8),
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: OutlinedButton.icon( child: SizedBox(
width: 260,
child: BotonFarolero.oscuro(
texto: AppLocalizations.of(context)!.assumeOnThisPhone,
icono: Icons.person_add_alt_1,
assetIconPath: 'assets/ui/generated/actions/action_add_player.webp',
onPressed: () => nearby.asumirUsuariosDesconectados(), onPressed: () => nearby.asumirUsuariosDesconectados(),
icon: const Icon(Icons.person_add_alt_1), ),
label: Text(AppLocalizations.of(context)!.assumeOnThisPhone),
), ),
), ),
], ],
@@ -404,68 +410,38 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
bool todosListos, bool todosListos,
ServicioNearby nearby, ServicioNearby nearby,
) { ) {
return Card( return TarjetaFaseFarolero(
child: Padding( icono: Icons.visibility,
padding: const EdgeInsets.all(16), assetIconPath: 'assets/ui/generated/actions/action_reveal_word.webp',
titulo: l10n.waitingPlayersSeeWord,
subtitulo: l10n.connectedPlayers,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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, _hostListo), _buildJugadorTile(nearby.miNombre ?? 'Host', true, _hostListo),
...nearby.jugadores.map( ...nearby.jugadores.map(
(j) => _buildJugadorTile( (jugador) => _buildJugadorTile(
j.nombre, jugador.nombre,
false, false,
_clientesListos[j.endpointId] ?? false, _clientesListos[jugador.endpointId] ?? false,
),
),
const Spacer(),
// Botón para que el host vea su palabra
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: () => _mostrarPalabraHost(context),
icon: const Icon(Icons.visibility),
label: Text(l10n.seeYourWord),
style: ElevatedButton.styleFrom(
backgroundColor: TemaApp.colorNaranja,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
if (todosListos) BotonFarolero(
Container( texto: l10n.seeYourWord,
padding: const EdgeInsets.all(12), icono: Icons.visibility,
decoration: BoxDecoration( assetIconPath: 'assets/ui/generated/actions/action_reveal_word.webp',
color: TemaApp.colorVerde.withValues(alpha: 0.2), onPressed: () => _mostrarPalabraHost(context),
borderRadius: BorderRadius.circular(12),
), ),
child: Row( if (todosListos) ...[
mainAxisAlignment: MainAxisAlignment.center, const SizedBox(height: 12),
children: [ EstadoJugadorFarolero(
const Icon(Icons.check_circle, color: TemaApp.colorVerde), nombre: l10n.allSeenStartDebate,
const SizedBox(width: 8), completado: true,
Text( icono: Icons.check_circle,
l10n.allSeenStartDebate,
style: const TextStyle(color: TemaApp.colorVerde),
), ),
], ],
),
),
], ],
), ),
),
); );
} }
@@ -563,67 +539,34 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
final estado = context.read<EstadoJuego>(); final estado = context.read<EstadoJuego>();
final tiempo = estado.partida?.config.tiempoDebateSegundos; final tiempo = estado.partida?.config.tiempoDebateSegundos;
return Card( return TarjetaFaseFarolero(
child: Padding( icono: Icons.forum,
padding: const EdgeInsets.all(16), assetIconPath: 'assets/ui/generated/actions/action_rules_book.webp',
titulo: l10n.debate,
subtitulo: l10n.debateInstructions,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (tiempo != null) ...[ if (tiempo != null) ...[
Text(l10n.debate, style: Theme.of(context).textTheme.titleLarge), TemporizadorFarolero(
const SizedBox(height: 16), etiqueta: _segundosRestantes == 0
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.timeUp
: l10n.timeRemaining, : l10n.timeRemaining,
style: Theme.of(context).textTheme.bodyMedium, tiempo: _formatearTiempo(_segundosRestantes),
), agotado: _segundosRestantes == 0,
Text(
_formatearTiempo(_segundosRestantes),
style: Theme.of(context).textTheme.headlineLarge,
),
],
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
], ],
_buildPrimerTurno(context), _buildPrimerTurno(context),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(l10n.activePlayers, style: Theme.of(context).textTheme.titleMedium),
l10n.activePlayers,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8), const SizedBox(height: 8),
Expanded( _buildJugadorTile(nearby.miNombre ?? 'Host', true, true),
child: ListView.builder( ...nearby.jugadores.map(
itemCount: nearby.jugadores.length + 1, (jugador) => _buildJugadorTile(jugador.nombre, false, true),
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);
},
),
), ),
], ],
), ),
),
); );
} }
@@ -640,11 +583,11 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
), ),
child: Row( child: Row(
children: [ children: [
const Icon(Icons.record_voice_over, color: TemaApp.colorNaranja), IconoFarolero(Icons.record_voice_over, color: TemaApp.colorNaranja),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
'Empieza $nombre diciendo su palabra.', AppLocalizations.of(context)!.firstTurnInstruction(nombre),
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
), ),
@@ -664,90 +607,44 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
final votosEmitidos = estado.votos.length; final votosEmitidos = estado.votos.length;
final progreso = totalVotos == 0 ? 0.0 : votosEmitidos / totalVotos; final progreso = totalVotos == 0 ? 0.0 : votosEmitidos / totalVotos;
return Card( return TarjetaFaseFarolero(
child: Padding( icono: Icons.how_to_vote,
padding: const EdgeInsets.all(16), assetIconPath: 'assets/ui/generated/actions/action_vote_mask.webp',
titulo: l10n.voting,
subtitulo: l10n.votesProgress(votosEmitidos, totalVotos),
color: TemaApp.colorAcento,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ 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(votosEmitidos, totalVotos)),
const SizedBox(height: 8),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(999),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: progreso.clamp(0.0, 1.0).toDouble(), value: progreso.clamp(0.0, 1.0).toDouble(),
backgroundColor: TemaApp.colorSuperficie, minHeight: 14,
valueColor: const AlwaysStoppedAnimation( backgroundColor: Colors.black.withValues(alpha: 0.35),
TemaApp.colorAcento, valueColor: const AlwaysStoppedAnimation(TemaApp.colorAcento),
),
minHeight: 8,
),
),
],
),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: _hostYaVoto(context) ? null : () => _abrirVotacionHost(context),
icon: const Icon(Icons.how_to_vote),
label: Text(
_hostYaVoto(context)
? l10n.playersVoted
: l10n.confirmVote,
),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( if (!_hostYaVoto(context))
l10n.playersVoted, BotonFarolero.secundario(
style: Theme.of(context).textTheme.titleMedium, texto: l10n.votar,
icono: Icons.how_to_vote,
onPressed: () => _abrirVotacionHost(context),
), ),
const SizedBox(height: 8), if (!_hostYaVoto(context)) const SizedBox(height: 16),
Expanded( ...partida.jugadoresActivos.map((jugador) {
child: ListView.builder(
itemCount: partida.jugadoresActivos.length,
itemBuilder: (context, index) {
final jugador = partida.jugadoresActivos[index];
final haVotado = estado.votos.containsKey(jugador.id); final haVotado = estado.votos.containsKey(jugador.id);
return _buildJugadorTile(jugador.nombre, false, haVotado); return _buildJugadorTile(jugador.nombre, false, haVotado);
}, }),
),
),
if (todosVotaron) if (todosVotaron)
Container( EstadoJugadorFarolero(
padding: const EdgeInsets.all(12), nombre: l10n.allVoted,
decoration: BoxDecoration( completado: true,
color: TemaApp.colorVerde.withValues(alpha: 0.2), icono: Icons.check_circle,
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),
), ),
], ],
), ),
),
],
),
),
); );
} }
@@ -760,132 +657,11 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
return Center(child: Text(l10n.noResult)); return Center(child: Text(l10n.noResult));
} }
final conteo = <String, int>{}; return SingleChildScrollView(
for (final votadoId in resultado.votos.values) { padding: const EdgeInsets.only(bottom: 8),
conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; child: ResultadoRondaFarolero(
} resultado: resultado,
final maxVotos = conteo.values.isEmpty jugadores: partida.jugadores,
? 1
: conteo.values.reduce((a, b) => a > b ? a : b);
final ranking = conteo.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.result, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: TemaApp.decoracionPanel(
color: resultado.eraImpostor
? TemaApp.colorVerde.withValues(alpha: 0.18)
: TemaApp.colorAcento.withValues(alpha: 0.18),
borderColor: resultado.eraImpostor
? TemaApp.colorVerde
: TemaApp.colorAcento,
),
child: Column(
children: [
Text(
resultado.eliminadoNombre,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 6),
Text(
resultado.eraImpostor
? l10n.wasImpostor
: l10n.wasInnocent,
style: TextStyle(
color: resultado.eraImpostor
? TemaApp.colorVerde
: TemaApp.colorAcento,
fontWeight: FontWeight.bold,
),
),
],
),
),
const SizedBox(height: 16),
Text(l10n.votesThisRound,
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 12),
Expanded(
child: ListView(
children: [
...ranking.map((entry) {
final jugador = partida.jugadores.firstWhere(
(j) => j.id == entry.key,
orElse: () => partida.jugadores.first,
);
return _buildBarraResultado(
context,
nombre: jugador.nombre,
votos: entry.value,
maxVotos: maxVotos,
destacado: entry.key == resultado.eliminadoId,
);
}),
const Divider(height: 24),
...resultado.votos.entries.map((entry) {
final votante = partida.jugadores.firstWhere(
(j) => j.id == entry.key,
orElse: () => partida.jugadores.first,
);
final votado = partida.jugadores.firstWhere(
(j) => j.id == entry.value,
orElse: () => partida.jugadores.first,
);
return ListTile(
dense: true,
leading: const Icon(Icons.how_to_vote),
title: Text('${votante.nombre}${votado.nombre}'),
);
}),
],
),
),
],
),
),
);
}
Widget _buildBarraResultado(
BuildContext context, {
required String nombre,
required int votos,
required int maxVotos,
required bool destacado,
}) {
final color = destacado ? TemaApp.colorAcento : TemaApp.colorNaranja;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: Text(nombre)),
Text('$votos',
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 6),
ClipRRect(
borderRadius: BorderRadius.circular(999),
child: LinearProgressIndicator(
value: (votos / maxVotos).clamp(0.0, 1.0).toDouble(),
minHeight: 10,
backgroundColor: TemaApp.colorSuperficie,
valueColor: AlwaysStoppedAnimation(color),
),
),
],
), ),
); );
} }
@@ -895,102 +671,64 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
final ultimo = partida?.historialVotaciones.isNotEmpty == true final ultimo = partida?.historialVotaciones.isNotEmpty == true
? partida!.historialVotaciones.last ? partida!.historialVotaciones.last
: null; : null;
return Card( return TarjetaFaseFarolero(
child: Padding( icono: Icons.psychology,
padding: const EdgeInsets.all(20), assetIconPath: 'assets/ui/generated/actions/action_impostor_mask.webp',
child: Column( titulo: l10n.impostorGuessTitle,
mainAxisAlignment: MainAxisAlignment.center, subtitulo: ultimo == null
children: [
const Icon(Icons.psychology, size: 56, color: TemaApp.colorNaranja),
const SizedBox(height: 16),
Text(
l10n.impostorGuessTitle,
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Text(
ultimo == null
? l10n.impostorCanGuess ? l10n.impostorCanGuess
: '${ultimo.eliminadoNombre}: ${l10n.impostorCanGuess}', : '${ultimo.eliminadoNombre}: ${l10n.impostorCanGuess}',
textAlign: TextAlign.center, color: TemaApp.colorNaranja,
), child: const ArteGameplayFarolero.resultado(height: 132),
],
),
),
); );
} }
Widget _buildFaseFinOnline(BuildContext context, AppLocalizations l10n) { Widget _buildFaseFinOnline(BuildContext context, AppLocalizations l10n) {
final partida = context.watch<EstadoJuego>().partida; final partida = context.watch<EstadoJuego>().partida;
final ganaronJugadores = partida?.ganador == 'jugadores'; if (partida == null) return Center(child: Text(l10n.noResult));
return Card(
child: Padding( final ganaronJugadores = partida.ganador == 'jugadores';
padding: const EdgeInsets.all(24), final color = ganaronJugadores ? TemaApp.colorVerde : TemaApp.colorAcento;
final impostores = partida.jugadores.where((j) => j.esImpostor).toList();
return SingleChildScrollView(
padding: const EdgeInsets.only(bottom: 8),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( HeroFinalPartidaFarolero(
ganaronJugadores ? '🎉' : '🎭', encabezado: l10n.gameOver,
style: const TextStyle(fontSize: 64), titulo: ganaronJugadores ? l10n.playersWin : l10n.impostorsWin,
), icono: ganaronJugadores ? Icons.emoji_events : Icons.theater_comedy,
const SizedBox(height: 16), color: color,
Text(
ganaronJugadores ? l10n.playersWin : l10n.impostorsWin,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( if (_progresoGamificacion == null)
partida == null ? '' : l10n.theWordWas(partida.palabraSecreta), const TarjetaRecompensaCargandoPremium()
textAlign: TextAlign.center, else
TarjetaProgresoGamificacionPremium(
progreso: _progresoGamificacion!,
), ),
if (_progresoGamificacion != null) ...[ const SizedBox(height: 18),
const SizedBox(height: 16), TarjetaSecretoPremium(
_buildProgresoGamificacion(context, _progresoGamificacion!), palabra: partida.palabraSecreta,
], categoria: BancoPalabras.nombreBonitoCategoria(
], partida.categoriaReal,
l10n,
), ),
), ),
); const SizedBox(height: 18),
} TarjetaImpostoresPremium(
titulo: impostores.length == 1
Widget _buildProgresoGamificacion( ? l10n.theImpostorWas
BuildContext context, : l10n.theImpostorsWere,
ProgresoGamificacionUsuario progreso, impostores: impostores,
) {
final nuevas = progreso.nuevasMedallas;
return PanelFarolero(
padding: const EdgeInsets.all(14),
color: TemaApp.colorSuperficie.withValues(alpha: 0.82),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Tu progreso', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 10),
Row(
children: [
const Text('🔥', style: TextStyle(fontSize: 22)),
const SizedBox(width: 8),
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(6),
child: LinearProgressIndicator(
value: progreso.despues.fuego / 100,
minHeight: 8,
color: TemaApp.colorNaranja,
backgroundColor: Colors.black.withValues(alpha: 0.35),
), ),
const SizedBox(height: 18),
if (partida.historialVotaciones.isNotEmpty)
TarjetaHistorialVotosPremium(
historial: partida.historialVotaciones,
jugadores: partida.jugadores,
), ),
),
const SizedBox(width: 8),
Text('${progreso.despues.fuego}%'),
],
),
if (nuevas.isNotEmpty) ...[
const SizedBox(height: 10),
MedallasCompactasFarolero(ids: nuevas, max: nuevas.length),
],
], ],
), ),
); );
@@ -1046,28 +784,12 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
} }
Widget _buildJugadorTile(String nombre, bool esHost, bool listo) { Widget _buildJugadorTile(String nombre, bool esHost, bool listo) {
return Container( return EstadoJugadorFarolero(
margin: const EdgeInsets.only(bottom: 8), nombre: nombre,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), destacado: esHost,
decoration: BoxDecoration( completado: listo,
color: listo icono: esHost ? Icons.phone_android : Icons.devices,
? TemaApp.colorVerde.withValues(alpha: 0.2) assetIconPath: esHost ? 'assets/ui/generated/actions/action_mobile_device.webp' : 'assets/ui/generated/actions/action_multidevice_signal.webp',
: TemaApp.colorTarjeta,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
esHost ? Icons.phone_android : Icons.devices,
color: esHost ? TemaApp.colorDorado : TemaApp.colorNaranja,
size: 22,
),
const SizedBox(width: 8),
Expanded(child: Text(nombre)),
if (listo)
const Icon(Icons.check_circle, color: TemaApp.colorVerde, size: 20),
],
),
); );
} }
@@ -1080,60 +802,39 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
) { ) {
switch (fase) { switch (fase) {
case FaseJuego.verPalabra: case FaseJuego.verPalabra:
return SizedBox( return BotonFarolero(
width: double.infinity, texto: todosListos ? l10n.allSeenStartDebate : l10n.waitingPlayersSeeWord,
height: 56, icono: Icons.forum,
child: ElevatedButton.icon( assetIconPath: 'assets/ui/generated/actions/action_rules_book.webp',
onPressed: todosListos onPressed: todosListos ? () => _avanzarAFase(FaseJuego.debate) : null,
? () => _avanzarAFase(FaseJuego.debate)
: null,
icon: const Icon(Icons.forum),
label: Text(
todosListos
? l10n.allSeenStartDebate
: l10n.waitingPlayersSeeWord,
),
),
); );
case FaseJuego.debate: case FaseJuego.debate:
return SizedBox( return BotonFarolero.secundario(
width: double.infinity, texto: l10n.goToVoting,
height: 56, icono: Icons.how_to_vote,
child: ElevatedButton.icon( assetIconPath: 'assets/ui/generated/actions/action_vote_mask.webp',
onPressed: () => _avanzarAFase(FaseJuego.votacion), onPressed: () => _avanzarAFase(FaseJuego.votacion),
icon: const Icon(Icons.how_to_vote),
label: Text(l10n.goToVoting),
),
); );
case FaseJuego.votacion: case FaseJuego.votacion:
return SizedBox( return BotonFarolero(
width: double.infinity, texto: todosVotaron ? l10n.revealResult : l10n.waitingVoting,
height: 56, icono: Icons.visibility,
child: ElevatedButton.icon( assetIconPath: 'assets/ui/generated/actions/action_result_trophy.webp',
onPressed: todosVotaron onPressed: todosVotaron ? () => _avanzarAFase(FaseJuego.resultado) : null,
? () => _avanzarAFase(FaseJuego.resultado)
: null,
icon: const Icon(Icons.visibility),
label: Text(todosVotaron ? l10n.revealResult : l10n.waitingVoting),
),
); );
case FaseJuego.resultado: case FaseJuego.resultado:
return _buildAccionesResultado(context, l10n); return _buildAccionesResultado(context, l10n);
case FaseJuego.adivinanza: case FaseJuego.adivinanza:
return _buildAccionesAdivinanza(context, l10n); return _buildAccionesAdivinanza(context, l10n);
case FaseJuego.finPartida: case FaseJuego.finPartida:
return SizedBox( return BotonFarolero.oscuro(
width: double.infinity, texto: l10n.mainMenu,
height: 56, icono: Icons.home,
child: OutlinedButton.icon(
onPressed: () async { onPressed: () async {
final nearby = context.read<ServicioNearby>(); final nearby = context.read<ServicioNearby>();
await nearby.desconectar(); await nearby.desconectar();
widget.onPartidaFin(); widget.onPartidaFin();
}, },
icon: const Icon(Icons.home),
label: Text(l10n.mainMenu),
),
); );
default: default:
return const SizedBox.shrink(); return const SizedBox.shrink();
@@ -1149,75 +850,54 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
if (partida == null || resultado == null) return const SizedBox.shrink(); if (partida == null || resultado == null) return const SizedBox.shrink();
if (_hayFinTrasVotacion(partida)) { if (_hayFinTrasVotacion(partida)) {
return SizedBox( return BotonFarolero(
width: double.infinity, texto: l10n.seeEndResult,
height: 56, icono: Icons.emoji_events,
child: ElevatedButton.icon( assetIconPath: 'assets/ui/generated/actions/action_result_trophy.webp',
onPressed: () => _finalizarPartidaOnline(context), onPressed: () => _finalizarPartidaOnline(context),
icon: const Icon(Icons.emoji_events),
label: Text(l10n.seeEndResult),
),
); );
} }
if (resultado.eraImpostor) { if (resultado.eraImpostor) {
return Column( return Column(
children: [ children: [
SizedBox( BotonFarolero.oscuro(
width: double.infinity, texto: l10n.impostorGuessWord,
height: 56, icono: Icons.psychology,
child: OutlinedButton.icon( assetIconPath: 'assets/ui/generated/actions/action_impostor_mask.webp',
onPressed: () => _iniciarAdivinanzaOnline(context), onPressed: () => _iniciarAdivinanzaOnline(context),
icon: const Icon(Icons.psychology),
label: Text(l10n.impostorGuessWord),
),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
SizedBox( BotonFarolero(
width: double.infinity, texto: l10n.nextRound,
height: 56, icono: Icons.skip_next,
child: ElevatedButton.icon(
onPressed: () => _siguienteRondaOnline(context), onPressed: () => _siguienteRondaOnline(context),
icon: const Icon(Icons.skip_next),
label: Text(l10n.nextRound),
),
), ),
], ],
); );
} }
return SizedBox( return BotonFarolero(
width: double.infinity, texto: l10n.nextRound,
height: 56, icono: Icons.skip_next,
child: ElevatedButton.icon(
onPressed: () => _siguienteRondaOnline(context), onPressed: () => _siguienteRondaOnline(context),
icon: const Icon(Icons.skip_next),
label: Text(l10n.nextRound),
),
); );
} }
Widget _buildAccionesAdivinanza(BuildContext context, AppLocalizations l10n) { Widget _buildAccionesAdivinanza(BuildContext context, AppLocalizations l10n) {
return Column( return Column(
children: [ children: [
SizedBox( BotonFarolero(
width: double.infinity, texto: l10n.guess,
height: 56, icono: Icons.check_circle,
child: ElevatedButton.icon( assetIconPath: 'assets/ui/generated/actions/action_impostor_mask.webp',
onPressed: () => _resolverAdivinanzaOnline(context), onPressed: () => _resolverAdivinanzaOnline(context),
icon: const Icon(Icons.check_circle),
label: Text(l10n.guess),
),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
SizedBox( BotonFarolero.oscuro(
width: double.infinity, texto: l10n.dontGuess,
height: 56, icono: Icons.skip_next,
child: OutlinedButton.icon(
onPressed: () => _siguienteRondaOnline(context), onPressed: () => _siguienteRondaOnline(context),
icon: const Icon(Icons.skip_next),
label: Text(l10n.dontGuess),
),
), ),
], ],
); );
@@ -1413,7 +1093,7 @@ class _PantallaRevelarPalabraHostState
child: _manteniendo child: _manteniendo
? Column( ? Column(
children: [ children: [
Icon( IconoFarolero(
widget.esImpostor widget.esImpostor
? Icons.theater_comedy ? Icons.theater_comedy
: Icons.key, : Icons.key,
@@ -1441,7 +1121,12 @@ class _PantallaRevelarPalabraHostState
if (widget.esImpostor && widget.pistaActiva) ...[ if (widget.esImpostor && widget.pistaActiva) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
'Categoría: ${widget.categoria}', l10n.clueCategory(
BancoPalabras.nombreBonitoCategoria(
widget.categoria,
l10n,
),
),
style: Theme.of(context).textTheme.bodyLarge style: Theme.of(context).textTheme.bodyLarge
?.copyWith(color: TemaApp.colorNaranja), ?.copyWith(color: TemaApp.colorNaranja),
), ),
@@ -1450,7 +1135,11 @@ class _PantallaRevelarPalabraHostState
) )
: Column( : Column(
children: [ children: [
const Text('🔒', style: TextStyle(fontSize: 48)), IconoFarolero(
Icons.lock,
color: TemaApp.colorDorado,
size: 48,
),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
l10n.holdToSeeWord, l10n.holdToSeeWord,
@@ -1496,21 +1185,15 @@ class _PantallaRevelarPalabraHostState
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
SizedBox( BotonFarolero(
width: double.infinity, texto: _haRevelado ? l10n.iveSeenIt : l10n.tapToSee,
height: 56, icono: Icons.check,
child: ElevatedButton.icon(
onPressed: _haRevelado onPressed: _haRevelado
? () { ? () {
widget.onVisto(); widget.onVisto();
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
: null, : null,
icon: const Icon(Icons.check),
label: Text(
_haRevelado ? l10n.iveSeenIt : l10n.tapToSee,
),
),
), ),
], ],
), ),
+2 -1
View File
@@ -30,6 +30,7 @@ class PantallaHistorial extends StatelessWidget {
const SizedBox(height: 12), const SizedBox(height: 12),
EstadoVacioFarolero( EstadoVacioFarolero(
icono: Icons.history_rounded, icono: Icons.history_rounded,
assetIconPath: 'assets/ui/generated/actions/action_history_ledger.webp',
titulo: l10n.history, titulo: l10n.history,
subtitulo: l10n.noSavedGames, subtitulo: l10n.noSavedGames,
), ),
@@ -68,7 +69,7 @@ class PantallaHistorial extends StatelessWidget {
color: color.withValues(alpha: 0.18), color: color.withValues(alpha: 0.18),
border: Border.all(color: color), border: Border.all(color: color),
), ),
child: Icon( child: IconoFarolero(
ganaronJugadores ganaronJugadores
? Icons.groups_rounded ? Icons.groups_rounded
: Icons.theater_comedy_rounded, : Icons.theater_comedy_rounded,

Some files were not shown because too many files have changed in this diff Show More