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:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:farolero/l10n/generated/app_localizations.dart';
|
||||
import '../modelos/usuario.dart';
|
||||
import '../servicios/servicio_nearby.dart';
|
||||
import '../tema/tema_app.dart';
|
||||
|
||||
@@ -22,6 +23,7 @@ class PantallaLobbyHost extends StatefulWidget {
|
||||
|
||||
class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
|
||||
bool _iniciando = false;
|
||||
String? _perfilSeleccionado;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -66,6 +68,65 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Selección de perfil
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l10n.selectYourProfile,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
value: _perfilSeleccionado,
|
||||
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
|
||||
...nearby.usuarios.map((usuario) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: usuario.nombre,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(usuario.avatar ?? '👤'),
|
||||
const SizedBox(width: 8),
|
||||
Text(usuario.nombre),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
onChanged: (valor) {
|
||||
if (valor == '_new_') {
|
||||
_crearNuevoUsuario(context);
|
||||
} else {
|
||||
setState(() => _perfilSeleccionado = valor);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Lista de jugadores
|
||||
Expanded(
|
||||
child: Column(
|
||||
@@ -116,7 +177,10 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('📱', style: TextStyle(fontSize: 48)),
|
||||
const Text(
|
||||
'📱',
|
||||
style: TextStyle(fontSize: 48),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
l10n.waitingForPlayers,
|
||||
@@ -141,15 +205,26 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
|
||||
if (totalJugadores < 3)
|
||||
Text(
|
||||
l10n.needMorePlayers(3 - totalJugadores),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: TemaApp.colorNaranja,
|
||||
),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: TemaApp.colorNaranja),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (_perfilSeleccionado == null)
|
||||
Text(
|
||||
l10n.selectProfile,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: TemaApp.colorNaranja),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: totalJugadores >= 3 && !_iniciando
|
||||
onPressed:
|
||||
totalJugadores >= 3 &&
|
||||
_perfilSeleccionado != null &&
|
||||
!_iniciando
|
||||
? () {
|
||||
setState(() => _iniciando = true);
|
||||
widget.onIniciar();
|
||||
@@ -181,10 +256,7 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
|
||||
Text(esHost ? '👑' : '🎭', style: const TextStyle(fontSize: 20)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
nombre,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
child: Text(nombre, style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
if (esHost)
|
||||
Container(
|
||||
@@ -206,4 +278,46 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _crearNuevoUsuario(BuildContext context) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final controller = TextEditingController();
|
||||
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: Text(l10n.accept),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (nombre != null && nombre.trim().isNotEmpty) {
|
||||
final nuevoUsuario = Usuario(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
nombre: nombre.trim(),
|
||||
);
|
||||
nearby.agregarUsuario(nuevoUsuario);
|
||||
setState(() => _perfilSeleccionado = nombre.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user