feat(multi-device): host puede participar como jugador

- Añadido modelo Usuario con pool de usuarios sincronizado
- El host ahora recibe palabra y rol como cualquier jugador
- UI de selección de perfil en pantallas de lobby
- Los clientes pueden ver usuarios del servidor o crear nuevos
- El juego no inicia hasta que el host selecciona perfil
This commit is contained in:
ShanaiaBot
2026-04-24 18:47:56 +02:00
parent 3df3ae1e95
commit d3fc3386f9
31 changed files with 1266 additions and 106 deletions

View File

@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import '../estado/estado_juego.dart';
import '../modelos/palabra.dart';
import '../modelos/partida.dart';
import '../modelos/usuario.dart';
import '../servicios/servicio_nearby.dart';
import '../servicios/servicio_permisos.dart';
import '../tema/tema_app.dart';
@@ -121,8 +122,8 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
return;
}
// 2. Pedir nombre del host
final nombre = await _pedirNombreHost();
// 2. Seleccionar o crear usuario del pool
final nombre = await _seleccionarUsuarioHost();
if (nombre == null || nombre.trim().isEmpty) return;
// 3. Iniciar host en Nearby
@@ -152,6 +153,10 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
onIniciar: () {
// Cuando el host toca "Iniciar" con suficientes jugadores
final estado = context.read<EstadoJuego>();
// Set host local player first (required for host-included game)
estado.setHostJugador(nombre.trim());
final jugadoresMulti = [
nombre.trim(),
...nearby.jugadores.map((j) => j.nombre),
@@ -207,6 +212,133 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
}
}
/// Muestra diálogo para seleccionar usuario del pool o crear nuevo
Future<String?> _seleccionarUsuarioHost() async {
final l10n = AppLocalizations.of(context)!;
final nearby = context.read<ServicioNearby>();
final usuarios = nearby.usuarios;
// Si hay usuarios en el pool, mostrar selección
if (usuarios.isNotEmpty) {
String? seleccionado;
return showDialog<String>(
context: context,
builder: (ctx) => StatefulBuilder(
builder: (ctx, setDialogState) => AlertDialog(
title: Text(l10n.selectYourProfile),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButtonFormField<String>(
value: seleccionado,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.person),
hintText: l10n.selectProfile,
border: const OutlineInputBorder(),
),
items: [
// Opción para crear nuevo usuario
DropdownMenuItem<String>(
value: '_new_',
child: Row(
children: [
const Icon(Icons.add, size: 18),
const SizedBox(width: 8),
Text(l10n.createNewUser),
],
),
),
// Usuarios existentes
...usuarios.map((usuario) {
return DropdownMenuItem<String>(
value: usuario.nombre,
child: Row(
children: [
Text(usuario.avatar ?? '👤'),
const SizedBox(width: 8),
Text(usuario.nombre),
],
),
);
}),
],
onChanged: (valor) {
setDialogState(() => seleccionado = valor);
},
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: Text(l10n.cancel),
),
TextButton(
onPressed: () {
if (seleccionado == '_new_') {
Navigator.pop(ctx);
_crearNuevoUsuarioHost();
} else if (seleccionado != null) {
Navigator.pop(ctx, seleccionado);
}
},
child: Text(l10n.accept),
),
],
),
),
);
}
// Pool vacío, pedir nombre nuevo
return _pedirNombreHost();
}
/// Crea un nuevo usuario y lo agrega al pool
Future<String?> _crearNuevoUsuarioHost() async {
final controller = TextEditingController();
final l10n = AppLocalizations.of(context)!;
final nearby = context.read<ServicioNearby>();
final nombre = await showDialog<String>(
context: context,
builder: (ctx) => AlertDialog(
title: Text(l10n.createNewUser),
content: TextField(
controller: controller,
autofocus: true,
textCapitalization: TextCapitalization.words,
decoration: InputDecoration(
hintText: l10n.yourName,
prefixIcon: const Icon(Icons.person),
),
onSubmitted: (v) => Navigator.pop(ctx, v),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: Text(l10n.cancel),
),
TextButton(
onPressed: () => Navigator.pop(ctx, controller.text),
child: const Text('OK'),
),
],
),
);
if (nombre != null && nombre.trim().isNotEmpty) {
final nuevoUsuario = Usuario(
id: DateTime.now().millisecondsSinceEpoch.toString(),
nombre: nombre.trim(),
);
nearby.agregarUsuario(nuevoUsuario);
return nombre.trim();
}
return null;
}
/// Método original para pedir nombre (usado cuando pool vacío)
Future<String?> _pedirNombreHost() async {
final controller = TextEditingController();
final l10n = AppLocalizations.of(context)!;