import 'package:flutter/material.dart'; import '../l10n/generated/app_localizations.dart'; import 'componentes_farolero.dart'; import 'tema_app.dart'; /// Quién está saliendo, que determina qué se pierde y por tanto qué se avisa. enum ContextoSalida { /// Un solo móvil: la partida vive en este dispositivo y se pierde entera. unDispositivo, /// El host de una partida multidispositivo: al salir la corta para todos. host, /// Un cliente: solo se va él y puede volver mientras la sala siga abierta. cliente, } /// Diálogo de confirmación con el estilo de la app. /// /// Devuelve `true` solo si el jugador confirma. Cerrarlo tocando fuera o con /// el gesto de atrás cuenta como cancelar, que es lo que interesa cuando lo /// que se está evitando es precisamente una salida accidental. Future confirmarFarolero({ required BuildContext context, required String titulo, required String mensaje, required String textoConfirmar, required String textoCancelar, IconData icono = Icons.warning_amber_rounded, IconData iconoConfirmar = Icons.logout, IconData iconoCancelar = Icons.play_arrow, }) async { final confirmado = await showDialog( context: context, barrierDismissible: true, builder: (dialogContext) => Dialog( backgroundColor: TemaApp.colorSuperficie, insetPadding: const EdgeInsets.symmetric(horizontal: 28, vertical: 24), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), side: BorderSide( color: TemaApp.colorNaranja.withValues(alpha: 0.45), ), ), child: Padding( padding: const EdgeInsets.fromLTRB(24, 28, 24, 24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 72, height: 72, decoration: BoxDecoration( shape: BoxShape.circle, color: TemaApp.colorAcento.withValues(alpha: 0.16), border: Border.all( color: TemaApp.colorAcento.withValues(alpha: 0.65), width: 2, ), ), child: IconoFarolero( icono, color: TemaApp.colorNaranja, size: 38, ), ), const SizedBox(height: 18), Text( titulo, textAlign: TextAlign.center, style: Theme.of(dialogContext).textTheme.titleLarge?.copyWith( color: TemaApp.colorDorado, ), ), const SizedBox(height: 10), Text( mensaje, textAlign: TextAlign.center, style: Theme.of(dialogContext).textTheme.bodyMedium?.copyWith( color: TemaApp.colorTextoSecundario, height: 1.35, ), ), const SizedBox(height: 26), // La acción segura va primero y destacada: si alguien llega aquí // sin querer, lo cómodo tiene que ser quedarse. BotonFarolero( texto: textoCancelar, icono: iconoCancelar, onPressed: () => Navigator.of(dialogContext).pop(false), ), const SizedBox(height: 12), BotonFarolero.oscuro( texto: textoConfirmar, icono: iconoConfirmar, onPressed: () => Navigator.of(dialogContext).pop(true), ), ], ), ), ), ); return confirmado ?? false; } /// Pregunta antes de abandonar una partida en curso. Future confirmarSalirDePartida( BuildContext context, ContextoSalida contexto, ) { final l10n = AppLocalizations.of(context)!; return confirmarFarolero( context: context, titulo: l10n.leaveGameTitle, mensaje: switch (contexto) { ContextoSalida.unDispositivo => l10n.leaveGameSingleDevice, ContextoSalida.host => l10n.leaveGameHost, ContextoSalida.cliente => l10n.leaveGameClient, }, textoConfirmar: l10n.leaveGame, textoCancelar: l10n.stayInGame, ); } /// Envuelve una pantalla de partida para que el gesto o botón de atrás no la /// abandone sin preguntar. /// /// Se encarga solo del atrás del sistema; los botones de cerrar de cada /// pantalla llaman a [confirmarSalirDePartida] por su cuenta, porque además de /// confirmar tienen que desconectar y navegar. class GuardiaSalidaPartida extends StatelessWidget { final Widget child; final ContextoSalida contexto; /// Qué hacer cuando el jugador confirma que quiere salir. Si es null se deja /// que la ruta se cierre con normalidad. final VoidCallback? onSalirConfirmado; /// Permite desactivar la guarda sin sacar el widget del árbol, por ejemplo /// cuando la partida ya ha terminado. final bool activo; const GuardiaSalidaPartida({ super.key, required this.child, required this.contexto, this.onSalirConfirmado, this.activo = true, }); @override Widget build(BuildContext context) { return PopScope( canPop: !activo, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; final salir = await confirmarSalirDePartida(context, contexto); if (!salir || !context.mounted) return; final alSalir = onSalirConfirmado; if (alSalir != null) { alSalir(); } else if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } }, child: child, ); } }