From 4599678e77692fa77aba61e9112137cee212233b Mon Sep 17 00:00:00 2001 From: freetlab Date: Mon, 11 May 2026 23:16:38 +0200 Subject: [PATCH] =?UTF-8?q?refactorizaci=C3=B3n=20de=20pantallas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/l10n/app_ar.arb | 16 + lib/l10n/app_ca.arb | 16 + lib/l10n/app_de.arb | 16 + lib/l10n/app_en.arb | 16 + lib/l10n/app_es.arb | 16 + lib/l10n/app_eu.arb | 16 + lib/l10n/app_fr.arb | 16 + lib/l10n/app_hi.arb | 16 + lib/l10n/app_it.arb | 16 + lib/l10n/app_ja.arb | 16 + lib/l10n/app_ko.arb | 16 + lib/l10n/app_nl.arb | 16 + lib/l10n/app_pl.arb | 16 + lib/l10n/app_pt.arb | 16 + lib/l10n/app_ru.arb | 16 + lib/l10n/app_tr.arb | 16 + lib/l10n/app_zh.arb | 16 + lib/l10n/app_zh_TW.arb | 16 + lib/l10n/generated/app_localizations.dart | 13 + lib/l10n/generated/app_localizations_ar.dart | 10 + lib/l10n/generated/app_localizations_ca.dart | 10 + lib/l10n/generated/app_localizations_de.dart | 10 + lib/l10n/generated/app_localizations_en.dart | 10 + lib/l10n/generated/app_localizations_es.dart | 10 + lib/l10n/generated/app_localizations_eu.dart | 10 + lib/l10n/generated/app_localizations_fr.dart | 10 + lib/l10n/generated/app_localizations_hi.dart | 10 + lib/l10n/generated/app_localizations_it.dart | 10 + lib/l10n/generated/app_localizations_ja.dart | 10 + lib/l10n/generated/app_localizations_ko.dart | 10 + lib/l10n/generated/app_localizations_nl.dart | 10 + lib/l10n/generated/app_localizations_pl.dart | 10 + lib/l10n/generated/app_localizations_pt.dart | 10 + lib/l10n/generated/app_localizations_ru.dart | 10 + lib/l10n/generated/app_localizations_tr.dart | 10 + lib/l10n/generated/app_localizations_zh.dart | 10 + lib/pantallas/pantalla_adivinanza.dart | 326 ++++---- lib/pantallas/pantalla_debate.dart | 225 ++---- lib/pantallas/pantalla_debate_cliente.dart | 183 ++--- lib/pantallas/pantalla_gestor_host.dart | 762 ++++++------------- lib/pantallas/pantalla_palabra_cliente.dart | 22 +- lib/pantallas/pantalla_palabras_cliente.dart | 18 +- lib/pantallas/pantalla_resultado_online.dart | 4 +- lib/pantallas/pantalla_revision_palabra.dart | 22 +- lib/pantallas/pantalla_ver_palabra.dart | 355 ++++----- lib/pantallas/pantalla_votacion.dart | 214 ++---- lib/pantallas/pantalla_votacion_cliente.dart | 60 +- lib/tema/componentes_farolero.dart | 247 ++++++ 48 files changed, 1446 insertions(+), 1463 deletions(-) diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 15b93d2..4f095a6 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "تصويت {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "يبدأ {name} بقول كلمته.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb index de075e8..2c3e527 100644 --- a/lib/l10n/app_ca.arb +++ b/lib/l10n/app_ca.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "Vot de {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "Comença {name} dient la seva paraula.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 70315a5..98badac 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "Stimme von {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} beginnt und sagt sein/ihr Wort.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dd9c55c..5aa450f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -333,5 +333,21 @@ "type": "String" } } + }, + "voteOf": "Vote from {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} starts by saying their word.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f1bd582..69f32ed 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -369,5 +369,21 @@ "type": "String" } } + }, + "voteOf": "Voto de {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "Empieza {name} diciendo su palabra.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_eu.arb b/lib/l10n/app_eu.arb index 35512e3..ad41846 100644 --- a/lib/l10n/app_eu.arb +++ b/lib/l10n/app_eu.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "{name}(r)en botoa", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} hasiko da bere hitza esanez.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 15ea069..1f79fcf 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "Vote de {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} commence en disant son mot.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index 304aa53..02dc9ea 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "{name} का वोट", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} अपनी शब्द बोलकर शुरू करता है।", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 8c8e540..dd39ab1 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "Voto di {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} inizia dicendo la sua parola.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 0b35c17..eb8b95b 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "{name} の投票", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} が自分のワードを言って始めます。", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index cf971c3..e9adf04 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "{name}의 투표", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name}님이 자신의 단어를 말하며 시작합니다.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 220485d..23f8d45 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "Stem van {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} begint door het woord te zeggen.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 81117cf..16a69ce 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -301,5 +301,21 @@ "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" + } + } } } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index cbeb295..d055b3f 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "Voto de {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} começa dizendo a sua palavra.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index b14a281..eb29f73 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "Голос игрока {name}", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} начинает, называя своё слово.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 1b849f5..0f492b1 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "{name} için oy", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} kelimesini söyleyerek başlar.", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f82f2c8..5dad3cd 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "{name} 的投票", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} 先说出自己的词。", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/app_zh_TW.arb b/lib/l10n/app_zh_TW.arb index 3c283ee..37585b8 100644 --- a/lib/l10n/app_zh_TW.arb +++ b/lib/l10n/app_zh_TW.arb @@ -301,5 +301,21 @@ "type": "String" } } + }, + "voteOf": "{name} 的投票", + "@voteOf": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "firstTurnInstruction": "{name} 先說出自己的詞。", + "@firstTurnInstruction": { + "placeholders": { + "name": { + "type": "String" + } + } } } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 23116f8..10ed437 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -520,6 +520,19 @@ abstract class AppLocalizations { /// **'Jugadores en debate'** 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. /// /// In es, this message translates to: diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index c0f6805..c2dfcdd 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -213,6 +213,16 @@ class AppLocalizationsAr extends AppLocalizations { @override String get playersInDebate => 'اللاعبون في النقاش'; + @override + String voteOf(String name) { + return "تصويت $name"; + } + + @override + String firstTurnInstruction(String name) { + return "يبدأ $name بقول كلمته."; + } + @override String activePlayersInfo(int active, int impostors) { return '$active نشطون • $impostors منتحل(ون) مختبئون'; diff --git a/lib/l10n/generated/app_localizations_ca.dart b/lib/l10n/generated/app_localizations_ca.dart index 9f0456e..ad1465c 100644 --- a/lib/l10n/generated/app_localizations_ca.dart +++ b/lib/l10n/generated/app_localizations_ca.dart @@ -214,6 +214,16 @@ class AppLocalizationsCa extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active actius • $impostors impostor(s) ocults'; diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 9560b5a..75fb2e9 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -216,6 +216,16 @@ class AppLocalizationsDe extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active aktiv • $impostors versteckte(r) Hochstapler'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 4837d1d..1fa7582 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -213,6 +213,16 @@ class AppLocalizationsEn extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active active • $impostors hidden impostor(s)'; diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index b2e78ef..460a9ea 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -213,6 +213,16 @@ class AppLocalizationsEs extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active activos • $impostors impostor(es) ocultos'; diff --git a/lib/l10n/generated/app_localizations_eu.dart b/lib/l10n/generated/app_localizations_eu.dart index e0b9504..d1d4d42 100644 --- a/lib/l10n/generated/app_localizations_eu.dart +++ b/lib/l10n/generated/app_localizations_eu.dart @@ -216,6 +216,16 @@ class AppLocalizationsEu extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active aktibo • $impostors inpostore ezkutu'; diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index 7e50200..5c17987 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -214,6 +214,16 @@ class AppLocalizationsFr extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active actifs • $impostors imposteur(s) caché(s)'; diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index 069ae4a..cc0f77d 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -213,6 +213,16 @@ class AppLocalizationsHi extends AppLocalizations { @override String get playersInDebate => 'बहस में खिलाड़ी'; + @override + String voteOf(String name) { + return "$name का वोट"; + } + + @override + String firstTurnInstruction(String name) { + return "$name अपनी शब्द बोलकर शुरू करता है।"; + } + @override String activePlayersInfo(int active, int impostors) { return '$active सक्रिय • $impostors धोखेबाज़ छिपे हुए'; diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 4bb0ad7..d5bf551 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -214,6 +214,16 @@ class AppLocalizationsIt extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active attivi • $impostors impostore/i nascosti'; diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index d8933cd..fcc1cce 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -213,6 +213,16 @@ class AppLocalizationsJa extends AppLocalizations { @override String get playersInDebate => '議論中のプレイヤー'; + @override + String voteOf(String name) { + return "$name の投票"; + } + + @override + String firstTurnInstruction(String name) { + return "$name が自分のワードを言って始めます。"; + } + @override String activePlayersInfo(int active, int impostors) { return '$active 人参加中 • $impostors 人のインポスターが潜伏中'; diff --git a/lib/l10n/generated/app_localizations_ko.dart b/lib/l10n/generated/app_localizations_ko.dart index 5789d7e..e9f4edb 100644 --- a/lib/l10n/generated/app_localizations_ko.dart +++ b/lib/l10n/generated/app_localizations_ko.dart @@ -213,6 +213,16 @@ class AppLocalizationsKo extends AppLocalizations { @override String get playersInDebate => '토론 중인 플레이어'; + @override + String voteOf(String name) { + return "$name의 투표"; + } + + @override + String firstTurnInstruction(String name) { + return "$name님이 자신의 단어를 말하며 시작합니다."; + } + @override String activePlayersInfo(int active, int impostors) { return '$active명 참여 중 • $impostors명의 임포스터 잠복 중'; diff --git a/lib/l10n/generated/app_localizations_nl.dart b/lib/l10n/generated/app_localizations_nl.dart index 1d2d1fe..ce7a42f 100644 --- a/lib/l10n/generated/app_localizations_nl.dart +++ b/lib/l10n/generated/app_localizations_nl.dart @@ -214,6 +214,16 @@ class AppLocalizationsNl extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active actief • $impostors verborgen bedrieger(s)'; diff --git a/lib/l10n/generated/app_localizations_pl.dart b/lib/l10n/generated/app_localizations_pl.dart index 81e48aa..fbd3294 100644 --- a/lib/l10n/generated/app_localizations_pl.dart +++ b/lib/l10n/generated/app_localizations_pl.dart @@ -214,6 +214,16 @@ class AppLocalizationsPl extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active aktywnych • $impostors ukrytych oszustów'; diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index f7736f4..8140de7 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -215,6 +215,16 @@ class AppLocalizationsPt extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active ativos • $impostors impostor(es) ocultos'; diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index 0fedc1a..0d65f1e 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -214,6 +214,16 @@ class AppLocalizationsRu extends AppLocalizations { @override String get playersInDebate => 'Игроки в обсуждении'; + @override + String voteOf(String name) { + return "Голос игрока $name"; + } + + @override + String firstTurnInstruction(String name) { + return "$name начинает, называя своё слово."; + } + @override String activePlayersInfo(int active, int impostors) { return '$active активных • $impostors скрытый(-х) самозванец(-ев)'; diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index 5b445ce..6b0c09e 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -213,6 +213,16 @@ class AppLocalizationsTr extends AppLocalizations { @override 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 String activePlayersInfo(int active, int impostors) { return '$active aktif • $impostors gizli sahtekar'; diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index 9493b6a..bb69e43 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -213,6 +213,16 @@ class AppLocalizationsZh extends AppLocalizations { @override String get playersInDebate => '参与讨论的玩家'; + @override + String voteOf(String name) { + return "$name 的投票"; + } + + @override + String firstTurnInstruction(String name) { + return "$name 先说出自己的词。"; + } + @override String activePlayersInfo(int active, int impostors) { return '$active 名在场 • $impostors 名冒牌者潜伏中'; diff --git a/lib/pantallas/pantalla_adivinanza.dart b/lib/pantallas/pantalla_adivinanza.dart index 07dae38..a44a962 100644 --- a/lib/pantallas/pantalla_adivinanza.dart +++ b/lib/pantallas/pantalla_adivinanza.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; @@ -30,6 +30,22 @@ class _PantallaAdivinanzaState extends State { 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 Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; @@ -44,199 +60,129 @@ class _PantallaAdivinanzaState extends State { ), body: FondoFarolero( intenso: true, - child: Center( - child: SingleChildScrollView( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const ArteGameplayFarolero.fase(height: 132), - const SizedBox(height: 12), - EncabezadoFarolero( - icono: Icons.theater_comedy, - titulo: l10n.impostorCanGuess, - subtitulo: l10n.ifCorrectImpostorsWin, - color: TemaApp.colorAcento, - trailing: Image.asset( - 'assets/ui/generated/meta/result_verdict_art.webp', - width: 42, - height: 42, - opacity: const AlwaysStoppedAnimation(0.64), - ), + child: SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const ArteGameplayFarolero.fase(height: 132), + const SizedBox(height: 12), + TarjetaFaseFarolero( + icono: Icons.theater_comedy, + titulo: l10n.impostorCanGuess, + subtitulo: l10n.ifCorrectImpostorsWin, + color: TemaApp.colorAcento, + child: _buildContenido(context, l10n, estado, partida.palabraSecreta), + ), + ], ), - const SizedBox(height: 32), - - if (_acierto == null) ...[ - TextField( - controller: _controlador, - decoration: InputDecoration( - hintText: l10n.guessWordHint, - prefixIcon: const Icon(Icons.search), - ), - textCapitalization: TextCapitalization.sentences, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 20), - onSubmitted: (_) => _intentarAdivinar(), - ), - const SizedBox(height: 24), - Row( - children: [ - Expanded( - child: OutlinedButton( - onPressed: () { - // No intenta adivinar, siguiente ronda - final fin = estado.comprobarFinPartida(); - 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), - Expanded( - flex: 2, - child: ElevatedButton.icon( - onPressed: _controlador.text.trim().isNotEmpty - ? _intentarAdivinar - : null, - icon: const Icon(Icons.send), - label: Text(l10n.guess), - ), - ), - ], - ), - ], - - if (_acierto == true) ...[ - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: TemaApp.colorAcento.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: TemaApp.colorAcento), - ), - child: Column( - children: [ - const Text('🎭🎉', style: TextStyle(fontSize: 48)), - const SizedBox(height: 12), - Text( - l10n.correctGuess, - style: Theme.of(context) - .textTheme - .headlineMedium - ?.copyWith(color: TemaApp.colorAcento), - ), - const SizedBox(height: 8), - Text( - l10n.theWordWas(partida.palabraSecreta), - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 8), - Text( - l10n.impostorsWin, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: TemaApp.colorNaranja, - ), - ), - ], - ), - ), - const SizedBox(height: 24), - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: () { - Navigator.pushReplacement( - context, - MaterialPageRoute( - 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(), - ), - ); - } - }, - icon: const Icon(Icons.skip_next), - label: Text(l10n.nextRound), - ), - ), - ], - ], - ), + ), ), ), ), ); } + + Widget _buildContenido( + BuildContext context, + AppLocalizations l10n, + EstadoJuego estado, + String palabraSecreta, + ) { + if (_acierto == null) { + return Column( + children: [ + TextField( + controller: _controlador, + decoration: InputDecoration( + hintText: l10n.guessWordHint, + prefixIcon: const Icon(Icons.search), + ), + textCapitalization: TextCapitalization.sentences, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 20), + onChanged: (value) => setState(() {}), + onSubmitted: (value) => _intentarAdivinar(), + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: BotonFarolero.oscuro( + texto: l10n.dontGuess, + icono: Icons.skip_next, + onPressed: () => _continuarTrasNoAdivinar(estado), + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: BotonFarolero( + texto: l10n.guess, + icono: Icons.send, + onPressed: _controlador.text.trim().isNotEmpty ? _intentarAdivinar : null, + ), + ), + ], + ), + ], + ); + } + + final acierto = _acierto == true; + final color = acierto ? TemaApp.colorAcento : TemaApp.colorVerde; + return Column( + children: [ + PanelFarolero( + padding: const EdgeInsets.all(24), + borderColor: color, + color: color.withValues(alpha: 0.18), + child: Column( + children: [ + Icon( + acierto ? Icons.celebration : Icons.cancel, + color: color, + size: 52, + ), + const SizedBox(height: 12), + Text( + acierto ? l10n.correctGuess : l10n.wrongGuess, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium?.copyWith(color: color), + ), + const SizedBox(height: 8), + Text( + acierto ? l10n.theWordWas(palabraSecreta) : l10n.gameContinues, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + if (acierto) ...[ + const SizedBox(height: 8), + Text( + l10n.impostorsWin, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: TemaApp.colorNaranja), + ), + ], + ], + ), + ), + const SizedBox(height: 24), + BotonFarolero( + texto: acierto ? l10n.seeEndResult : l10n.nextRound, + icono: acierto ? Icons.emoji_events : Icons.skip_next, + onPressed: acierto + ? () { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const PantallaFinPartida()), + ); + } + : () => _continuarTrasNoAdivinar(estado), + ), + ], + ); + } } diff --git a/lib/pantallas/pantalla_debate.dart b/lib/pantallas/pantalla_debate.dart index dda0ab7..74a5b41 100644 --- a/lib/pantallas/pantalla_debate.dart +++ b/lib/pantallas/pantalla_debate.dart @@ -4,7 +4,6 @@ import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; import '../tema/componentes_farolero.dart'; -import '../tema/tema_app.dart'; import 'pantalla_notas.dart'; import 'pantalla_votacion.dart'; @@ -67,9 +66,6 @@ class _PantallaDebateState extends State { if (partida == null) return const SizedBox.shrink(); final tieneTemporizador = partida.config.tiempoDebateSegundos != null; - final progreso = tieneTemporizador - ? _segundosRestantes / partida.config.tiempoDebateSegundos! - : 0.0; return Scaffold( appBar: AppBar( @@ -78,172 +74,75 @@ class _PantallaDebateState extends State { ), body: FondoFarolero( intenso: true, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - const ArteGameplayFarolero.fase(height: 110), - const SizedBox(height: 10), - // Temporizador - if (tieneTemporizador) ...[ - Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - 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), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const ArteGameplayFarolero.fase(height: 110), + const SizedBox(height: 10), + if (tieneTemporizador) ...[ + TemporizadorFarolero( + etiqueta: _tiempoAgotado ? l10n.timeUp : l10n.timeRemaining, + tiempo: _formatearTiempo(_segundosRestantes), + agotado: _tiempoAgotado, + ), + const SizedBox(height: 16), + ], + Expanded( + child: TarjetaFaseFarolero( + icono: Icons.forum, + titulo: l10n.playersInDebate, + subtitulo: l10n.activePlayersInfo( + partida.jugadoresActivos.length, + partida.impostoresActivos.length, + ), + child: Expanded( + child: ListView.separated( + itemCount: partida.jugadores.length, + separatorBuilder: (context, index) => const SizedBox(height: 8), + itemBuilder: (context, index) { + final jugador = partida.jugadores[index]; + return EstadoJugadorFarolero( + nombre: '${index + 1}. ${jugador.nombre}', + subtitulo: jugador.eliminado ? l10n.eliminated : null, + icono: jugador.eliminado ? Icons.person_off : Icons.record_voice_over, + destacado: !jugador.eliminado, + completado: !jugador.eliminado, + ); + }, ), ), - 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), + Row( + children: [ + Expanded( + child: BotonFarolero.oscuro( + texto: l10n.notes, + icono: Icons.edit_note, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const PantallaNotas()), + ); + }, + ), + ), + const SizedBox(width: 12), + Expanded( + flex: 2, + child: BotonFarolero( + texto: l10n.goToVoting, + icono: Icons.how_to_vote, + onPressed: _irAVotacion, + ), ), ], ), - ), - const SizedBox(height: 16), - ], - - // Jugadores activos - Expanded( - child: Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.playersInDebate, - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 4), - Text( - 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, - itemBuilder: (context, index) { - final j = partida.jugadores[index]; - return ListTile( - leading: CircleAvatar( - backgroundColor: j.eliminado - ? Colors.grey - : TemaApp.colorAcento, - child: Text( - j.eliminado ? '💀' : '${index + 1}', - 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), - - // Botones - Row( - children: [ - Expanded( - child: OutlinedButton.icon( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const PantallaNotas(), - ), - ); - }, - icon: const Text('📝', style: TextStyle(fontSize: 18)), - label: Text(l10n.notes), - ), - ), - const SizedBox(width: 12), - Expanded( - flex: 2, - child: ElevatedButton.icon( - onPressed: _irAVotacion, - icon: const Text('🗳️', style: TextStyle(fontSize: 18)), - label: Text(l10n.goToVoting), - ), - ), ], ), - ], ), ), ), diff --git a/lib/pantallas/pantalla_debate_cliente.dart b/lib/pantallas/pantalla_debate_cliente.dart index 7b1b121..4679f7b 100644 --- a/lib/pantallas/pantalla_debate_cliente.dart +++ b/lib/pantallas/pantalla_debate_cliente.dart @@ -1,4 +1,4 @@ -import 'dart:async'; +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:farolero/modelos/inicio_partida_multijugador.dart'; @@ -11,8 +11,6 @@ import 'package:farolero/tema/componentes_farolero.dart'; import 'package:farolero/tema/tema_app.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 { final int? tiempoDebateSegundos; final String? primerTurnoNombre; @@ -87,9 +85,7 @@ class _PantallaDebateClienteState extends State { void dispose() { _timer?.cancel(); final listener = _listener; - if (listener != null) { - _nearby?.removeMensajeListener(listener); - } + if (listener != null) _nearby?.removeMensajeListener(listener); super.dispose(); } @@ -160,138 +156,67 @@ class _PantallaDebateClienteState extends State { ), body: FondoFarolero( intenso: true, - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - const ArteGameplayFarolero.fase(height: 124), - const SizedBox(height: 10), - const Spacer(), - - // Timer - if (widget.tiempoDebateSegundos != null) ...[ - Container( - padding: const EdgeInsets.all(32), - decoration: BoxDecoration( - color: _segundosRestantes == 0 - ? TemaApp.colorAcento.withValues(alpha: 0.3) - : TemaApp.colorTarjeta, - borderRadius: BorderRadius.circular(24), - ), - child: 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 + child: SafeArea( + top: false, + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + const ArteGameplayFarolero.fase(height: 124), + const SizedBox(height: 12), + TarjetaFaseFarolero( + icono: Icons.forum, + titulo: l10n.debate, + subtitulo: l10n.debateInstructions, + child: Column( + children: [ + if (widget.tiempoDebateSegundos != null) ...[ + TemporizadorFarolero( + etiqueta: _segundosRestantes == 0 ? l10n.timeUp : l10n.timeRemaining, - style: Theme.of(context).textTheme.titleMedium, + tiempo: _formatearTiempo(_segundosRestantes), + agotado: _segundosRestantes == 0, ), - const SizedBox(height: 8), + const SizedBox(height: 16), + ] else ...[ Text( - _formatearTiempo(_segundosRestantes), - style: - Theme.of(context).textTheme.displayMedium?.copyWith( - fontWeight: FontWeight.bold, - color: _segundosRestantes == 0 - ? TemaApp.colorAcento - : TemaApp.colorTexto, - ), + l10n.debatePhaseActive, + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, ), + const SizedBox(height: 16), ], - ), - ], - ), - ), - const SizedBox(height: 32), - ] else ...[ - Text( - l10n.debatePhaseActive, - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - ], - - // Instrucciones - if (widget.primerTurnoNombre != null) ...[ - Container( - width: double.infinity, - 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), + if (widget.primerTurnoNombre != null) ...[ + EstadoJugadorFarolero( + nombre: l10n.firstTurnInstruction( + widget.primerTurnoNombre!, + ), + destacado: true, + completado: true, + icono: Icons.record_voice_over, + ), + const SizedBox(height: 12), + ], + BotonFarolero.secundario( + texto: _votacionSolicitada + ? l10n.votacionSolicitada + : l10n.solicitarVotacion, + icono: _votacionSolicitada + ? Icons.hourglass_empty + : Icons.how_to_vote, + onPressed: _votacionSolicitada + ? null + : () { + setState(() => _votacionSolicitada = true); + widget.onSolicitarVotacion(); + }, + ), + ], ), ), - child: Row( - children: [ - const Icon( - Icons.record_voice_over, - color: TemaApp.colorNaranja, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - widget.primerTurnoNombre!, - style: Theme.of(context).textTheme.titleMedium, - ), - ), - ], - ), - ), - const SizedBox(height: 16), - ], - - Text( - l10n.debateInstructions, - textAlign: TextAlign.center, - style: TextStyle( - color: TemaApp.colorTextoSecundario, - fontSize: 16, - ), + ], ), - - const Spacer(), - - // Botón solicitar votación - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: _votacionSolicitada - ? null - : () { - setState(() => _votacionSolicitada = true); - widget.onSolicitarVotacion(); - }, - icon: Icon(_votacionSolicitada ? Icons.hourglass_empty : Icons.how_to_vote), - label: Text( - _votacionSolicitada - ? l10n.votacionSolicitada - : l10n.solicitarVotacion, - ), - style: ElevatedButton.styleFrom( - backgroundColor: _votacionSolicitada - ? TemaApp.colorTarjeta - : TemaApp.colorAcento, - foregroundColor: Colors.white, - textStyle: const TextStyle(fontSize: 16), - ), - ), - ), - ], ), ), ), diff --git a/lib/pantallas/pantalla_gestor_host.dart b/lib/pantallas/pantalla_gestor_host.dart index f254bcc..7808f5b 100644 --- a/lib/pantallas/pantalla_gestor_host.dart +++ b/lib/pantallas/pantalla_gestor_host.dart @@ -7,12 +7,14 @@ import '../estado/estado_juego.dart'; import '../modelos/gamificacion_usuario.dart'; import '../modelos/inicio_partida_multijugador.dart'; import '../modelos/jugador.dart'; +import '../modelos/palabra.dart'; import '../modelos/partida.dart'; import '../modelos/snapshot_partida_online.dart'; import '../servicios/servicio_historial_partidas.dart'; import '../servicios/servicio_nearby.dart'; import '../servicios/servicio_perfil_usuario.dart'; import '../tema/componentes_farolero.dart'; +import '../tema/componentes_resultado_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_notas_online.dart'; import 'pantalla_revision_palabra.dart'; @@ -279,10 +281,13 @@ class _PantallaGestorHostState extends State { const SizedBox(height: 8), Align( alignment: Alignment.centerRight, - child: OutlinedButton.icon( - onPressed: () => nearby.asumirUsuariosDesconectados(), - icon: const Icon(Icons.person_add_alt_1), - label: Text(AppLocalizations.of(context)!.assumeOnThisPhone), + child: SizedBox( + width: 260, + child: BotonFarolero.oscuro( + texto: AppLocalizations.of(context)!.assumeOnThisPhone, + icono: Icons.person_add_alt_1, + onPressed: () => nearby.asumirUsuariosDesconectados(), + ), ), ), ], @@ -404,67 +409,35 @@ class _PantallaGestorHostState extends State { bool todosListos, ServicioNearby nearby, ) { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.waitingPlayersSeeWord, - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 16), - Text( - l10n.connectedPlayers, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - _buildJugadorTile(nearby.miNombre ?? 'Host', true, _hostListo), - ...nearby.jugadores.map( - (j) => _buildJugadorTile( - j.nombre, - false, - _clientesListos[j.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), - ), - ), + return TarjetaFaseFarolero( + icono: Icons.visibility, + titulo: l10n.waitingPlayersSeeWord, + subtitulo: l10n.connectedPlayers, + child: Column( + children: [ + _buildJugadorTile(nearby.miNombre ?? 'Host', true, _hostListo), + ...nearby.jugadores.map( + (jugador) => _buildJugadorTile( + jugador.nombre, + false, + _clientesListos[jugador.endpointId] ?? false, ), + ), + const SizedBox(height: 12), + BotonFarolero( + texto: l10n.seeYourWord, + icono: Icons.visibility, + onPressed: () => _mostrarPalabraHost(context), + ), + if (todosListos) ...[ const SizedBox(height: 12), - if (todosListos) - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: TemaApp.colorVerde.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.check_circle, color: TemaApp.colorVerde), - const SizedBox(width: 8), - Text( - l10n.allSeenStartDebate, - style: const TextStyle(color: TemaApp.colorVerde), - ), - ], - ), - ), + EstadoJugadorFarolero( + nombre: l10n.allSeenStartDebate, + completado: true, + icono: Icons.check_circle, + ), ], - ), + ], ), ); } @@ -563,66 +536,32 @@ class _PantallaGestorHostState extends State { final estado = context.read(); final tiempo = estado.partida?.config.tiempoDebateSegundos; - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (tiempo != null) ...[ - Text(l10n.debate, style: Theme.of(context).textTheme.titleLarge), - const SizedBox(height: 16), - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: _segundosRestantes == 0 - ? TemaApp.colorAcento.withValues(alpha: 0.3) - : TemaApp.colorTarjeta, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - children: [ - Text( - _segundosRestantes == 0 - ? l10n.timeUp - : l10n.timeRemaining, - style: Theme.of(context).textTheme.bodyMedium, - ), - Text( - _formatearTiempo(_segundosRestantes), - style: Theme.of(context).textTheme.headlineLarge, - ), - ], - ), - ), - const SizedBox(height: 16), - ], - _buildPrimerTurno(context), + return TarjetaFaseFarolero( + icono: Icons.forum, + titulo: l10n.debate, + subtitulo: l10n.debateInstructions, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (tiempo != null) ...[ + TemporizadorFarolero( + etiqueta: _segundosRestantes == 0 + ? l10n.timeUp + : l10n.timeRemaining, + tiempo: _formatearTiempo(_segundosRestantes), + agotado: _segundosRestantes == 0, + ), const SizedBox(height: 16), - Text( - l10n.activePlayers, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - Expanded( - child: ListView.builder( - itemCount: nearby.jugadores.length + 1, - itemBuilder: (context, index) { - if (index == 0) { - return _buildJugadorTile( - nearby.miNombre ?? 'Host', - true, - true, - ); - } - final j = nearby.jugadores[index - 1]; - return _buildJugadorTile(j.nombre, false, true); - }, - ), - ), ], - ), + _buildPrimerTurno(context), + const SizedBox(height: 16), + Text(l10n.activePlayers, style: Theme.of(context).textTheme.titleMedium), + const SizedBox(height: 8), + _buildJugadorTile(nearby.miNombre ?? 'Host', true, true), + ...nearby.jugadores.map( + (jugador) => _buildJugadorTile(jugador.nombre, false, true), + ), + ], ), ); } @@ -644,7 +583,7 @@ class _PantallaGestorHostState extends State { const SizedBox(width: 12), Expanded( child: Text( - 'Empieza $nombre diciendo su palabra.', + AppLocalizations.of(context)!.firstTurnInstruction(nombre), style: Theme.of(context).textTheme.titleMedium, ), ), @@ -664,89 +603,42 @@ class _PantallaGestorHostState extends State { final votosEmitidos = estado.votos.length; final progreso = totalVotos == 0 ? 0.0 : votosEmitidos / totalVotos; - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(l10n.voting, style: Theme.of(context).textTheme.titleLarge), - const SizedBox(height: 16), - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: TemaApp.colorTarjeta, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - children: [ - Text(l10n.votesProgress(votosEmitidos, totalVotos)), - const SizedBox(height: 8), - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: LinearProgressIndicator( - value: progreso.clamp(0.0, 1.0).toDouble(), - backgroundColor: TemaApp.colorSuperficie, - valueColor: const AlwaysStoppedAnimation( - TemaApp.colorAcento, - ), - minHeight: 8, - ), - ), - ], - ), + return TarjetaFaseFarolero( + icono: Icons.how_to_vote, + titulo: l10n.voting, + subtitulo: l10n.votesProgress(votosEmitidos, totalVotos), + color: TemaApp.colorAcento, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(999), + child: LinearProgressIndicator( + value: progreso.clamp(0.0, 1.0).toDouble(), + minHeight: 14, + backgroundColor: Colors.black.withValues(alpha: 0.35), + valueColor: const AlwaysStoppedAnimation(TemaApp.colorAcento), ), - 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), + if (!_hostYaVoto(context)) + BotonFarolero.secundario( + texto: l10n.votar, + icono: Icons.how_to_vote, + onPressed: () => _abrirVotacionHost(context), ), - const SizedBox(height: 16), - Text( - l10n.playersVoted, - style: Theme.of(context).textTheme.titleMedium, + if (!_hostYaVoto(context)) const SizedBox(height: 16), + ...partida.jugadoresActivos.map((jugador) { + final haVotado = estado.votos.containsKey(jugador.id); + return _buildJugadorTile(jugador.nombre, false, haVotado); + }), + if (todosVotaron) + EstadoJugadorFarolero( + nombre: l10n.allVoted, + completado: true, + icono: Icons.check_circle, ), - const SizedBox(height: 8), - Expanded( - child: ListView.builder( - itemCount: partida.jugadoresActivos.length, - itemBuilder: (context, index) { - final jugador = partida.jugadoresActivos[index]; - final haVotado = estado.votos.containsKey(jugador.id); - return _buildJugadorTile(jugador.nombre, false, haVotado); - }, - ), - ), - if (todosVotaron) - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: TemaApp.colorVerde.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.check_circle, color: TemaApp.colorVerde), - const SizedBox(width: 8), - Text( - l10n.allVoted, - style: const TextStyle(color: TemaApp.colorVerde), - ), - ], - ), - ), - ], - ), + ], ), ); } @@ -760,132 +652,11 @@ class _PantallaGestorHostState extends State { return Center(child: Text(l10n.noResult)); } - final conteo = {}; - for (final votadoId in resultado.votos.values) { - conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; - } - final maxVotos = conteo.values.isEmpty - ? 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), - ), - ), - ], + return SingleChildScrollView( + padding: const EdgeInsets.only(bottom: 8), + child: ResultadoRondaFarolero( + resultado: resultado, + jugadores: partida.jugadores, ), ); } @@ -895,102 +666,63 @@ class _PantallaGestorHostState extends State { final ultimo = partida?.historialVotaciones.isNotEmpty == true ? partida!.historialVotaciones.last : null; - return Card( - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - 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 - : '${ultimo.eliminadoNombre}: ${l10n.impostorCanGuess}', - textAlign: TextAlign.center, - ), - ], - ), - ), + return TarjetaFaseFarolero( + icono: Icons.psychology, + titulo: l10n.impostorGuessTitle, + subtitulo: ultimo == null + ? l10n.impostorCanGuess + : '${ultimo.eliminadoNombre}: ${l10n.impostorCanGuess}', + color: TemaApp.colorNaranja, + child: const ArteGameplayFarolero.resultado(height: 132), ); } Widget _buildFaseFinOnline(BuildContext context, AppLocalizations l10n) { final partida = context.watch().partida; - final ganaronJugadores = partida?.ganador == 'jugadores'; - return Card( - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - ganaronJugadores ? '🎉' : '🎭', - style: const TextStyle(fontSize: 64), - ), - const SizedBox(height: 16), - Text( - ganaronJugadores ? l10n.playersWin : l10n.impostorsWin, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 12), - Text( - partida == null ? '' : l10n.theWordWas(partida.palabraSecreta), - textAlign: TextAlign.center, - ), - if (_progresoGamificacion != null) ...[ - const SizedBox(height: 16), - _buildProgresoGamificacion(context, _progresoGamificacion!), - ], - ], - ), - ), - ); - } + if (partida == null) return Center(child: Text(l10n.noResult)); - Widget _buildProgresoGamificacion( - BuildContext context, - ProgresoGamificacionUsuario progreso, - ) { - final nuevas = progreso.nuevasMedallas; - return PanelFarolero( - padding: const EdgeInsets.all(14), - color: TemaApp.colorSuperficie.withValues(alpha: 0.82), + final ganaronJugadores = partida.ganador == 'jugadores'; + 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( - 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(width: 8), - Text('${progreso.despues.fuego}%'), - ], + HeroFinalPartidaFarolero( + encabezado: l10n.gameOver, + titulo: ganaronJugadores ? l10n.playersWin : l10n.impostorsWin, + icono: ganaronJugadores ? Icons.emoji_events : Icons.theater_comedy, + color: color, ), - if (nuevas.isNotEmpty) ...[ - const SizedBox(height: 10), - MedallasCompactasFarolero(ids: nuevas, max: nuevas.length), - ], + const SizedBox(height: 12), + if (_progresoGamificacion == null) + const TarjetaRecompensaCargandoPremium() + else + TarjetaProgresoGamificacionPremium( + progreso: _progresoGamificacion!, + ), + const SizedBox(height: 18), + TarjetaSecretoPremium( + palabra: partida.palabraSecreta, + categoria: BancoPalabras.nombreBonitoCategoria( + partida.categoriaReal, + l10n, + ), + ), + const SizedBox(height: 18), + TarjetaImpostoresPremium( + titulo: impostores.length == 1 + ? l10n.theImpostorWas + : l10n.theImpostorsWere, + impostores: impostores, + ), + const SizedBox(height: 18), + if (partida.historialVotaciones.isNotEmpty) + TarjetaHistorialVotosPremium( + historial: partida.historialVotaciones, + jugadores: partida.jugadores, + ), ], ), ); @@ -1046,28 +778,11 @@ class _PantallaGestorHostState extends State { } Widget _buildJugadorTile(String nombre, bool esHost, bool listo) { - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: listo - ? TemaApp.colorVerde.withValues(alpha: 0.2) - : TemaApp.colorTarjeta, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - 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), - ], - ), + return EstadoJugadorFarolero( + nombre: nombre, + destacado: esHost, + completado: listo, + icono: esHost ? Icons.phone_android : Icons.devices, ); } @@ -1080,60 +795,36 @@ class _PantallaGestorHostState extends State { ) { switch (fase) { case FaseJuego.verPalabra: - return SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: todosListos - ? () => _avanzarAFase(FaseJuego.debate) - : null, - icon: const Icon(Icons.forum), - label: Text( - todosListos - ? l10n.allSeenStartDebate - : l10n.waitingPlayersSeeWord, - ), - ), + return BotonFarolero( + texto: todosListos ? l10n.allSeenStartDebate : l10n.waitingPlayersSeeWord, + icono: Icons.forum, + onPressed: todosListos ? () => _avanzarAFase(FaseJuego.debate) : null, ); case FaseJuego.debate: - return SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: () => _avanzarAFase(FaseJuego.votacion), - icon: const Icon(Icons.how_to_vote), - label: Text(l10n.goToVoting), - ), + return BotonFarolero.secundario( + texto: l10n.goToVoting, + icono: Icons.how_to_vote, + onPressed: () => _avanzarAFase(FaseJuego.votacion), ); case FaseJuego.votacion: - return SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: todosVotaron - ? () => _avanzarAFase(FaseJuego.resultado) - : null, - icon: const Icon(Icons.visibility), - label: Text(todosVotaron ? l10n.revealResult : l10n.waitingVoting), - ), + return BotonFarolero( + texto: todosVotaron ? l10n.revealResult : l10n.waitingVoting, + icono: Icons.visibility, + onPressed: todosVotaron ? () => _avanzarAFase(FaseJuego.resultado) : null, ); case FaseJuego.resultado: return _buildAccionesResultado(context, l10n); case FaseJuego.adivinanza: return _buildAccionesAdivinanza(context, l10n); case FaseJuego.finPartida: - return SizedBox( - width: double.infinity, - height: 56, - child: OutlinedButton.icon( - onPressed: () async { - final nearby = context.read(); - await nearby.desconectar(); - widget.onPartidaFin(); - }, - icon: const Icon(Icons.home), - label: Text(l10n.mainMenu), - ), + return BotonFarolero.oscuro( + texto: l10n.mainMenu, + icono: Icons.home, + onPressed: () async { + final nearby = context.read(); + await nearby.desconectar(); + widget.onPartidaFin(); + }, ); default: return const SizedBox.shrink(); @@ -1149,80 +840,58 @@ class _PantallaGestorHostState extends State { if (partida == null || resultado == null) return const SizedBox.shrink(); if (_hayFinTrasVotacion(partida)) { - return SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: () => _finalizarPartidaOnline(context), - icon: const Icon(Icons.emoji_events), - label: Text(l10n.seeEndResult), - ), + return BotonFarolero( + texto: l10n.seeEndResult, + icono: Icons.emoji_events, + onPressed: () => _finalizarPartidaOnline(context), ); } if (resultado.eraImpostor) { return Column( children: [ - SizedBox( - width: double.infinity, - height: 56, - child: OutlinedButton.icon( - onPressed: () => _iniciarAdivinanzaOnline(context), - icon: const Icon(Icons.psychology), - label: Text(l10n.impostorGuessWord), - ), + BotonFarolero.oscuro( + texto: l10n.impostorGuessWord, + icono: Icons.psychology, + onPressed: () => _iniciarAdivinanzaOnline(context), ), const SizedBox(height: 12), - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: () => _siguienteRondaOnline(context), - icon: const Icon(Icons.skip_next), - label: Text(l10n.nextRound), - ), + BotonFarolero( + texto: l10n.nextRound, + icono: Icons.skip_next, + onPressed: () => _siguienteRondaOnline(context), ), ], ); } - return SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: () => _siguienteRondaOnline(context), - icon: const Icon(Icons.skip_next), - label: Text(l10n.nextRound), - ), + return BotonFarolero( + texto: l10n.nextRound, + icono: Icons.skip_next, + onPressed: () => _siguienteRondaOnline(context), ); } Widget _buildAccionesAdivinanza(BuildContext context, AppLocalizations l10n) { return Column( children: [ - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: () => _resolverAdivinanzaOnline(context), - icon: const Icon(Icons.check_circle), - label: Text(l10n.guess), - ), + BotonFarolero( + texto: l10n.guess, + icono: Icons.check_circle, + onPressed: () => _resolverAdivinanzaOnline(context), ), const SizedBox(height: 12), - SizedBox( - width: double.infinity, - height: 56, - child: OutlinedButton.icon( - onPressed: () => _siguienteRondaOnline(context), - icon: const Icon(Icons.skip_next), - label: Text(l10n.dontGuess), - ), + BotonFarolero.oscuro( + texto: l10n.dontGuess, + icono: Icons.skip_next, + onPressed: () => _siguienteRondaOnline(context), ), ], ); } + Future _finalizarPartidaOnline + Future _finalizarPartidaOnline(BuildContext context) async { final estado = context.read(); final nearby = context.read(); @@ -1441,7 +1110,12 @@ class _PantallaRevelarPalabraHostState if (widget.esImpostor && widget.pistaActiva) ...[ const SizedBox(height: 12), Text( - 'Categoría: ${widget.categoria}', + l10n.clueCategory( + BancoPalabras.nombreBonitoCategoria( + widget.categoria, + l10n, + ), + ), style: Theme.of(context).textTheme.bodyLarge ?.copyWith(color: TemaApp.colorNaranja), ), @@ -1450,7 +1124,11 @@ class _PantallaRevelarPalabraHostState ) : Column( children: [ - const Text('🔒', style: TextStyle(fontSize: 48)), + const Icon( + Icons.lock, + color: TemaApp.colorDorado, + size: 48, + ), const SizedBox(height: 16), Text( l10n.holdToSeeWord, @@ -1496,21 +1174,15 @@ class _PantallaRevelarPalabraHostState ), ), const SizedBox(height: 16), - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: _haRevelado - ? () { - widget.onVisto(); - Navigator.of(context).pop(); - } - : null, - icon: const Icon(Icons.check), - label: Text( - _haRevelado ? l10n.iveSeenIt : l10n.tapToSee, - ), - ), + BotonFarolero( + texto: _haRevelado ? l10n.iveSeenIt : l10n.tapToSee, + icono: Icons.check, + onPressed: _haRevelado + ? () { + widget.onVisto(); + Navigator.of(context).pop(); + } + : null, ), ], ), diff --git a/lib/pantallas/pantalla_palabra_cliente.dart b/lib/pantallas/pantalla_palabra_cliente.dart index 06eba7a..22b5ff9 100644 --- a/lib/pantallas/pantalla_palabra_cliente.dart +++ b/lib/pantallas/pantalla_palabra_cliente.dart @@ -5,7 +5,7 @@ import 'package:farolero/tema/componentes_farolero.dart'; import 'package:farolero/tema/tema_app.dart'; /// Pantalla que ve cada jugador cuando recibe su palabra (modo multidispositivo). -/// El cliente recibe la palabra via ServicioNearby y se navega aquí. +/// El cliente recibe la palabra vía ServicioNearby y se navega aquí. /// NO es la pantalla del host. class PantallaPalabraCliente extends StatefulWidget { final String palabra; @@ -166,23 +166,11 @@ class _PantallaPalabraClienteState extends State { ), const Spacer(), - // Botón confirmar - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: _haRevelado ? widget.onVisto : null, - icon: const Icon(Icons.check), - label: Text( - _haRevelado ? l10n.iveSeenIt : l10n.tapToSee, - ), - style: ElevatedButton.styleFrom( - backgroundColor: TemaApp.colorAcento, - foregroundColor: Colors.white, - textStyle: const TextStyle(fontSize: 16), - ), - ), + BotonFarolero( + texto: _haRevelado ? l10n.iveSeenIt : l10n.tapToSee, + icono: Icons.check, + onPressed: _haRevelado ? widget.onVisto : null, ), ], ), diff --git a/lib/pantallas/pantalla_palabras_cliente.dart b/lib/pantallas/pantalla_palabras_cliente.dart index 8fc937c..16cfae7 100644 --- a/lib/pantallas/pantalla_palabras_cliente.dart +++ b/lib/pantallas/pantalla_palabras_cliente.dart @@ -130,18 +130,12 @@ class _PantallaPalabrasClienteState extends State { style: TextStyle(color: TemaApp.colorTextoSecundario), ), const Spacer(), - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: _actualRevelado ? _continuar : null, - icon: Icon(_esUltimo ? Icons.check : Icons.arrow_forward), - label: Text( - _actualRevelado - ? (_esUltimo ? l10n.iveSeenIt : l10n.next) - : l10n.tapToSee, - ), - ), + BotonFarolero( + texto: _actualRevelado + ? (_esUltimo ? l10n.iveSeenIt : l10n.next) + : l10n.tapToSee, + icono: _esUltimo ? Icons.check : Icons.arrow_forward, + onPressed: _actualRevelado ? _continuar : null, ), ], ), diff --git a/lib/pantallas/pantalla_resultado_online.dart b/lib/pantallas/pantalla_resultado_online.dart index 39ea05a..1824fa0 100644 --- a/lib/pantallas/pantalla_resultado_online.dart +++ b/lib/pantallas/pantalla_resultado_online.dart @@ -223,7 +223,9 @@ class _PantallaResultadoOnlineState extends State { AppLocalizations l10n, ) { return Center( - child: Card( + child: PanelFarolero( + margin: const EdgeInsets.all(16), + borderColor: TemaApp.colorNaranja.withValues(alpha: 0.48), child: Padding( padding: const EdgeInsets.all(24), child: Column( diff --git a/lib/pantallas/pantalla_revision_palabra.dart b/lib/pantallas/pantalla_revision_palabra.dart index 4613cba..51a97f5 100644 --- a/lib/pantallas/pantalla_revision_palabra.dart +++ b/lib/pantallas/pantalla_revision_palabra.dart @@ -29,13 +29,10 @@ Future mostrarRevisionPalabraOnline({ ), const SizedBox(height: 12), ...jugadoresControlados.map( - (jugador) => Card( - color: TemaApp.colorTarjeta, - child: ListTile( - leading: const Icon(Icons.visibility), - title: Text(jugador.nombre), - onTap: () => Navigator.pop(sheetContext, jugador), - ), + (jugador) => EstadoJugadorFarolero( + nombre: jugador.nombre, + icono: Icons.visibility, + onTap: () => Navigator.pop(sheetContext, jugador), ), ), ], @@ -121,13 +118,10 @@ class _DialogoRevisionPalabra extends StatelessWidget { ), ), const SizedBox(height: 20), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.check), - label: Text(l10n.back), - ), + BotonFarolero.secundario( + texto: l10n.back, + icono: Icons.check, + onPressed: () => Navigator.pop(context), ), ], ), diff --git a/lib/pantallas/pantalla_ver_palabra.dart b/lib/pantallas/pantalla_ver_palabra.dart index da53cd2..80f972a 100644 --- a/lib/pantallas/pantalla_ver_palabra.dart +++ b/lib/pantallas/pantalla_ver_palabra.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; @@ -24,8 +24,7 @@ class _PantallaVerPalabraState extends State { final partida = estado.partida; if (partida == null) return const SizedBox.shrink(); - final todosHanVisto = - partida.jugadores.every((j) => _hanVisto.contains(j.id)); + final todosHanVisto = partida.jugadores.every((j) => _hanVisto.contains(j.id)); return Scaffold( appBar: AppBar( @@ -34,79 +33,55 @@ class _PantallaVerPalabraState extends State { ), body: FondoFarolero( intenso: true, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - const ArteGameplayFarolero.fase(height: 110), - const SizedBox(height: 10), - EncabezadoFarolero( - icono: Icons.visibility, - titulo: l10n.roundNumber(partida.rondaActual), - subtitulo: l10n.eachPlayerMustSee, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const ArteGameplayFarolero.fase(height: 110), + const SizedBox(height: 10), + TarjetaFaseFarolero( + icono: Icons.visibility, + titulo: l10n.roundNumber(partida.rondaActual), + subtitulo: l10n.eachPlayerMustSee, + ), + const SizedBox(height: 16), + Expanded( + child: ListView.separated( + itemCount: partida.jugadores.length, + separatorBuilder: (context, index) => const SizedBox(height: 10), + itemBuilder: (context, index) { + final jugador = partida.jugadores[index]; + final haVisto = _hanVisto.contains(jugador.id); + return EstadoJugadorFarolero( + nombre: '${index + 1}. ${jugador.nombre}', + subtitulo: haVisto ? l10n.alreadySeen : l10n.tapToSee, + icono: haVisto ? Icons.check_circle : Icons.visibility, + destacado: haVisto, + completado: haVisto, + onTap: haVisto ? null : () => _mostrarPalabra(context, jugador.id), + ); + }, + ), + ), + const SizedBox(height: 16), + BotonFarolero( + texto: todosHanVisto + ? l10n.allSeenStartDebate + : l10n.playersRemaining(partida.jugadores.length - _hanVisto.length), + icono: Icons.forum, + onPressed: todosHanVisto + ? () { + estado.iniciarDebate(); + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => const PantallaDebate()), + ); + } + : null, + ), + ], ), - const SizedBox(height: 16), - Expanded( - child: ListView.builder( - itemCount: partida.jugadores.length, - itemBuilder: (context, index) { - final jugador = partida.jugadores[index]; - final haVisto = _hanVisto.contains(jugador.id); - return Card( - color: haVisto - ? TemaApp.colorVerde.withValues(alpha: 0.2) - : TemaApp.colorTarjeta, - child: ListTile( - leading: CircleAvatar( - backgroundColor: haVisto - ? TemaApp.colorVerde - : TemaApp.colorAcento, - child: Text( - haVisto ? '✓' : '${index + 1}', - style: - const TextStyle(color: Colors.white), - ), - ), - title: Text(jugador.nombre), - subtitle: Text( - haVisto ? l10n.alreadySeen : l10n.tapToSee, - ), - trailing: haVisto - ? const Icon(Icons.check_circle, - color: TemaApp.colorVerde) - : const Icon(Icons.visibility, - color: TemaApp.colorTextoSecundario), - onTap: haVisto - ? null - : () => _mostrarPalabra(context, jugador.id), - ), - ); - }, - ), - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: todosHanVisto - ? () { - estado.iniciarDebate(); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (_) => const PantallaDebate(), - ), - ); - } - : null, - icon: const Icon(Icons.forum), - label: Text(todosHanVisto - ? l10n.allSeenStartDebate - : l10n.playersRemaining(partida.jugadores.length - _hanVisto.length)), - ), - ), - ], ), ), ), @@ -154,8 +129,7 @@ class _PantallaRevelarPalabra extends StatefulWidget { }); @override - State<_PantallaRevelarPalabra> createState() => - _PantallaRevelarPalabraState(); + State<_PantallaRevelarPalabra> createState() => _PantallaRevelarPalabraState(); } class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> { @@ -165,159 +139,128 @@ class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> { @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; + final colorEstado = widget.esImpostor ? TemaApp.colorAcento : TemaApp.colorVerde; return Scaffold( appBar: AppBar(title: Text(widget.nombre)), body: FondoFarolero( intenso: true, - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), + child: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const ArteGameplayFarolero.fase(height: 128), - const SizedBox(height: 12), - Text( - widget.nombre, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 32), - - // Zona de revelación - AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: double.infinity, - padding: const EdgeInsets.all(32), - decoration: BoxDecoration( - color: _manteniendo - ? (widget.esImpostor - ? TemaApp.colorAcento.withValues(alpha: 0.3) - : TemaApp.colorVerde.withValues(alpha: 0.3)) - : TemaApp.colorTarjeta, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: _manteniendo - ? (widget.esImpostor - ? TemaApp.colorAcento - : TemaApp.colorVerde) - : Colors.transparent, - width: 2, - ), + children: [ + const ArteGameplayFarolero.fase(height: 128), + const SizedBox(height: 12), + TarjetaFaseFarolero( + icono: widget.esImpostor ? Icons.theater_comedy : Icons.search, + titulo: widget.nombre, + subtitulo: l10n.holdToSeeWord, + color: colorEstado, ), - child: _manteniendo - ? Column( - children: [ - Text( - widget.esImpostor ? '🎭' : '🔍', - style: const TextStyle(fontSize: 48), - ), - const SizedBox(height: 16), - Text( - widget.esImpostor - ? l10n.youAreImpostor - : l10n.yourWordIs, - style: Theme.of(context) - .textTheme - .titleLarge - ?.copyWith( - color: widget.esImpostor - ? TemaApp.colorAcento - : TemaApp.colorVerde, - ), - ), - if (!widget.esImpostor) ...[ - const SizedBox(height: 12), - TarjetaPalabraFarolero(palabra: widget.palabra), - ], - if (widget.esImpostor && widget.pistaActiva) ...[ - const SizedBox(height: 12), + const SizedBox(height: 18), + AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: TemaApp.decoracionPanel( + color: (_manteniendo ? colorEstado : TemaApp.colorTarjeta).withValues(alpha: _manteniendo ? 0.22 : 0.92), + borderColor: _manteniendo ? colorEstado : TemaApp.colorBorde, + radius: 24, + ), + child: _manteniendo + ? Column( + children: [ + Icon( + widget.esImpostor ? Icons.theater_comedy : Icons.search, + color: colorEstado, + size: 52, + ), + const SizedBox(height: 16), Text( - l10n.clueCategory(BancoPalabras.nombreBonitoCategoria(widget.categoria, l10n)), - style: Theme.of(context) - .textTheme - .bodyLarge - ?.copyWith( - color: TemaApp.colorNaranja, - ), + widget.esImpostor ? l10n.youAreImpostor : l10n.yourWordIs, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge?.copyWith(color: colorEstado), + ), + if (!widget.esImpostor) ...[ + const SizedBox(height: 12), + TarjetaPalabraFarolero(palabra: widget.palabra), + ], + if (widget.esImpostor && widget.pistaActiva) ...[ + const SizedBox(height: 12), + Text( + l10n.clueCategory(BancoPalabras.nombreBonitoCategoria(widget.categoria, l10n)), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: TemaApp.colorNaranja), + ), + ], + ], + ) + : Column( + children: [ + const Icon(Icons.lock, color: TemaApp.colorDorado, size: 52), + const SizedBox(height: 16), + Text( + l10n.holdToSeeWord, + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + l10n.makeSureNoOneLooks, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, ), ], - ], - ) - : Column( - children: [ - const Text('🔒', style: TextStyle(fontSize: 48)), - const SizedBox(height: 16), - Text( - l10n.holdToSeeWord, - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - l10n.makeSureNoOneLooks, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], + ), + ), + const SizedBox(height: 24), + GestureDetector( + onLongPressStart: (details) { + setState(() { + _manteniendo = true; + _visto = true; + }); + }, + onLongPressEnd: (details) => setState(() => _manteniendo = false), + child: Container( + width: double.infinity, + height: 64, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: _manteniendo + ? [TemaApp.colorNaranja, TemaApp.colorAcento] + : [TemaApp.colorAcento, TemaApp.colorAcento], ), - ), - const SizedBox(height: 24), - - // Botón mantener pulsado - GestureDetector( - onLongPressStart: (_) { - setState(() { - _manteniendo = true; - _visto = true; - }); - }, - onLongPressEnd: (_) { - setState(() => _manteniendo = false); - }, - child: Container( - width: double.infinity, - height: 64, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: _manteniendo - ? [TemaApp.colorNaranja, TemaApp.colorAcento] - : [TemaApp.colorAcento, TemaApp.colorAcento], + borderRadius: BorderRadius.circular(18), ), - borderRadius: BorderRadius.circular(16), - ), - child: Center( - child: Text( - _manteniendo - ? l10n.showingWord - : l10n.holdToSee, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, + child: Center( + child: Text( + _manteniendo ? l10n.showingWord : l10n.holdToSee, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), ), ), ), - ), - const SizedBox(height: 24), - - if (_visto) - SizedBox( - width: double.infinity, - child: OutlinedButton.icon( + const SizedBox(height: 24), + if (_visto) + BotonFarolero.secundario( + texto: l10n.seenMyWord, + icono: Icons.check, onPressed: () { widget.onVisto(); Navigator.pop(context); }, - icon: const Icon(Icons.check), - label: Text(l10n.seenMyWord), ), - ), - ], + ], + ), ), ), ), - ), ); } } diff --git a/lib/pantallas/pantalla_votacion.dart b/lib/pantallas/pantalla_votacion.dart index af3a40d..9cdaa25 100644 --- a/lib/pantallas/pantalla_votacion.dart +++ b/lib/pantallas/pantalla_votacion.dart @@ -25,12 +25,6 @@ class _PantallaVotacionState extends State { final activos = partida.jugadoresActivos; final todosVotaron = estado.todosHanVotado(); - // Modo un solo móvil - if (!partida.config.modoMultimovil) { - return _construirVotacionUnMovil(context, estado, partida, activos, todosVotaron); - } - - // Modo multimóvil sería similar pero controlado por Nearby return _construirVotacionUnMovil(context, estado, partida, activos, todosVotaron); } @@ -41,10 +35,7 @@ class _PantallaVotacionState extends State { List activos, bool todosVotaron, ) { - // Encontrar el siguiente votante que no haya votado - final sinVotar = activos - .where((j) => !estado.votos.containsKey(j.id)) - .toList(); + final sinVotar = activos.where((j) => !estado.votos.containsKey(j.id)).toList(); if (todosVotaron) { return _construirTodosVotaron(context, estado); @@ -61,119 +52,65 @@ class _PantallaVotacionState extends State { ), body: FondoFarolero( intenso: true, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - const ArteGameplayFarolero.fase(height: 108), - const SizedBox(height: 10), - // Progreso de votos - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: TemaApp.decoracionPanel( - color: TemaApp.colorTarjeta.withValues(alpha: 0.90), - borderColor: TemaApp.colorNaranja.withValues(alpha: 0.38), - ), - child: Column( - children: [ - Text( - l10n.turnToVote, - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 4), - Text( - votanteActual.nombre, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: TemaApp.colorNaranja, - ), - ), - const SizedBox(height: 8), - Text( - l10n.votesProgress(estado.votos.length, activos.length), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 4), - ClipRRect( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const ArteGameplayFarolero.fase(height: 108), + const SizedBox(height: 10), + TarjetaFaseFarolero( + icono: Icons.how_to_vote, + titulo: l10n.voteOf(votanteActual.nombre), + subtitulo: l10n.votesProgress(estado.votos.length, activos.length), + color: TemaApp.colorAcento, + child: ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: estado.votos.length / activos.length, backgroundColor: TemaApp.colorSuperficie, valueColor: const AlwaysStoppedAnimation(TemaApp.colorAcento), - minHeight: 6, + minHeight: 7, ), ), - ], - ), - ), - const SizedBox(height: 16), - - EncabezadoFarolero( - icono: Icons.how_to_vote, - titulo: l10n.whoIsImpostor, - subtitulo: l10n.selectOnePlayer, - color: TemaApp.colorAcento, - trailing: Image.asset( - 'assets/ui/generated/meta/result_verdict_art.webp', - width: 42, - height: 42, - opacity: const AlwaysStoppedAnimation(0.64), - ), - ), - const SizedBox(height: 12), - - // Lista de candidatos - Expanded( - child: ListView.builder( - itemCount: puedenRecibir.length, - itemBuilder: (context, index) { - final candidato = puedenRecibir[index]; - final seleccionado = _seleccionado == candidato.id; - return Card( - color: seleccionado - ? TemaApp.colorAcento.withValues(alpha: 0.3) - : TemaApp.colorTarjeta, - child: ListTile( - leading: CircleAvatar( - backgroundColor: seleccionado - ? TemaApp.colorAcento - : TemaApp.colorSuperficie, - child: Text('${index + 1}', - style: const TextStyle(color: Colors.white)), - ), - title: Text(candidato.nombre), - trailing: seleccionado - ? const Icon(Icons.check_circle, - color: TemaApp.colorAcento) - : const Icon(Icons.radio_button_unchecked), - onTap: () { - setState(() => _seleccionado = candidato.id); + ), + const SizedBox(height: 16), + Expanded( + child: TarjetaFaseFarolero( + icono: Icons.person_search, + titulo: l10n.whoIsImpostor, + subtitulo: l10n.selectOnePlayer, + color: TemaApp.colorNaranja, + child: Expanded( + child: ListView.separated( + itemCount: puedenRecibir.length, + separatorBuilder: (context, index) => const SizedBox(height: 8), + itemBuilder: (context, index) { + final candidato = puedenRecibir[index]; + final seleccionado = _seleccionado == candidato.id; + return SelectorVotoFarolero( + nombre: '${index + 1}. ${candidato.nombre}', + seleccionado: seleccionado, + onTap: () => setState(() => _seleccionado = candidato.id), + ); }, + ), ), - ); - }, - ), + ), + ), + const SizedBox(height: 16), + BotonFarolero( + texto: l10n.confirmVote, + icono: Icons.how_to_vote, + onPressed: _seleccionado != null + ? () { + estado.registrarVoto(votanteActual.id, _seleccionado!); + setState(() => _seleccionado = null); + } + : null, + ), + ], ), - const SizedBox(height: 16), - - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: _seleccionado != null - ? () { - estado.registrarVoto( - votanteActual.id, _seleccionado!); - setState(() { - _seleccionado = null; - }); - } - : null, - icon: const Icon(Icons.how_to_vote), - label: Text(l10n.confirmVote), - ), - ), - ], ), ), ), @@ -190,45 +127,40 @@ class _PantallaVotacionState extends State { ), body: FondoFarolero( intenso: true, - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const ArteGameplayFarolero.fase(height: 132), - const SizedBox(height: 24), - Text( - l10n.allVoted, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 16), - Text( - l10n.tapToReveal, - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 32), - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( + child: SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ArteGameplayFarolero.fase(height: 132), + const SizedBox(height: 24), + TarjetaFaseFarolero( + icono: Icons.check_circle, + titulo: l10n.allVoted, + subtitulo: l10n.tapToReveal, + color: TemaApp.colorVerde, + child: const SizedBox.shrink(), + ), + const SizedBox(height: 28), + BotonFarolero( + texto: l10n.revealResult, + icono: Icons.visibility, onPressed: () { final resultado = estado.procesarVotacion(); if (resultado != null) { Navigator.pushReplacement( context, MaterialPageRoute( - builder: (_) => - PantallaResultado(resultado: resultado), + builder: (_) => PantallaResultado(resultado: resultado), ), ); } }, - icon: const Icon(Icons.visibility), - label: Text(l10n.revealResult), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pantallas/pantalla_votacion_cliente.dart b/lib/pantallas/pantalla_votacion_cliente.dart index e25af96..375d64f 100644 --- a/lib/pantallas/pantalla_votacion_cliente.dart +++ b/lib/pantallas/pantalla_votacion_cliente.dart @@ -183,21 +183,12 @@ class _PantallaVotacionClienteState extends State { ), ), const SizedBox(height: 16), - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: _votacionCompleta - ? () => widget.onVotos(Map.unmodifiable(_votosPorVotante)) - : null, - icon: const Icon(Icons.how_to_vote), - label: Text(l10n.votar), - style: ElevatedButton.styleFrom( - backgroundColor: TemaApp.colorAcento, - foregroundColor: Colors.white, - textStyle: const TextStyle(fontSize: 16), - ), - ), + BotonFarolero.secundario( + texto: l10n.votar, + icono: Icons.how_to_vote, + onPressed: _votacionCompleta + ? () => widget.onVotos(Map.unmodifiable(_votosPorVotante)) + : null, ), ], ), @@ -232,16 +223,15 @@ class _PantallaVotacionClienteState extends State { BuildContext context, JugadorInicioPartida votante, ) { - return Card( - color: TemaApp.colorSuperficie, + return PanelFarolero( margin: const EdgeInsets.only(bottom: 16), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( + padding: const EdgeInsets.all(14), + borderColor: TemaApp.colorAcento.withValues(alpha: 0.48), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Voto de ${votante.nombre}', + AppLocalizations.of(context)!.voteOf(votante.nombre), style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), @@ -258,7 +248,6 @@ class _PantallaVotacionClienteState extends State { ); }), ], - ), ), ); } @@ -269,29 +258,10 @@ class _PantallaVotacionClienteState extends State { required bool selected, required VoidCallback onTap, }) { - return Card( - color: selected - ? TemaApp.colorAcento.withValues(alpha: 0.3) - : TemaApp.colorTarjeta, - margin: const EdgeInsets.only(bottom: 8), - child: ListTile( - leading: CircleAvatar( - backgroundColor: selected - ? TemaApp.colorAcento - : TemaApp.colorAcento.withValues(alpha: 0.3), - child: Text( - '${index + 1}', - style: TextStyle( - color: selected ? Colors.white : TemaApp.colorTexto, - ), - ), - ), - title: Text(jugador.nombre), - trailing: selected - ? const Icon(Icons.check_circle, color: TemaApp.colorAcento) - : null, - onTap: onTap, - ), + return SelectorVotoFarolero( + nombre: '${index + 1}. ${jugador.nombre}', + seleccionado: selected, + onTap: onTap, ); } } diff --git a/lib/tema/componentes_farolero.dart b/lib/tema/componentes_farolero.dart index adaa219..dc72dc7 100644 --- a/lib/tema/componentes_farolero.dart +++ b/lib/tema/componentes_farolero.dart @@ -335,6 +335,253 @@ class BotonFarolero extends StatelessWidget { } } +class TarjetaFaseFarolero extends StatelessWidget { + final IconData icono; + final String titulo; + final String? subtitulo; + final Color color; + final Widget child; + final EdgeInsetsGeometry padding; + + const TarjetaFaseFarolero({ + super.key, + required this.icono, + required this.titulo, + this.subtitulo, + this.color = TemaApp.colorNaranja, + this.child = const SizedBox.shrink(), + this.padding = const EdgeInsets.all(18), + }); + + @override + Widget build(BuildContext context) { + return PanelFarolero( + padding: padding, + borderColor: color.withValues(alpha: 0.48), + color: TemaApp.colorSuperficie.withValues(alpha: 0.84), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 54, + height: 54, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + color.withValues(alpha: 0.42), + Colors.black.withValues(alpha: 0.62), + ], + ), + border: Border.all(color: color.withValues(alpha: 0.72)), + ), + child: Icon(icono, color: color, size: 30), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + titulo, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: TemaApp.colorDorado, + fontWeight: FontWeight.w900, + ), + ), + if (subtitulo != null) ...[ + const SizedBox(height: 3), + Text( + subtitulo!, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: TemaApp.colorTextoSecundario, + ), + ), + ], + ], + ), + ), + ], + ), + const SizedBox(height: 16), + child, + ], + ), + ); + } +} + +class EstadoJugadorFarolero extends StatelessWidget { + final String nombre; + final bool destacado; + final bool completado; + final IconData icono; + final String? subtitulo; + final VoidCallback? onTap; + + const EstadoJugadorFarolero({ + super.key, + required this.nombre, + this.destacado = false, + this.completado = false, + this.icono = Icons.person, + this.subtitulo, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + final color = completado ? TemaApp.colorVerde : TemaApp.colorNaranja; + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(18), + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + decoration: BoxDecoration( + color: completado + ? TemaApp.colorVerde.withValues(alpha: 0.14) + : TemaApp.colorTarjeta.withValues(alpha: 0.84), + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: destacado + ? TemaApp.colorDorado.withValues(alpha: 0.72) + : color.withValues(alpha: completado ? 0.54 : 0.28), + ), + ), + child: Row( + children: [ + Container( + width: 42, + height: 42, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color.withValues(alpha: 0.16), + border: Border.all(color: color.withValues(alpha: 0.72)), + ), + child: Icon(icono, color: color, size: 24), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + nombre, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: destacado ? FontWeight.w900 : FontWeight.w700, + ), + ), + if (subtitulo != null) ...[ + const SizedBox(height: 2), + Text( + subtitulo!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: TemaApp.colorTextoSecundario, + ), + ), + ], + ], + ), + ), + Icon( + completado ? Icons.check_circle : Icons.hourglass_bottom, + color: completado ? TemaApp.colorVerde : TemaApp.colorTextoSecundario, + ), + ], + ), + ), + ), + ), + ); + } +} + +class SelectorVotoFarolero extends StatelessWidget { + final String nombre; + final bool seleccionado; + final VoidCallback onTap; + + const SelectorVotoFarolero({ + super.key, + required this.nombre, + required this.seleccionado, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return EstadoJugadorFarolero( + nombre: nombre, + destacado: seleccionado, + completado: seleccionado, + icono: seleccionado ? Icons.check : Icons.person_search, + onTap: onTap, + ); + } +} + +class TemporizadorFarolero extends StatelessWidget { + final String etiqueta; + final String tiempo; + final bool agotado; + + const TemporizadorFarolero({ + super.key, + required this.etiqueta, + required this.tiempo, + this.agotado = false, + }); + + @override + Widget build(BuildContext context) { + final color = agotado ? TemaApp.colorAcento : TemaApp.colorNaranja; + return PanelFarolero( + padding: const EdgeInsets.all(22), + borderColor: color.withValues(alpha: 0.62), + color: color.withValues(alpha: agotado ? 0.18 : 0.10), + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/ui/generated/gameplay/gameplay_phase_emblem.webp', + height: 132, + fit: BoxFit.contain, + opacity: const AlwaysStoppedAnimation(0.34), + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + etiqueta, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + tiempo, + style: Theme.of(context).textTheme.displaySmall?.copyWith( + color: color, + fontWeight: FontWeight.w900, + ), + ), + ], + ), + ], + ), + ); + } +} + class MarcoBotonFaroleroPainter extends CustomPainter { final LinearGradient gradient; final bool destacado;