aplicadas correcciones

This commit is contained in:
2026-05-10 22:53:41 +02:00
parent 12af58d828
commit fa7901019f
42 changed files with 2708 additions and 76 deletions

View File

@@ -55,8 +55,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
final servicioPerfil = context.read<ServicioPerfilUsuario>();
if (!servicioPerfil.cargado) return;
final perfil = servicioPerfil.perfil;
final nombre =
perfil.nombre.trim().isEmpty ? 'Jugador' : perfil.nombre.trim();
final l10n = AppLocalizations.of(context)!;
final nombre = perfil.nombre.trim().isEmpty
? l10n.defaultPlayerName
: perfil.nombre.trim();
if (_jugadores.contains(nombre)) return;
setState(() {
_jugadores.insert(0, nombre);
@@ -101,8 +103,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
void _eliminarJugador(int index) {
final perfil = context.read<ServicioPerfilUsuario>().perfil;
final nombrePerfil =
perfil.nombre.trim().isEmpty ? 'Jugador' : perfil.nombre.trim();
final l10n = AppLocalizations.of(context)!;
final nombrePerfil = perfil.nombre.trim().isEmpty
? l10n.defaultPlayerName
: perfil.nombre.trim();
if (index == 0 && _jugadores[index] == nombrePerfil) return;
setState(() {
_jugadores.removeAt(index);
@@ -146,13 +150,14 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
}
Future<void> _iniciarPartidaMulti() async {
final l10n = AppLocalizations.of(context)!;
// 1. Pedir permisos automáticamente
final permisosOk = await ServicioPermisos.solicitarPermisosNearby(context);
if (!permisosOk) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Se necesitan permisos de Bluetooth y ubicación'),
SnackBar(
content: Text(l10n.bluetoothLocationPermissionsShort),
),
);
}
@@ -182,8 +187,8 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
if (!ok) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('No se pudo crear la sala. Verifica Bluetooth.'),
SnackBar(
content: Text(l10n.couldNotCreateRoom),
),
);
}
@@ -207,7 +212,9 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'No se puede iniciar: ${validacion.codigo ?? "sala inválida"}',
l10n.cannotStartWithReason(
validacion.codigo ?? l10n.invalidRoom,
),
),
),
);
@@ -301,7 +308,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
final categorias = ['todas', ...?estado.banco?.nombresCategorias];
final etiquetas = _etiquetasTiempo(l10n);
final nombrePerfilActual = servicioPerfil.perfil.nombre.trim().isEmpty
? 'Jugador'
? l10n.defaultPlayerName
: servicioPerfil.perfil.nombre.trim();
if (!_modoMultimovil &&
servicioPerfil.cargado &&
@@ -383,8 +390,8 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
Expanded(
child: Text(
_modoMultimovil
? 'Partida multidispositivo'
: 'Partida en este dispositivo',
? l10n.multiDeviceGameLabel
: l10n.singleDeviceGameLabel,
style: Theme.of(context).textTheme.titleMedium,
),
),
@@ -475,7 +482,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
..._jugadores.asMap().entries.map((e) {
final perfil = servicioPerfil.perfil;
final nombrePerfil = perfil.nombre.trim().isEmpty
? 'Jugador'
? l10n.defaultPlayerName
: perfil.nombre.trim();
final inicialPerfil = nombrePerfil.isEmpty
? '?'
@@ -498,7 +505,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
),
title: Text(e.value),
subtitle: esPerfilLocal
? const Text('Usuario principal del dispositivo')
? Text(l10n.mainDeviceUser)
: null,
trailing: esPerfilLocal
? const Icon(Icons.lock, color: TemaApp.colorDorado)

View File

@@ -220,6 +220,7 @@ class _HeroResultado extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final apertura = String.fromCharCode(0x00A1);
final tituloLimpio = titulo
.replaceAll(apertura, '')
@@ -254,7 +255,7 @@ class _HeroResultado extends StatelessWidget {
).animate().fadeIn(duration: 260.ms).slideY(begin: -0.18),
const SizedBox(height: 62),
Text(
'RESULTADOS',
l10n.result.toUpperCase(),
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: TemaApp.colorDorado,
fontWeight: FontWeight.w900,

View File

@@ -103,7 +103,7 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
),
const SizedBox(height: 10),
Text(
'Escanea este código desde otro móvil',
l10n.scanThisCodeFromAnotherPhone,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
@@ -111,7 +111,12 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
),
),
const SizedBox(height: 16),
_buildResumenSala(context, seleccionados, nearby.jugadores.length),
_buildResumenSala(
context,
l10n,
seleccionados,
nearby.jugadores.length,
),
const SizedBox(height: 12),
Expanded(
child: Card(
@@ -124,7 +129,7 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
children: [
Expanded(
child: Text(
'Usuarios de la partida',
l10n.gameUsers,
style: Theme.of(context).textTheme.titleLarge,
),
),
@@ -141,7 +146,11 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
: ListView.builder(
itemCount: usuarios.length,
itemBuilder: (context, index) =>
_buildUsuarioTile(context, usuarios[index]),
_buildUsuarioTile(
context,
l10n,
usuarios[index],
),
),
),
],
@@ -152,7 +161,7 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
const SizedBox(height: 12),
if (!puedeIniciar)
Text(
_mensajeValidacion(validacionInicio?.codigo),
_mensajeValidacion(validacionInicio?.codigo, l10n),
style: Theme.of(context)
.textTheme
.bodyMedium
@@ -182,6 +191,7 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
Widget _buildResumenSala(
BuildContext context,
AppLocalizations l10n,
int seleccionados,
int clientesRemotos,
) {
@@ -191,7 +201,7 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
child: _buildStat(
context,
icon: Icons.groups,
label: 'Jugadores seleccionados',
label: l10n.selectedPlayers,
value: '$seleccionados',
ok: seleccionados >= 3,
),
@@ -201,7 +211,7 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
child: _buildStat(
context,
icon: Icons.devices,
label: 'Móviles conectados',
label: l10n.connectedPhones,
value: '${clientesRemotos + 1}',
ok: true,
),
@@ -241,7 +251,11 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
);
}
Widget _buildUsuarioTile(BuildContext context, Usuario usuario) {
Widget _buildUsuarioTile(
BuildContext context,
AppLocalizations l10n,
Usuario usuario,
) {
final nearby = context.read<ServicioNearby>();
final miClientId = nearby.miClientId;
final seleccionadoPorMi = usuario.clienteIdSeleccionado == miClientId;
@@ -270,10 +284,10 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
children: [
Text(
seleccionadoPorMi
? 'Seleccionado por este móvil'
? l10n.selectedOnThisPhone
: seleccionadoPorOtro
? 'Seleccionado por otro cliente'
: 'Disponible',
? l10n.selectedByAnotherDevice
: l10n.available,
),
if (usuario.medallas.isNotEmpty) ...[
const SizedBox(height: 4),
@@ -286,19 +300,19 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
children: [
if (seleccionadoPorMi)
IconButton(
tooltip: 'Liberar',
tooltip: l10n.release,
icon: const Icon(Icons.close),
onPressed: () => nearby.liberarUsuarioSala(usuario.id),
)
else if (!seleccionadoPorOtro)
IconButton(
tooltip: 'Seleccionar',
tooltip: l10n.select,
icon: const Icon(Icons.check_circle_outline),
onPressed: () => nearby.seleccionarUsuarioSala(usuario.id),
),
if (!usuario.estaSeleccionado)
IconButton(
tooltip: 'Eliminar',
tooltip: l10n.delete,
icon: const Icon(Icons.delete_outline, color: TemaApp.colorAcento),
onPressed: () => nearby.eliminarUsuarioSala(usuario.id),
),
@@ -307,16 +321,16 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
);
}
String _mensajeValidacion(String? codigo) {
String _mensajeValidacion(String? codigo, AppLocalizations l10n) {
switch (codigo) {
case 'faltan_jugadores':
return 'Seleccioná al menos 3 usuarios para iniciar.';
return l10n.selectAtLeastThreeUsersToStart;
case 'host_sin_usuario':
return 'El móvil servidor debe seleccionar al menos un usuario.';
return l10n.hostPhoneMustSelectUser;
case 'sala_cerrada':
return 'La sala ya no está en lobby.';
return l10n.roomNoLongerInLobby;
default:
return 'Completá la selección de usuarios para iniciar.';
return l10n.completeUserSelectionToStart;
}
}

View File

@@ -36,8 +36,8 @@ class PantallaSeleccionModoJuego extends StatelessWidget {
marcoAsset: 'assets/ui/generated/mode/mode_single_card_frame.png',
icono: Icons.phone_android_rounded,
titulo: l10n.singleDevice,
subtitulo: 'Partida en este dispositivo',
descripcion: 'Ideal para jugar todos juntos pasando el móvil. Configuración rápida y directa.',
subtitulo: l10n.singleDeviceSubtitle,
descripcion: l10n.singleDeviceDescription,
onTap: () => Navigator.push(
context,
MaterialPageRoute(
@@ -53,8 +53,8 @@ class PantallaSeleccionModoJuego extends StatelessWidget {
marcoAsset: 'assets/ui/generated/mode/mode_multi_card_frame.png',
icono: Icons.devices_rounded,
titulo: l10n.multiDevice,
subtitulo: 'Cada jugador en su móvil',
descripcion: 'Crea una sala premium, comparte QR y gestiona usuarios desde el lobby.',
subtitulo: l10n.multiDeviceSubtitle,
descripcion: l10n.multiDeviceDescription,
destacado: true,
onTap: () => Navigator.push(
context,

View File

@@ -301,13 +301,13 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
/// Paso 1: validar nombre, pedir permisos e iniciar discovery
Future<void> _iniciarBusqueda() async {
if (!_formKey.currentState!.validate()) return;
final l10n = AppLocalizations.of(context)!;
// Solicitar permisos automáticamente
final permisosOk = await ServicioPermisos.solicitarPermisosNearby(context);
if (!permisosOk) {
setState(() {
_error =
'Se necesitan permisos de Bluetooth y ubicación para buscar partidas.';
_error = l10n.bluetoothLocationPermissionsRequired;
});
return;
}
@@ -332,14 +332,14 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
});
} else {
setState(() {
_error =
'No se pudo iniciar la búsqueda. Verifica Bluetooth y ubicación.';
_error = l10n.couldNotStartSearch;
});
}
}
/// Conectar a un host de la lista
Future<void> _conectarAHost(String endpointId, String nombreHost) async {
final l10n = AppLocalizations.of(context)!;
setState(() {
_conectando = true;
_salaSeleccionada = nombreHost;
@@ -363,7 +363,7 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
if (!ok && mounted) {
setState(() {
_conectando = false;
_error = 'No se pudo conectar a $nombreHost';
_error = l10n.couldNotConnectToHost(nombreHost);
});
// Reiniciar búsqueda
_iniciarBusqueda();
@@ -387,11 +387,12 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
final datos = ServicioNearby.parsearQR(valor);
if (datos != null) {
final l10n = AppLocalizations.of(context)!;
setState(() {
_escaneandoQR = false;
_conectando = true;
_salaSeleccionada =
datos['host'] as String? ?? datos['sala'] as String? ?? 'Sala';
datos['host'] as String? ?? datos['sala'] as String? ?? l10n.room;
});
// Iniciar búsqueda para que Nearby encuentre al host
@@ -530,8 +531,8 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
? '${l10n.connectingTo} ${_salaSeleccionada ?? ""}...'
: l10n.searchingGames,
subtitulo: _conectando
? 'Preparando la sala segura'
: 'Buscando partidas cercanas por Bluetooth',
? l10n.preparingSecureRoom
: l10n.searchingNearbyBluetoothGames,
color: _conectando ? TemaApp.colorAcento : TemaApp.colorNaranja,
trailing: SizedBox(
width: 24,
@@ -560,7 +561,7 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
itemCount: hosts.length,
itemBuilder: (context, index) {
final entry = hosts.entries.elementAt(index);
return _buildHostTile(entry.key, entry.value);
return _buildHostTile(l10n, entry.key, entry.value);
},
),
),
@@ -597,7 +598,11 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
);
}
Widget _buildHostTile(String endpointId, String nombre) {
Widget _buildHostTile(
AppLocalizations l10n,
String endpointId,
String nombre,
) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: Material(
@@ -630,7 +635,7 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
style: Theme.of(context).textTheme.titleMedium,
),
Text(
'Toca para unirte',
l10n.tapToJoin,
style: Theme.of(context).textTheme.bodySmall,
),
],
@@ -842,16 +847,18 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
}
}
/// Env?a el usuario seleccionado/creado al host
/// Envía el usuario seleccionado/creado al host
void _enviarUsuarioAlHost(Usuario usuario) {
final l10n = AppLocalizations.of(context)!;
final nearby = context.read<ServicioNearby>();
nearby.seleccionarUsuarioSala(usuario.id);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('${usuario.nombre} seleccionado')));
).showSnackBar(SnackBar(content: Text(l10n.profileSelected)));
}
Widget _buildUsuarioSalaTile(Usuario usuario) {
final l10n = AppLocalizations.of(context)!;
final nearby = context.read<ServicioNearby>();
final miClientId = nearby.miClientId;
final seleccionadoPorMi = usuario.clienteIdSeleccionado == miClientId;
@@ -872,10 +879,10 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
children: [
Text(
seleccionadoPorMi
? 'Seleccionado por este móvil'
? l10n.selectedOnThisPhone
: seleccionadoPorOtro
? 'No disponible'
: 'Disponible',
? l10n.notAvailable
: l10n.available,
),
if (usuario.medallas.isNotEmpty) ...[
const SizedBox(height: 4),