Auto-sync: 20260311_230000
This commit is contained in:
parent
5aa831f750
commit
50f67320a5
2 changed files with 92 additions and 21 deletions
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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?;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue