v0.2.0: i18n 18 idiomas + pantalla ajustes + bancos multiidioma
Internacionalización completa: - 18 ficheros .arb: es, en, fr, pt, de, it, ru, ja, ko, zh, zh_TW, ar, hi, tr, pl, nl, ca, eu - Todos los strings extraídos de todas las pantallas - Detección automática de idioma del sistema - Selector manual en pantalla de ajustes Pantalla de ajustes nueva: - Selector de idioma con banderas emoji - Vibración ON/OFF - Acerca de (versión, desarrollador) Bancos de palabras multiidioma: - palabras.json (castellano, 1000 palabras) - palabras_en.json (inglés, 1000 palabras) - palabras_fr.json (francés, 1000 palabras) - Fallback a castellano si no hay banco del idioma 13138 líneas Dart, 39 ficheros, 0 issues en flutter analyze
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
@@ -30,13 +31,14 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return const SizedBox.shrink();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('🎯 Adivinanza del impostor'),
|
||||
title: Text(l10n.impostorGuessTitle),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Center(
|
||||
@@ -48,13 +50,13 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
const Text('🎭', style: TextStyle(fontSize: 64)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'El impostor eliminado puede\nintentar adivinar la palabra',
|
||||
l10n.impostorCanGuess,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Si acierta, ¡los impostores ganan!',
|
||||
l10n.ifCorrectImpostorsWin,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: TemaApp.colorNaranja,
|
||||
),
|
||||
@@ -64,9 +66,9 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
if (_acierto == null) ...[
|
||||
TextField(
|
||||
controller: _controlador,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '¿Cuál crees que es la palabra?',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.guessWordHint,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
),
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
textAlign: TextAlign.center,
|
||||
@@ -98,7 +100,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
);
|
||||
}
|
||||
},
|
||||
child: const Text('No intentar'),
|
||||
child: Text(l10n.dontGuess),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -109,7 +111,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
? _intentarAdivinar
|
||||
: null,
|
||||
icon: const Icon(Icons.send),
|
||||
label: const Text('Adivinar'),
|
||||
label: Text(l10n.guess),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -130,7 +132,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
const Text('🎭🎉', style: TextStyle(fontSize: 48)),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'¡Ha acertado!',
|
||||
l10n.correctGuess,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
@@ -138,12 +140,12 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'La palabra era: ${partida.palabraSecreta}',
|
||||
l10n.theWordWas(partida.palabraSecreta),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'¡Los impostores ganan!',
|
||||
l10n.impostorsWin,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: TemaApp.colorNaranja,
|
||||
),
|
||||
@@ -165,7 +167,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.emoji_events),
|
||||
label: const Text('Ver resultado final'),
|
||||
label: Text(l10n.seeEndResult),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -184,7 +186,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
const Text('❌', style: TextStyle(fontSize: 48)),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'¡No ha acertado!',
|
||||
l10n.wrongGuess,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
@@ -192,7 +194,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'La partida continúa...',
|
||||
l10n.gameContinues,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
],
|
||||
@@ -223,7 +225,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.skip_next),
|
||||
label: const Text('Siguiente ronda'),
|
||||
label: Text(l10n.nextRound),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
182
lib/pantallas/pantalla_ajustes.dart
Normal file
182
lib/pantallas/pantalla_ajustes.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../servicios/servicio_idioma.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
|
||||
class PantallaAjustes extends StatefulWidget {
|
||||
const PantallaAjustes({super.key});
|
||||
|
||||
@override
|
||||
State<PantallaAjustes> createState() => _PantallaAjustesState();
|
||||
}
|
||||
|
||||
class _PantallaAjustesState extends State<PantallaAjustes> {
|
||||
double _volumen = 0.7;
|
||||
bool _vibracion = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final servicioIdioma = context.watch<ServicioIdioma>();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.settingsTitle)),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Selector de idioma
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.language,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
// Opción automática (sistema)
|
||||
_opcionIdioma(
|
||||
context,
|
||||
bandera: '🌐',
|
||||
nombre: 'Auto (${_nombreIdiomaDelSistema()})',
|
||||
codigo: 'sistema',
|
||||
seleccionado: servicioIdioma.codigoActual == 'sistema',
|
||||
onTap: () => servicioIdioma.cambiarIdioma('sistema'),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
// Lista de idiomas
|
||||
...ServicioIdioma.idiomasSoportados.entries.map((entrada) {
|
||||
return _opcionIdioma(
|
||||
context,
|
||||
bandera: entrada.value.bandera,
|
||||
nombre: entrada.value.nombre,
|
||||
codigo: entrada.key,
|
||||
seleccionado:
|
||||
servicioIdioma.codigoActual == entrada.key,
|
||||
onTap: () =>
|
||||
servicioIdioma.cambiarIdioma(entrada.key),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Volumen de efectos de sonido
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.soundVolume,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 8),
|
||||
Slider(
|
||||
value: _volumen,
|
||||
onChanged: (v) => setState(() => _volumen = v),
|
||||
activeColor: TemaApp.colorAcento,
|
||||
inactiveColor: TemaApp.colorTarjeta,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Vibración
|
||||
Card(
|
||||
child: SwitchListTile(
|
||||
title: Text(l10n.vibration),
|
||||
value: _vibracion,
|
||||
onChanged: (v) => setState(() => _vibracion = v),
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Acerca de
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(l10n.about,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
_filaInfo(context, l10n.version, '1.0.0'),
|
||||
const SizedBox(height: 8),
|
||||
_filaInfo(context, l10n.developer, 'FreeTTimeLab'),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
showLicensePage(
|
||||
context: context,
|
||||
applicationName: 'El Impostor',
|
||||
applicationVersion: '1.0.0',
|
||||
);
|
||||
},
|
||||
child: Text(l10n.licenses),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _opcionIdioma(
|
||||
BuildContext context, {
|
||||
required String bandera,
|
||||
required String nombre,
|
||||
required String codigo,
|
||||
required bool seleccionado,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return ListTile(
|
||||
leading: Text(bandera, style: const TextStyle(fontSize: 24)),
|
||||
title: Text(nombre),
|
||||
trailing: seleccionado
|
||||
? const Icon(Icons.check_circle, color: TemaApp.colorAcento)
|
||||
: null,
|
||||
onTap: onTap,
|
||||
dense: true,
|
||||
selected: seleccionado,
|
||||
selectedTileColor: TemaApp.colorAcento.withValues(alpha: 0.1),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _filaInfo(BuildContext context, String etiqueta, String valor) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(etiqueta, style: Theme.of(context).textTheme.bodyMedium),
|
||||
Text(valor, style: Theme.of(context).textTheme.bodyLarge),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _nombreIdiomaDelSistema() {
|
||||
final locale = WidgetsBinding.instance.platformDispatcher.locale;
|
||||
final codigo = locale.countryCode != null && locale.countryCode!.isNotEmpty
|
||||
? '${locale.languageCode}_${locale.countryCode}'
|
||||
: locale.languageCode;
|
||||
final info = ServicioIdioma.idiomasSoportados[codigo] ??
|
||||
ServicioIdioma.idiomasSoportados[locale.languageCode];
|
||||
return info?.nombre ?? locale.languageCode;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../modelos/palabra.dart';
|
||||
@@ -23,22 +24,25 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
final _controladorNombre = TextEditingController();
|
||||
|
||||
final _opcionesTiempo = <int?>[null, 60, 120, 180, 300];
|
||||
final _etiquetasTiempo = ['Sin límite', '1 min', '2 min', '3 min', '5 min'];
|
||||
|
||||
int get _maxImpostores => (_jugadores.length / 3).floor().clamp(1, 4);
|
||||
|
||||
List<String> _etiquetasTiempo(AppLocalizations l10n) =>
|
||||
[l10n.noLimit, l10n.oneMin, l10n.twoMin, l10n.threeMin, l10n.fiveMin];
|
||||
|
||||
void _agregarJugador() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final nombre = _controladorNombre.text.trim();
|
||||
if (nombre.isEmpty) return;
|
||||
if (_jugadores.contains(nombre)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Ya existe un jugador con ese nombre')),
|
||||
SnackBar(content: Text(l10n.playerAlreadyExists)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_jugadores.length >= 20) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Máximo 20 jugadores')),
|
||||
SnackBar(content: Text(l10n.maxPlayersReached)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -61,9 +65,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
}
|
||||
|
||||
void _iniciarPartida() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
if (_jugadores.length < 3) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Se necesitan al menos 3 jugadores')),
|
||||
SnackBar(content: Text(l10n.minPlayersRequired)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -94,11 +99,13 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final categorias = ['todas', ...?estado.banco?.nombresCategorias];
|
||||
final etiquetas = _etiquetasTiempo(l10n);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Crear partida')),
|
||||
appBar: AppBar(title: Text(l10n.createGame)),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
@@ -111,20 +118,20 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Modo de juego',
|
||||
Text(l10n.gameMode,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
SegmentedButton<bool>(
|
||||
segments: const [
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: false,
|
||||
label: Text('Un solo móvil'),
|
||||
icon: Icon(Icons.phone_android),
|
||||
label: Text(l10n.singleDevice),
|
||||
icon: const Icon(Icons.phone_android),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: true,
|
||||
label: Text('Multimóvil'),
|
||||
icon: Icon(Icons.devices),
|
||||
label: Text(l10n.multiDevice),
|
||||
icon: const Icon(Icons.devices),
|
||||
),
|
||||
],
|
||||
selected: {_modoMultimovil},
|
||||
@@ -145,7 +152,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Categoría',
|
||||
Text(l10n.category,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
@@ -158,7 +165,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
items: categorias.map((c) {
|
||||
return DropdownMenuItem(
|
||||
value: c,
|
||||
child: Text(BancoPalabras.nombreBonitoCategoria(c)),
|
||||
child: Text(BancoPalabras.nombreBonitoCategoria(c, l10n)),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (v) => setState(() => _categoria = v!),
|
||||
@@ -180,9 +187,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('Jugadores (${_jugadores.length})',
|
||||
Text(l10n.playersCount(_jugadores.length),
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
Text('3-20',
|
||||
Text(l10n.playersRangeHint,
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
),
|
||||
@@ -192,9 +199,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _controladorNombre,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Nombre del jugador',
|
||||
prefixIcon: Icon(Icons.person_add),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.playerNameHint,
|
||||
prefixIcon: const Icon(Icons.person_add),
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
onSubmitted: (_) => _agregarJugador(),
|
||||
@@ -237,7 +244,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Configuración',
|
||||
Text(l10n.configuration,
|
||||
style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
@@ -245,7 +252,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('🎭 Impostores'),
|
||||
Text(l10n.impostors),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
@@ -271,9 +278,8 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
|
||||
// Pista para impostor
|
||||
SwitchListTile(
|
||||
title: const Text('🔍 Pista para impostor'),
|
||||
subtitle: const Text(
|
||||
'El impostor conoce la categoría'),
|
||||
title: Text(l10n.impostorClue),
|
||||
subtitle: Text(l10n.impostorClueDescription),
|
||||
value: _pistaImpostor,
|
||||
onChanged: (v) =>
|
||||
setState(() => _pistaImpostor = v),
|
||||
@@ -284,14 +290,14 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('⏱️ Tiempo de debate'),
|
||||
Text(l10n.debateTime),
|
||||
DropdownButton<int?>(
|
||||
value: _tiempoDebate,
|
||||
items: List.generate(
|
||||
_opcionesTiempo.length,
|
||||
(i) => DropdownMenuItem(
|
||||
value: _opcionesTiempo[i],
|
||||
child: Text(_etiquetasTiempo[i]),
|
||||
child: Text(etiquetas[i]),
|
||||
),
|
||||
),
|
||||
onChanged: (v) =>
|
||||
@@ -312,7 +318,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _jugadores.length >= 3 ? _iniciarPartida : null,
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
label: const Text('Iniciar partida'),
|
||||
label: Text(l10n.startGame),
|
||||
style: ElevatedButton.styleFrom(
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 18,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
@@ -59,6 +60,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return const SizedBox.shrink();
|
||||
@@ -70,7 +72,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Debate - Ronda ${partida.rondaActual}'),
|
||||
title: Text(l10n.debateRound(partida.rondaActual)),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
@@ -94,7 +96,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
_tiempoAgotado ? '⏰ ¡Tiempo agotado!' : '⏱️ Tiempo restante',
|
||||
_tiempoAgotado ? l10n.timeUp : l10n.timeRemaining,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: _tiempoAgotado
|
||||
? TemaApp.colorAcento
|
||||
@@ -141,12 +143,12 @@ class _PantallaDebateState extends State<PantallaDebate> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Jugadores en debate',
|
||||
l10n.playersInDebate,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${partida.jugadoresActivos.length} activos • ${partida.impostoresActivos.length} impostor(es) ocultos',
|
||||
l10n.activePlayersInfo(partida.jugadoresActivos.length, partida.impostoresActivos.length),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -178,7 +180,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
|
||||
),
|
||||
),
|
||||
subtitle: j.eliminado
|
||||
? const Text('Eliminado')
|
||||
? Text(l10n.eliminated)
|
||||
: null,
|
||||
dense: true,
|
||||
);
|
||||
@@ -206,7 +208,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
|
||||
);
|
||||
},
|
||||
icon: const Text('📝', style: TextStyle(fontSize: 18)),
|
||||
label: const Text('Notas'),
|
||||
label: Text(l10n.notes),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -215,7 +217,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: _irAVotacion,
|
||||
icon: const Text('🗳️', style: TextStyle(fontSize: 18)),
|
||||
label: const Text('Ir a votación'),
|
||||
label: Text(l10n.goToVoting),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../modelos/palabra.dart';
|
||||
@@ -11,6 +12,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return const SizedBox.shrink();
|
||||
@@ -21,7 +23,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Fin de partida'),
|
||||
title: Text(l10n.gameOver),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
@@ -56,8 +58,8 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
ganaronJugadores
|
||||
? '¡Los jugadores ganan!'
|
||||
: '¡Los impostores ganan!',
|
||||
? l10n.playersWin
|
||||
: l10n.impostorsWin,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineMedium
|
||||
@@ -79,7 +81,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Text('🔍 La palabra era:',
|
||||
Text(l10n.theSecretWordWas,
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
@@ -94,7 +96,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Categoría: ${BancoPalabras.nombreBonitoCategoria(partida.categoriaReal)}',
|
||||
l10n.categoryLabel(BancoPalabras.nombreBonitoCategoria(partida.categoriaReal, l10n)),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
@@ -110,7 +112,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'🎭 ${impostores.length == 1 ? 'El impostor era:' : 'Los impostores eran:'}',
|
||||
impostores.length == 1 ? l10n.theImpostorWas : l10n.theImpostorsWere,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -119,8 +121,8 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('🎭 ',
|
||||
style: const TextStyle(fontSize: 18)),
|
||||
const Text('🎭 ',
|
||||
style: TextStyle(fontSize: 18)),
|
||||
Text(
|
||||
j.nombre,
|
||||
style: Theme.of(context)
|
||||
@@ -150,7 +152,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('📊 Historial de votaciones',
|
||||
Text(l10n.votingHistory,
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 12),
|
||||
...partida.historialVotaciones
|
||||
@@ -165,7 +167,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ronda $ronda: ${resultado.eliminadoNombre} ${resultado.eraImpostor ? '🎭' : '😇'}',
|
||||
'${l10n.roundElimination(ronda, resultado.eliminadoNombre)} ${resultado.eraImpostor ? '🎭' : '😇'}',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: resultado.eraImpostor
|
||||
@@ -210,7 +212,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.replay),
|
||||
label: const Text('Revancha'),
|
||||
label: Text(l10n.rematch),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -229,7 +231,7 @@ class PantallaFinPartida extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.home),
|
||||
label: const Text('Menú principal'),
|
||||
label: Text(l10n.mainMenu),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../servicios/servicio_notas.dart';
|
||||
@@ -57,6 +58,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return const SizedBox.shrink();
|
||||
@@ -68,7 +70,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('📝 Notas'),
|
||||
title: Text(l10n.notesTitle),
|
||||
actions: [
|
||||
if (_jugadorSeleccionadoId != null)
|
||||
IconButton(
|
||||
@@ -77,7 +79,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
await _guardarNotas();
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Notas guardadas')),
|
||||
SnackBar(content: Text(l10n.notesSaved)),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -91,18 +93,20 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
}
|
||||
|
||||
Widget _construirSelectorJugador(dynamic partida) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'¿Quién eres?',
|
||||
l10n.whoAreYou,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Selecciona tu nombre para ver tus notas privadas',
|
||||
l10n.selectYourName,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -142,6 +146,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final jugadorActual = partida.jugadores
|
||||
.firstWhere((j) => j.id == _jugadorSeleccionadoId);
|
||||
|
||||
@@ -167,7 +172,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
},
|
||||
),
|
||||
Text(
|
||||
'Notas de ${jugadorActual.nombre}',
|
||||
l10n.notesOf(jugadorActual.nombre),
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
@@ -176,7 +181,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
|
||||
// Notas por jugador
|
||||
Text(
|
||||
'Apuntes sobre cada jugador',
|
||||
l10n.notesAboutPlayers,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: TemaApp.colorTextoSecundario,
|
||||
),
|
||||
@@ -190,7 +195,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
decoration: InputDecoration(
|
||||
labelText: j.nombre,
|
||||
prefixIcon: const Icon(Icons.person, size: 20),
|
||||
hintText: '¿Qué ha dicho? ¿Sospechoso?',
|
||||
hintText: l10n.playerNoteHint,
|
||||
),
|
||||
maxLines: 2,
|
||||
minLines: 1,
|
||||
@@ -200,7 +205,7 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Nota libre',
|
||||
l10n.freeNote,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: TemaApp.colorTextoSecundario,
|
||||
),
|
||||
@@ -208,9 +213,9 @@ class _PantallaNotasState extends State<PantallaNotas> {
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _controladorNotaLibre,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Apuntes personales...',
|
||||
prefixIcon: Icon(Icons.note, size: 20),
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.freeNoteHint,
|
||||
prefixIcon: const Icon(Icons.note, size: 20),
|
||||
),
|
||||
maxLines: 5,
|
||||
minLines: 3,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
import 'pantalla_ajustes.dart';
|
||||
import 'pantalla_crear_partida.dart';
|
||||
import 'pantalla_reglas.dart';
|
||||
import 'pantalla_unirse.dart';
|
||||
@@ -9,6 +11,8 @@ class PantallaPrincipal extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
@@ -47,7 +51,7 @@ class PantallaPrincipal extends StatelessWidget {
|
||||
|
||||
// Título
|
||||
Text(
|
||||
'El Impostor',
|
||||
l10n.appTitle,
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
fontSize: 36,
|
||||
letterSpacing: 1.2,
|
||||
@@ -55,7 +59,7 @@ class PantallaPrincipal extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Juego de deducción social',
|
||||
l10n.subtitle,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 16,
|
||||
),
|
||||
@@ -75,7 +79,7 @@ class PantallaPrincipal extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
icon: const Text('🎮', style: TextStyle(fontSize: 20)),
|
||||
label: const Text('Crear partida'),
|
||||
label: Text(l10n.createGame),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -92,7 +96,7 @@ class PantallaPrincipal extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
icon: const Text('📱', style: TextStyle(fontSize: 20)),
|
||||
label: const Text('Unirse a partida'),
|
||||
label: Text(l10n.joinGame),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -109,13 +113,30 @@ class PantallaPrincipal extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
icon: const Text('📖', style: TextStyle(fontSize: 20)),
|
||||
label: const Text('Cómo jugar'),
|
||||
label: Text(l10n.howToPlay),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const PantallaAjustes(),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.settings, size: 20),
|
||||
label: Text(l10n.settings),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
|
||||
Text(
|
||||
'3-20 jugadores • Sin internet',
|
||||
l10n.playersRange,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 12,
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
|
||||
class PantallaReglas extends StatelessWidget {
|
||||
@@ -6,78 +7,22 @@ class PantallaReglas extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('📖 Cómo jugar')),
|
||||
appBar: AppBar(title: Text(l10n.rulesTitle)),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_seccion(
|
||||
context,
|
||||
'🎭 ¿Qué es El Impostor?',
|
||||
'Un juego de deducción social para 3-20 jugadores. '
|
||||
'Todos reciben una palabra secreta... ¡excepto el impostor! '
|
||||
'Tu misión: descubrir quién finge.',
|
||||
),
|
||||
_seccion(
|
||||
context,
|
||||
'🔍 ¿Cómo se juega?',
|
||||
'1. Se reparten los roles: todos reciben la misma palabra, '
|
||||
'excepto el/los impostores.\n\n'
|
||||
'2. Debate: por turnos, cada jugador describe la palabra '
|
||||
'SIN decirla directamente. El impostor debe fingir que la conoce.\n\n'
|
||||
'3. Votación: al terminar el debate, todos votan a quién '
|
||||
'creen que es el impostor.\n\n'
|
||||
'4. Eliminación: el más votado queda eliminado y se revela '
|
||||
'si era impostor o no.\n\n'
|
||||
'5. Si era impostor, puede intentar adivinar la palabra. '
|
||||
'Si acierta, ¡los impostores ganan!',
|
||||
),
|
||||
_seccion(
|
||||
context,
|
||||
'🏆 ¿Quién gana?',
|
||||
'• Jugadores: ganan si eliminan a TODOS los impostores.\n'
|
||||
'• Impostores: ganan si no son descubiertos hasta que '
|
||||
'queden igual o menos jugadores normales que impostores, '
|
||||
'o si adivinan la palabra al ser eliminados.',
|
||||
),
|
||||
_seccion(
|
||||
context,
|
||||
'💡 Consejos para jugadores',
|
||||
'• Da pistas sutiles que demuestren que conoces la palabra, '
|
||||
'pero no tan obvias que el impostor las use.\n'
|
||||
'• Observa quién da respuestas vagas o genéricas.\n'
|
||||
'• Usa las notas para apuntar lo que dice cada uno.\n'
|
||||
'• No digas la palabra directamente, ¡eso ayuda al impostor!',
|
||||
),
|
||||
_seccion(
|
||||
context,
|
||||
'🎭 Consejos para el impostor',
|
||||
'• Escucha atentamente las pistas de los demás.\n'
|
||||
'• Intenta deducir la palabra para dar pistas creíbles.\n'
|
||||
'• No seas el primero en hablar si no estás seguro.\n'
|
||||
'• Si te dan la categoría como pista, úsala a tu favor.\n'
|
||||
'• Acusa a otros para desviar la atención.',
|
||||
),
|
||||
_seccion(
|
||||
context,
|
||||
'📱 Modos de juego',
|
||||
'• Un solo móvil: todos comparten el dispositivo. '
|
||||
'Cada jugador ve su palabra pulsando y manteniendo un botón.\n\n'
|
||||
'• Multimóvil: cada jugador usa su propio dispositivo. '
|
||||
'Se conectan por Bluetooth/WiFi Direct sin necesidad de internet.',
|
||||
),
|
||||
_ejemplo(
|
||||
context,
|
||||
'✏️ Ejemplo de partida',
|
||||
'Palabra secreta: "Pizza"\n\n'
|
||||
'• Ana: "Se come caliente" ✓\n'
|
||||
'• Carlos: "Viene en una caja" ✓\n'
|
||||
'• Eva (impostor): "Es muy popular" 🤔\n'
|
||||
'• David: "Tiene queso" ✓\n\n'
|
||||
'Eva dio una respuesta muy genérica... ¡Sospechosa!',
|
||||
),
|
||||
_seccion(context, l10n.rulesWhatIsTitle, l10n.rulesWhatIsBody),
|
||||
_seccion(context, l10n.rulesHowToPlayTitle, l10n.rulesHowToPlayBody),
|
||||
_seccion(context, l10n.rulesWhoWinsTitle, l10n.rulesWhoWinsBody),
|
||||
_seccion(context, l10n.rulesTipsPlayersTitle, l10n.rulesTipsPlayersBody),
|
||||
_seccion(context, l10n.rulesTipsImpostorTitle, l10n.rulesTipsImpostorBody),
|
||||
_seccion(context, l10n.rulesModesTitle, l10n.rulesModesBody),
|
||||
_ejemplo(context, l10n.rulesExampleTitle, l10n.rulesExampleBody),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../modelos/partida.dart';
|
||||
@@ -52,12 +53,13 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.read<EstadoJuego>();
|
||||
final partida = estado.partida;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Resultado'),
|
||||
title: Text(l10n.result),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Center(
|
||||
@@ -71,7 +73,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
const Text('🥁', style: TextStyle(fontSize: 64)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Revelando...',
|
||||
l10n.revealing,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
@@ -113,8 +115,8 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
),
|
||||
child: Text(
|
||||
widget.resultado.eraImpostor
|
||||
? '¡Era IMPOSTOR! 🎉'
|
||||
: 'Era INOCENTE 😱',
|
||||
? l10n.wasImpostor
|
||||
: l10n.wasInnocent,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -133,7 +135,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Votos de esta ronda',
|
||||
Text(l10n.votesThisRound,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium),
|
||||
@@ -177,6 +179,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
}
|
||||
|
||||
Widget _construirBotones(BuildContext context, EstadoJuego estado) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return const SizedBox.shrink();
|
||||
|
||||
@@ -195,7 +198,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.emoji_events),
|
||||
label: const Text('Ver resultado final'),
|
||||
label: Text(l10n.seeEndResult),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -217,7 +220,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
);
|
||||
},
|
||||
icon: const Text('🎯', style: TextStyle(fontSize: 18)),
|
||||
label: const Text('¿El impostor adivina la palabra?'),
|
||||
label: Text(l10n.impostorGuessWord),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -227,7 +230,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => _siguienteRonda(context, estado),
|
||||
icon: const Icon(Icons.skip_next),
|
||||
label: const Text('Siguiente ronda'),
|
||||
label: Text(l10n.nextRound),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -240,7 +243,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => _siguienteRonda(context, estado),
|
||||
icon: const Icon(Icons.skip_next),
|
||||
label: const Text('Siguiente ronda'),
|
||||
label: Text(l10n.nextRound),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
|
||||
class PantallaUnirse extends StatelessWidget {
|
||||
@@ -6,8 +7,10 @@ class PantallaUnirse extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Unirse a partida')),
|
||||
appBar: AppBar(title: Text(l10n.joinGameTitle)),
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
@@ -17,13 +20,12 @@ class PantallaUnirse extends StatelessWidget {
|
||||
const Text('📱', style: TextStyle(fontSize: 64)),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Modo multimóvil',
|
||||
l10n.multiDeviceMode,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Escanea el código QR que muestra el host '
|
||||
'para conectarte a la partida vía Bluetooth/WiFi Direct.',
|
||||
l10n.scanQrDescription,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -41,17 +43,14 @@ class PantallaUnirse extends StatelessWidget {
|
||||
const Text('🚧', style: TextStyle(fontSize: 32)),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Próximamente',
|
||||
l10n.comingSoon,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
color: TemaApp.colorNaranja,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'La conexión multimóvil con Nearby Connections '
|
||||
'requiere dispositivos Android físicos.\n\n'
|
||||
'Por ahora, usa el modo "Un solo móvil" '
|
||||
'para jugar en un dispositivo compartido.',
|
||||
l10n.nearbyNotAvailable,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
@@ -64,7 +63,7 @@ class PantallaUnirse extends StatelessWidget {
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
label: const Text('Volver'),
|
||||
label: Text(l10n.back),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../modelos/palabra.dart';
|
||||
@@ -17,6 +18,7 @@ class _PantallaVerPalabraState extends State<PantallaVerPalabra> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final estado = context.watch<EstadoJuego>();
|
||||
final partida = estado.partida;
|
||||
if (partida == null) return const SizedBox.shrink();
|
||||
@@ -26,7 +28,7 @@ class _PantallaVerPalabraState extends State<PantallaVerPalabra> {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Ver tu palabra'),
|
||||
title: Text(l10n.seeYourWord),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
@@ -34,13 +36,13 @@ class _PantallaVerPalabraState extends State<PantallaVerPalabra> {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Cada jugador debe ver su palabra en secreto',
|
||||
l10n.eachPlayerMustSee,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Ronda ${partida.rondaActual}',
|
||||
l10n.roundNumber(partida.rondaActual),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: TemaApp.colorNaranja,
|
||||
),
|
||||
@@ -69,7 +71,7 @@ class _PantallaVerPalabraState extends State<PantallaVerPalabra> {
|
||||
),
|
||||
title: Text(jugador.nombre),
|
||||
subtitle: Text(
|
||||
haVisto ? 'Ya ha visto su palabra' : 'Pulsa para ver',
|
||||
haVisto ? l10n.alreadySeen : l10n.tapToSee,
|
||||
),
|
||||
trailing: haVisto
|
||||
? const Icon(Icons.check_circle,
|
||||
@@ -102,8 +104,8 @@ class _PantallaVerPalabraState extends State<PantallaVerPalabra> {
|
||||
: null,
|
||||
icon: const Icon(Icons.forum),
|
||||
label: Text(todosHanVisto
|
||||
? 'Todos han visto → Iniciar debate'
|
||||
: 'Faltan ${partida.jugadores.length - _hanVisto.length} jugadores'),
|
||||
? l10n.allSeenStartDebate
|
||||
: l10n.playersRemaining(partida.jugadores.length - _hanVisto.length)),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -163,6 +165,8 @@ class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(widget.nombre)),
|
||||
body: Center(
|
||||
@@ -208,8 +212,8 @@ class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
widget.esImpostor
|
||||
? '¡Eres el impostor!'
|
||||
: 'Tu palabra es:',
|
||||
? l10n.youAreImpostor
|
||||
: l10n.yourWordIs,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
@@ -236,7 +240,7 @@ class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> {
|
||||
if (widget.esImpostor && widget.pistaActiva) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Pista: ${BancoPalabras.nombreBonitoCategoria(widget.categoria)}',
|
||||
l10n.clueCategory(BancoPalabras.nombreBonitoCategoria(widget.categoria, l10n)),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
@@ -252,13 +256,13 @@ class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> {
|
||||
const Text('🔒', style: TextStyle(fontSize: 48)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Mantén pulsado para ver tu palabra',
|
||||
l10n.holdToSeeWord,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Asegúrate de que nadie más mira',
|
||||
l10n.makeSureNoOneLooks,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
@@ -291,8 +295,8 @@ class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> {
|
||||
child: Center(
|
||||
child: Text(
|
||||
_manteniendo
|
||||
? '👁️ Mostrando...'
|
||||
: '👆 Mantén pulsado para ver',
|
||||
? l10n.showingWord
|
||||
: l10n.holdToSee,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
@@ -313,7 +317,7 @@ class _PantallaRevelarPalabraState extends State<_PantallaRevelarPalabra> {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
icon: const Icon(Icons.check),
|
||||
label: const Text('He visto mi palabra'),
|
||||
label: Text(l10n.seenMyWord),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:el_impostor/l10n/generated/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../estado/estado_juego.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
@@ -48,12 +49,13 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
return _construirTodosVotaron(context, estado);
|
||||
}
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final votanteActual = sinVotar.isNotEmpty ? sinVotar[0] : activos[0];
|
||||
final puedenRecibir = activos.where((j) => j.id != votanteActual.id).toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('🗳️ Votación'),
|
||||
title: Text(l10n.voting),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Padding(
|
||||
@@ -71,7 +73,7 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'Turno de votar:',
|
||||
l10n.turnToVote,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -83,7 +85,7 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Votos: ${estado.votos.length}/${activos.length}',
|
||||
l10n.votesProgress(estado.votos.length, activos.length),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -102,7 +104,7 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Text(
|
||||
'¿Quién crees que es el impostor?',
|
||||
l10n.whoIsImpostor,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -155,7 +157,7 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.how_to_vote),
|
||||
label: const Text('Confirmar voto'),
|
||||
label: Text(l10n.confirmVote),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -165,9 +167,11 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
}
|
||||
|
||||
Widget _construirTodosVotaron(BuildContext context, EstadoJuego estado) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('🗳️ Votación completa'),
|
||||
title: Text(l10n.votingComplete),
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
body: Center(
|
||||
@@ -179,12 +183,12 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
const Text('🗳️', style: TextStyle(fontSize: 64)),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'¡Todos han votado!',
|
||||
l10n.allVoted,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Pulsa para revelar el resultado',
|
||||
l10n.tapToReveal,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
@@ -205,7 +209,7 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.visibility),
|
||||
label: const Text('Revelar resultado'),
|
||||
label: Text(l10n.revealResult),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user