Auto-sync: 20260311_230000

This commit is contained in:
Paolo 2026-03-11 23:00:01 +01:00
parent 5aa831f750
commit 50f67320a5
2 changed files with 92 additions and 21 deletions

View file

@ -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<QuerySnapshot> getPublicRooms() {
return _gamesCollection
.where('status', isEqualTo: 'waiting')

View file

@ -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<LobbyScreen> createState() => _LobbyScreenState();
}
class _LobbyScreenState extends State<LobbyScreen> {
class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
final MultiplayerService _multiplayerService = MultiplayerService();
late TextEditingController _codeController;
@ -531,11 +533,14 @@ class _LobbyScreenState extends State<LobbyScreen> {
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<LobbyScreen> {
}
@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<void> _createRoom() async {
if (_isLoading) return;
@ -559,7 +585,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
);
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<LobbyScreen> {
if (snapshot.hasData && snapshot.data!.exists) {
var data = snapshot.data!.data() as Map<String, dynamic>;
if (data['status'] == 'playing') {
_roomStarted = true; // Il gioco è iniziato, non dobbiamo più cancellare la stanza!
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pop(context);
context.read<GameController>().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _isTimeMode);
@ -674,19 +701,31 @@ class _LobbyScreenState extends State<LobbyScreen> {
}
}
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<LobbyScreen> {
StreamBuilder<QuerySnapshot>(
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<LobbyScreen> {
);
}
// 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<String, dynamic>;
// 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<String, dynamic>)['createdAt'] as Timestamp?;
Timestamp? tB = (b.data() as Map<String, dynamic>)['createdAt'] as Timestamp?;