From 50f67320a5663324179f1016e4bc504d3d21a347 Mon Sep 17 00:00:00 2001 From: Paolo Date: Wed, 11 Mar 2026 23:00:01 +0100 Subject: [PATCH] Auto-sync: 20260311_230000 --- lib/services/multiplayer_service.dart | 4 +- lib/ui/multiplayer/lobby_screen.dart | 109 +++++++++++++++++++++----- 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/lib/services/multiplayer_service.dart b/lib/services/multiplayer_service.dart index b601288..bb79d26 100644 --- a/lib/services/multiplayer_service.dart +++ b/lib/services/multiplayer_service.dart @@ -4,11 +4,13 @@ import 'dart:math'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:share_plus/share_plus.dart'; class MultiplayerService { final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final FirebaseAuth _auth = FirebaseAuth.instance; CollectionReference get _gamesCollection => _firestore.collection('games'); @@ -25,6 +27,7 @@ class MultiplayerService { 'moves': [], 'seed': randomSeed, 'hostName': hostName, + 'hostUid': _auth.currentUser?.uid, // NUOVO: Salviamo l'ID univoco del creatore 'guestName': '', 'shape': shapeName, 'timeMode': isTimeMode, @@ -52,7 +55,6 @@ class MultiplayerService { return null; } - // QUERY BLINDATA: Chiede a Firebase solo le stanze waiting E pubbliche Stream getPublicRooms() { return _gamesCollection .where('status', isEqualTo: 'waiting') diff --git a/lib/ui/multiplayer/lobby_screen.dart b/lib/ui/multiplayer/lobby_screen.dart index b2f6a44..37fce8b 100644 --- a/lib/ui/multiplayer/lobby_screen.dart +++ b/lib/ui/multiplayer/lobby_screen.dart @@ -6,6 +6,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'dart:math' as math; import '../../logic/game_controller.dart'; import '../../models/game_board.dart'; @@ -511,6 +512,7 @@ class _CyberBorderPainter extends CustomPainter { bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue; } +// NUOVO: Aggiunto WidgetsBindingObserver per intercettare l'app in background class LobbyScreen extends StatefulWidget { final String? initialRoomCode; @@ -520,7 +522,7 @@ class LobbyScreen extends StatefulWidget { State createState() => _LobbyScreenState(); } -class _LobbyScreenState extends State { +class _LobbyScreenState extends State with WidgetsBindingObserver { final MultiplayerService _multiplayerService = MultiplayerService(); late TextEditingController _codeController; @@ -531,11 +533,14 @@ class _LobbyScreenState extends State { int _selectedRadius = 4; ArenaShape _selectedShape = ArenaShape.classic; bool _isTimeMode = true; - bool _isPublicRoom = true; // Di default la stanza è visibile a tutti + bool _isPublicRoom = true; + + bool _roomStarted = false; // Flag per capire se il gioco è effettivamente iniziato @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); // Attiviamo la sentinella _codeController = TextEditingController(); _playerName = StorageService.instance.playerName; @@ -547,7 +552,28 @@ class _LobbyScreenState extends State { } @override - void dispose() { _codeController.dispose(); super.dispose(); } + void dispose() { + WidgetsBinding.instance.removeObserver(this); // Rimuoviamo la sentinella + _cleanupGhostRoom(); // Se l'utente chiude la schermata, spazziamo via la stanza + _codeController.dispose(); + super.dispose(); + } + + // Intercetta quando l'app viene messa in background o chiusa! + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { + _cleanupGhostRoom(); + } + } + + // La funzione "Spazzino" + void _cleanupGhostRoom() { + if (_myRoomCode != null && !_roomStarted) { + FirebaseFirestore.instance.collection('games').doc(_myRoomCode).delete(); + _myRoomCode = null; // Evitiamo che venga chiamata due volte + } + } Future _createRoom() async { if (_isLoading) return; @@ -559,7 +585,7 @@ class _LobbyScreenState extends State { ); if (!mounted) return; - setState(() { _myRoomCode = code; _isLoading = false; }); + setState(() { _myRoomCode = code; _isLoading = false; _roomStarted = false; }); if (!_isPublicRoom) { _multiplayerService.shareInviteLink(code); @@ -666,6 +692,7 @@ class _LobbyScreenState extends State { if (snapshot.hasData && snapshot.data!.exists) { var data = snapshot.data!.data() as Map; if (data['status'] == 'playing') { + _roomStarted = true; // Il gioco è iniziato, non dobbiamo più cancellare la stanza! WidgetsBinding.instance.addPostFrameCallback((_) { Navigator.pop(context); context.read().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _isTimeMode); @@ -674,19 +701,31 @@ class _LobbyScreenState extends State { } } - return Dialog( - backgroundColor: Colors.transparent, - insetPadding: const EdgeInsets.all(20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - dialogContent, - const SizedBox(height: 20), - TextButton( - onPressed: () { FirebaseFirestore.instance.collection('games').doc(code).delete(); Navigator.pop(context); }, - child: Text("ANNULLA", style: _getTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))), - ), - ], + // NUOVO: PopScope intercetta lo swipe indietro e il tasto back di Android + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (didPop) return; + _cleanupGhostRoom(); // Spazza via la stanza se l'utente striscia per tornare indietro + Navigator.pop(context); + }, + child: Dialog( + backgroundColor: Colors.transparent, + insetPadding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + dialogContent, + const SizedBox(height: 20), + TextButton( + onPressed: () { + _cleanupGhostRoom(); // Spazza via la stanza se clicca ANNULLA + Navigator.pop(context); + }, + child: Text("ANNULLA", style: _getTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))), + ), + ], + ), ), ); }, @@ -869,7 +908,6 @@ class _LobbyScreenState extends State { StreamBuilder( stream: _multiplayerService.getPublicRooms(), builder: (context, snapshot) { - // RIMOZIONE DELL'ERRORE ROSSO DALLA SCHERMATA if (snapshot.connectionState == ConnectionState.waiting) { return Padding(padding: const EdgeInsets.all(20), child: Center(child: CircularProgressIndicator(color: theme.playerBlue))); } @@ -881,8 +919,39 @@ class _LobbyScreenState extends State { ); } - // Ordiniamo le stanze dalla più recente - var docs = snapshot.data!.docs; + DateTime now = DateTime.now(); // Tempo attuale + String? myUid = FirebaseAuth.instance.currentUser?.uid; + + // FILTRO LOCALE E SCADENZA (15 MINUTI) + var docs = snapshot.data!.docs.where((doc) { + var data = doc.data() as Map; + + // 1. Deve essere pubblica + if (data['isPublic'] != true) return false; + + // 2. Non devo vedere la mia stessa stanza + if (data['hostUid'] != null && data['hostUid'] == myUid) return false; + + // 3. Non deve essere una "Stanza Fantasma" (più vecchia di 15 minuti) + Timestamp? createdAt = data['createdAt'] as Timestamp?; + if (createdAt != null) { + int ageInMinutes = now.difference(createdAt.toDate()).inMinutes; + if (ageInMinutes > 15) { + FirebaseFirestore.instance.collection('games').doc(doc.id).delete(); + return false; + } + } + return true; // Se passa i test, mostrala! + }).toList(); + + if (docs.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))), + ); + } + + // Ordiniamo le stanze valide dalla più recente docs.sort((a, b) { Timestamp? tA = (a.data() as Map)['createdAt'] as Timestamp?; Timestamp? tB = (b.data() as Map)['createdAt'] as Timestamp?;