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 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
class MultiplayerService { class MultiplayerService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance; final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;
CollectionReference get _gamesCollection => _firestore.collection('games'); CollectionReference get _gamesCollection => _firestore.collection('games');
@ -25,6 +27,7 @@ class MultiplayerService {
'moves': [], 'moves': [],
'seed': randomSeed, 'seed': randomSeed,
'hostName': hostName, 'hostName': hostName,
'hostUid': _auth.currentUser?.uid, // NUOVO: Salviamo l'ID univoco del creatore
'guestName': '', 'guestName': '',
'shape': shapeName, 'shape': shapeName,
'timeMode': isTimeMode, 'timeMode': isTimeMode,
@ -52,7 +55,6 @@ class MultiplayerService {
return null; return null;
} }
// QUERY BLINDATA: Chiede a Firebase solo le stanze waiting E pubbliche
Stream<QuerySnapshot> getPublicRooms() { Stream<QuerySnapshot> getPublicRooms() {
return _gamesCollection return _gamesCollection
.where('status', isEqualTo: 'waiting') .where('status', isEqualTo: 'waiting')

View file

@ -6,6 +6,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'dart:math' as math; import 'dart:math' as math;
import '../../logic/game_controller.dart'; import '../../logic/game_controller.dart';
import '../../models/game_board.dart'; import '../../models/game_board.dart';
@ -511,6 +512,7 @@ class _CyberBorderPainter extends CustomPainter {
bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue; bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue;
} }
// NUOVO: Aggiunto WidgetsBindingObserver per intercettare l'app in background
class LobbyScreen extends StatefulWidget { class LobbyScreen extends StatefulWidget {
final String? initialRoomCode; final String? initialRoomCode;
@ -520,7 +522,7 @@ class LobbyScreen extends StatefulWidget {
State<LobbyScreen> createState() => _LobbyScreenState(); State<LobbyScreen> createState() => _LobbyScreenState();
} }
class _LobbyScreenState extends State<LobbyScreen> { class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
final MultiplayerService _multiplayerService = MultiplayerService(); final MultiplayerService _multiplayerService = MultiplayerService();
late TextEditingController _codeController; late TextEditingController _codeController;
@ -531,11 +533,14 @@ class _LobbyScreenState extends State<LobbyScreen> {
int _selectedRadius = 4; int _selectedRadius = 4;
ArenaShape _selectedShape = ArenaShape.classic; ArenaShape _selectedShape = ArenaShape.classic;
bool _isTimeMode = true; 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 @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); // Attiviamo la sentinella
_codeController = TextEditingController(); _codeController = TextEditingController();
_playerName = StorageService.instance.playerName; _playerName = StorageService.instance.playerName;
@ -547,7 +552,28 @@ class _LobbyScreenState extends State<LobbyScreen> {
} }
@override @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 { Future<void> _createRoom() async {
if (_isLoading) return; if (_isLoading) return;
@ -559,7 +585,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
); );
if (!mounted) return; if (!mounted) return;
setState(() { _myRoomCode = code; _isLoading = false; }); setState(() { _myRoomCode = code; _isLoading = false; _roomStarted = false; });
if (!_isPublicRoom) { if (!_isPublicRoom) {
_multiplayerService.shareInviteLink(code); _multiplayerService.shareInviteLink(code);
@ -666,6 +692,7 @@ class _LobbyScreenState extends State<LobbyScreen> {
if (snapshot.hasData && snapshot.data!.exists) { if (snapshot.hasData && snapshot.data!.exists) {
var data = snapshot.data!.data() as Map<String, dynamic>; var data = snapshot.data!.data() as Map<String, dynamic>;
if (data['status'] == 'playing') { if (data['status'] == 'playing') {
_roomStarted = true; // Il gioco è iniziato, non dobbiamo più cancellare la stanza!
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pop(context); Navigator.pop(context);
context.read<GameController>().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _isTimeMode); context.read<GameController>().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _isTimeMode);
@ -674,7 +701,15 @@ class _LobbyScreenState extends State<LobbyScreen> {
} }
} }
return Dialog( // 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, backgroundColor: Colors.transparent,
insetPadding: const EdgeInsets.all(20), insetPadding: const EdgeInsets.all(20),
child: Column( child: Column(
@ -683,11 +718,15 @@ class _LobbyScreenState extends State<LobbyScreen> {
dialogContent, dialogContent,
const SizedBox(height: 20), const SizedBox(height: 20),
TextButton( TextButton(
onPressed: () { FirebaseFirestore.instance.collection('games').doc(code).delete(); Navigator.pop(context); }, 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)]))), 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>( StreamBuilder<QuerySnapshot>(
stream: _multiplayerService.getPublicRooms(), stream: _multiplayerService.getPublicRooms(),
builder: (context, snapshot) { builder: (context, snapshot) {
// RIMOZIONE DELL'ERRORE ROSSO DALLA SCHERMATA
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return Padding(padding: const EdgeInsets.all(20), child: Center(child: CircularProgressIndicator(color: theme.playerBlue))); 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 DateTime now = DateTime.now(); // Tempo attuale
var docs = snapshot.data!.docs; 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) { docs.sort((a, b) {
Timestamp? tA = (a.data() as Map<String, dynamic>)['createdAt'] as Timestamp?; Timestamp? tA = (a.data() as Map<String, dynamic>)['createdAt'] as Timestamp?;
Timestamp? tB = (b.data() as Map<String, dynamic>)['createdAt'] as Timestamp?; Timestamp? tB = (b.data() as Map<String, dynamic>)['createdAt'] as Timestamp?;