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 '../servicios/servicio_perfil_usuario.dart'; import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; /// Lobby del host. El host es autoridad de sala y tambiƩn cliente local. class PantallaLobbyHost extends StatefulWidget { final String nombreSala; final VoidCallback onIniciar; const PantallaLobbyHost({ super.key, required this.nombreSala, required this.onIniciar, }); @override State createState() => _PantallaLobbyHostState(); } class _PantallaLobbyHostState extends State { bool _iniciando = false; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final nearby = context.watch(); final sala = nearby.estadoSala; final usuarios = nearby.usuarios; final seleccionados = usuarios.where((u) => u.estaSeleccionado).length; final validacionInicio = sala?.validarInicio(); final puedeIniciar = validacionInicio?.exitoso ?? false; return Scaffold( appBar: AppBar( title: Text(widget.nombreSala), leading: IconButton( icon: const Icon(Icons.close), onPressed: () async { await nearby.desconectar(); if (context.mounted) Navigator.pop(context); }, ), ), body: FondoFarolero( intenso: true, child: SafeArea( top: false, child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 20, 20, 24), child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const _LobbySignalArt(), const SizedBox(height: 12), EncabezadoFarolero( icono: Icons.wifi_tethering, titulo: widget.nombreSala, subtitulo: l10n.scanToJoin, ), const SizedBox(height: 14), PanelFarolero( padding: const EdgeInsets.fromLTRB(18, 20, 18, 18), child: Column( children: [ SizedBox( width: 268, height: 268, child: Stack( alignment: Alignment.center, children: [ Positioned.fill( child: IgnorePointer( child: Image.asset( 'assets/ui/generated/join_lobby/qr_frame.png', fit: BoxFit.contain, ), ), ), Container( width: 210, height: 210, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: TemaApp.colorDorado.withValues(alpha: 0.42), width: 2, ), boxShadow: [ BoxShadow( color: TemaApp.colorNaranja.withValues(alpha: 0.20), blurRadius: 24, ), ], ), child: QrImageView( data: nearby.generarDatosQR(widget.nombreSala), version: QrVersions.auto, size: 182, backgroundColor: Colors.white, ), ), ], ), ), const SizedBox(height: 10), Text( l10n.scanThisCodeFromAnotherPhone, style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center, ), ], ), ), const SizedBox(height: 16), _buildResumenSala( context, l10n, seleccionados, nearby.jugadores.length, ), const SizedBox(height: 12), Card( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Expanded( child: Text( l10n.gameUsers, style: Theme.of(context).textTheme.titleLarge, ), ), IconButton.filledTonal( onPressed: () => _crearNuevoUsuario(context), icon: const Icon(Icons.person_add), ), ], ), const SizedBox(height: 8), if (usuarios.isEmpty) SizedBox( height: 96, child: Center(child: Text(l10n.waitingForPlayers)), ) else ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: usuarios.length, itemBuilder: (context, index) => _buildUsuarioTile( context, l10n, usuarios[index], ), ), ], ), ), ), const SizedBox(height: 12), if (!puedeIniciar) Text( _mensajeValidacion(validacionInicio?.codigo, l10n), style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(color: TemaApp.colorNaranja), textAlign: TextAlign.center, ), const SizedBox(height: 12), BotonFarolero( texto: _iniciando ? l10n.starting : l10n.startGame, icono: Icons.play_arrow, onPressed: puedeIniciar && !_iniciando ? () { setState(() => _iniciando = true); widget.onIniciar(); } : null, ), ], ), ), ), ), ), ), ); } Widget _buildResumenSala( BuildContext context, AppLocalizations l10n, int seleccionados, int clientesRemotos, ) { return Row( children: [ Expanded( child: _buildStat( context, icon: Icons.groups, label: l10n.selectedPlayers, value: '$seleccionados', ok: seleccionados >= 3, ), ), const SizedBox(width: 8), Expanded( child: _buildStat( context, icon: Icons.devices, label: l10n.connectedPhones, value: '${clientesRemotos + 1}', ok: true, ), ), ], ); } Widget _buildStat( BuildContext context, { required IconData icon, required String label, required String value, required bool ok, }) { final color = ok ? TemaApp.colorVerde : TemaApp.colorNaranja; return Container( constraints: const BoxConstraints(minHeight: 70), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( color: color.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withValues(alpha: 0.55)), ), child: Row( children: [ Icon(icon, color: color, size: 26), const SizedBox(width: 10), Expanded( child: Text( label, maxLines: 2, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.bodySmall?.copyWith(height: 1.08), ), ), const SizedBox(width: 8), Text( value, style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 18, ), ), ], ), ); } Widget _buildUsuarioTile( BuildContext context, AppLocalizations l10n, Usuario usuario, ) { final nearby = context.read(); final miClientId = nearby.miClientId; final seleccionadoPorMi = usuario.clienteIdSeleccionado == miClientId; final seleccionadoPorOtro = usuario.estaSeleccionado && usuario.clienteIdSeleccionado != miClientId; return ListTile( minLeadingWidth: 58, leading: SizedBox( width: 58, height: 58, child: AvatarFarolero( texto: usuario.nombre.isEmpty ? '?' : usuario.nombre[0], assetPath: usuario.avatar, color: seleccionadoPorMi ? TemaApp.colorVerde : seleccionadoPorOtro ? TemaApp.colorNaranja : TemaApp.colorAzul, size: 48, fuego: usuario.fuego, medallas: usuario.medallas, ), ), title: Text(usuario.nombre), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( seleccionadoPorMi ? l10n.selectedOnThisPhone : seleccionadoPorOtro ? l10n.selectedByAnotherDevice : l10n.available, ), if (usuario.medallas.isNotEmpty) ...[ const SizedBox(height: 4), MedallasCompactasFarolero(ids: usuario.medallas), ], ], ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (seleccionadoPorMi) IconButton( tooltip: l10n.release, icon: const Icon(Icons.close), onPressed: () => nearby.liberarUsuarioSala(usuario.id), ) else if (!seleccionadoPorOtro) IconButton( tooltip: l10n.select, icon: const Icon(Icons.check_circle_outline), onPressed: () => nearby.seleccionarUsuarioSala(usuario.id), ), if (!usuario.estaSeleccionado) IconButton( tooltip: l10n.delete, icon: const Icon(Icons.delete_outline, color: TemaApp.colorAcento), onPressed: () => nearby.eliminarUsuarioSala(usuario.id), ), ], ), ); } String _mensajeValidacion(String? codigo, AppLocalizations l10n) { switch (codigo) { case 'faltan_jugadores': return l10n.selectAtLeastThreeUsersToStart; case 'host_sin_usuario': return l10n.hostPhoneMustSelectUser; case 'sala_cerrada': return l10n.roomNoLongerInLobby; default: return l10n.completeUserSelectionToStart; } } Future _crearNuevoUsuario(BuildContext context) async { final l10n = AppLocalizations.of(context)!; final controller = TextEditingController(); final nearby = context.read(); final perfil = context.read().perfil; final gamificacion = context.read().resumenGamificacion; final nombre = await showDialog( 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) { await nearby.crearUsuarioSala( nombre.trim(), seleccionar: true, nick: perfil.nick, avatar: perfil.avatarAsset, fuego: gamificacion.fuego, medallas: gamificacion.medallas, ); } } } class _LobbySignalArt extends StatelessWidget { const _LobbySignalArt(); @override Widget build(BuildContext context) { return SizedBox( height: 104, width: double.infinity, child: Image.asset( 'assets/ui/generated/join_lobby/signal_art.png', fit: BoxFit.contain, opacity: const AlwaysStoppedAnimation(0.90), ), ); } }