Auto-sync: 20260312_140000

This commit is contained in:
Paolo 2026-03-12 14:00:01 +01:00
parent 50f67320a5
commit 384a9d7bd7
4 changed files with 204 additions and 99 deletions

View file

@ -1190,6 +1190,10 @@ PODS:
- abseil/xcprivacy (1.20240722.0) - abseil/xcprivacy (1.20240722.0)
- app_links (7.0.0): - app_links (7.0.0):
- Flutter - Flutter
- AppCheckCore (11.2.0):
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- audioplayers_darwin (0.0.1): - audioplayers_darwin (0.0.1):
- Flutter - Flutter
- BoringSSL-GRPC (0.0.37): - BoringSSL-GRPC (0.0.37):
@ -1199,32 +1203,60 @@ PODS:
- BoringSSL-GRPC/Interface (= 0.0.37) - BoringSSL-GRPC/Interface (= 0.0.37)
- BoringSSL-GRPC/Interface (0.0.37) - BoringSSL-GRPC/Interface (0.0.37)
- cloud_firestore (6.1.2): - cloud_firestore (6.1.2):
- Firebase/Firestore (= 12.8.0) - Firebase/Firestore (= 12.9.0)
- firebase_core - firebase_core
- Flutter - Flutter
- Firebase/CoreOnly (12.8.0): - Firebase/Auth (12.9.0):
- FirebaseCore (~> 12.8.0)
- Firebase/Firestore (12.8.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseFirestore (~> 12.8.0) - FirebaseAuth (~> 12.9.0)
- firebase_core (4.4.0): - Firebase/CoreOnly (12.9.0):
- Firebase/CoreOnly (= 12.8.0) - FirebaseCore (~> 12.9.0)
- Firebase/Firestore (12.9.0):
- Firebase/CoreOnly
- FirebaseFirestore (~> 12.9.0)
- firebase_app_check (0.4.1-5):
- Firebase/CoreOnly (~> 12.9.0)
- firebase_core
- FirebaseAppCheck (~> 12.9.0)
- Flutter - Flutter
- FirebaseAppCheckInterop (12.8.0) - firebase_auth (6.1.4):
- FirebaseCore (12.8.0): - Firebase/Auth (= 12.9.0)
- FirebaseCoreInternal (~> 12.8.0) - firebase_core
- Flutter
- firebase_core (4.5.0):
- Firebase/CoreOnly (= 12.9.0)
- Flutter
- FirebaseAppCheck (12.9.0):
- AppCheckCore (~> 11.0)
- FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseCore (~> 12.9.0)
- GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/UserDefaults (~> 8.1)
- FirebaseAppCheckInterop (12.9.0)
- FirebaseAuth (12.9.0):
- FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseAuthInterop (~> 12.9.0)
- FirebaseCore (~> 12.9.0)
- FirebaseCoreExtension (~> 12.9.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
- GoogleUtilities/Environment (~> 8.1)
- GTMSessionFetcher/Core (< 6.0, >= 3.4)
- RecaptchaInterop (~> 101.0)
- FirebaseAuthInterop (12.9.0)
- FirebaseCore (12.9.0):
- FirebaseCoreInternal (~> 12.9.0)
- GoogleUtilities/Environment (~> 8.1) - GoogleUtilities/Environment (~> 8.1)
- GoogleUtilities/Logger (~> 8.1) - GoogleUtilities/Logger (~> 8.1)
- FirebaseCoreExtension (12.8.0): - FirebaseCoreExtension (12.9.0):
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- FirebaseCoreInternal (12.8.0): - FirebaseCoreInternal (12.9.0):
- "GoogleUtilities/NSData+zlib (~> 8.1)" - "GoogleUtilities/NSData+zlib (~> 8.1)"
- FirebaseFirestore (12.8.0): - FirebaseFirestore (12.9.0):
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- FirebaseCoreExtension (~> 12.8.0) - FirebaseCoreExtension (~> 12.9.0)
- FirebaseFirestoreInternal (~> 12.8.0) - FirebaseFirestoreInternal (~> 12.9.0)
- FirebaseSharedSwift (~> 12.8.0) - FirebaseSharedSwift (~> 12.9.0)
- FirebaseFirestoreInternal (12.8.0): - FirebaseFirestoreInternal (12.9.0):
- abseil/algorithm (~> 1.20240722.0) - abseil/algorithm (~> 1.20240722.0)
- abseil/base (~> 1.20240722.0) - abseil/base (~> 1.20240722.0)
- abseil/container/flat_hash_map (~> 1.20240722.0) - abseil/container/flat_hash_map (~> 1.20240722.0)
@ -1233,22 +1265,38 @@ PODS:
- abseil/strings/strings (~> 1.20240722.0) - abseil/strings/strings (~> 1.20240722.0)
- abseil/time (~> 1.20240722.0) - abseil/time (~> 1.20240722.0)
- abseil/types (~> 1.20240722.0) - abseil/types (~> 1.20240722.0)
- FirebaseAppCheckInterop (~> 12.8.0) - FirebaseAppCheckInterop (~> 12.9.0)
- FirebaseCore (~> 12.8.0) - FirebaseCore (~> 12.9.0)
- "gRPC-C++ (~> 1.69.0)" - "gRPC-C++ (~> 1.69.0)"
- gRPC-Core (~> 1.69.0) - gRPC-Core (~> 1.69.0)
- leveldb-library (~> 1.22) - leveldb-library (~> 1.22)
- nanopb (~> 3.30910.0) - nanopb (~> 3.30910.0)
- FirebaseSharedSwift (12.8.0) - FirebaseSharedSwift (12.9.0)
- Flutter (1.0.0) - Flutter (1.0.0)
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.0): - GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.0): - GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment - GoogleUtilities/Environment
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.0)": - "GoogleUtilities/NSData+zlib (8.1.0)":
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.0) - GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- "gRPC-C++ (1.69.0)": - "gRPC-C++ (1.69.0)":
- "gRPC-C++/Implementation (= 1.69.0)" - "gRPC-C++/Implementation (= 1.69.0)"
- "gRPC-C++/Interface (= 1.69.0)" - "gRPC-C++/Interface (= 1.69.0)"
@ -1341,12 +1389,15 @@ PODS:
- gRPC-Core/Privacy (= 1.69.0) - gRPC-Core/Privacy (= 1.69.0)
- gRPC-Core/Interface (1.69.0) - gRPC-Core/Interface (1.69.0)
- gRPC-Core/Privacy (1.69.0) - gRPC-Core/Privacy (1.69.0)
- GTMSessionFetcher/Core (5.1.0)
- leveldb-library (1.22.6) - leveldb-library (1.22.6)
- nanopb (3.30910.0): - nanopb (3.30910.0):
- nanopb/decode (= 3.30910.0) - nanopb/decode (= 3.30910.0)
- nanopb/encode (= 3.30910.0) - nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0) - nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0) - nanopb/encode (3.30910.0)
- PromisesObjC (2.4.0)
- RecaptchaInterop (101.0.0)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
@ -1357,6 +1408,8 @@ DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`) - app_links (from `.symlinks/plugins/app_links/ios`)
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
- firebase_app_check (from `.symlinks/plugins/firebase_app_check/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
@ -1365,9 +1418,13 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- abseil - abseil
- AppCheckCore
- BoringSSL-GRPC - BoringSSL-GRPC
- Firebase - Firebase
- FirebaseAppCheck
- FirebaseAppCheckInterop - FirebaseAppCheckInterop
- FirebaseAuth
- FirebaseAuthInterop
- FirebaseCore - FirebaseCore
- FirebaseCoreExtension - FirebaseCoreExtension
- FirebaseCoreInternal - FirebaseCoreInternal
@ -1377,8 +1434,11 @@ SPEC REPOS:
- GoogleUtilities - GoogleUtilities
- "gRPC-C++" - "gRPC-C++"
- gRPC-Core - gRPC-Core
- GTMSessionFetcher
- leveldb-library - leveldb-library
- nanopb - nanopb
- PromisesObjC
- RecaptchaInterop
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links: app_links:
@ -1387,6 +1447,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/audioplayers_darwin/ios" :path: ".symlinks/plugins/audioplayers_darwin/ios"
cloud_firestore: cloud_firestore:
:path: ".symlinks/plugins/cloud_firestore/ios" :path: ".symlinks/plugins/cloud_firestore/ios"
firebase_app_check:
:path: ".symlinks/plugins/firebase_app_check/ios"
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core: firebase_core:
:path: ".symlinks/plugins/firebase_core/ios" :path: ".symlinks/plugins/firebase_core/ios"
Flutter: Flutter:
@ -1399,24 +1463,33 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
abseil: a05cc83bf02079535e17169a73c5be5ba47f714b abseil: a05cc83bf02079535e17169a73c5be5ba47f714b
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8 app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab
BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508 BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508
cloud_firestore: 4bd00c3464706d9e09dabac0bb8e9610456109f5 cloud_firestore: 81f6c428ecee874dc3808afe0e0c48a87beb5bdf
Firebase: 9a58fdbc9d8655ed7b79a19cf9690bb007d3d46d Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
firebase_core: ee30637e6744af8e0c12a6a1e8a9718506ec2398 firebase_app_check: 33f1df6830ec8ebadee0db0120956c44a65c7213
FirebaseAppCheckInterop: ba3dc604a89815379e61ec2365101608d365cf7d firebase_auth: fecf9fe293464b52063f5f2a7110e63ff2ab3403
FirebaseCore: 0dbad74bda10b8fb9ca34ad8f375fb9dd3ebef7c firebase_core: afac1aac13c931e0401c7e74ed1276112030efab
FirebaseCoreExtension: 6605938d51f765d8b18bfcafd2085276a252bee2 FirebaseAppCheck: 94dae4d9bb682bdef85a778b0c1024a4613f1e89
FirebaseCoreInternal: fe5fa466aeb314787093a7dce9f0beeaad5a2a21 FirebaseAppCheckInterop: 4bade10286cc977e516f75d2d8312cbdfa534789
FirebaseFirestore: 67f23000ca238ccbab79127ed59636a9a2689e74 FirebaseAuth: 3a39f6436c21ebfd7919b698228b4f89ff94c23b
FirebaseFirestoreInternal: a0e7382af3d208898dcd1d4d52d8a7870632e881 FirebaseAuthInterop: f8f6ff72dc24621906497fbe5cf3c42ee815e59c
FirebaseSharedSwift: f57ed48f4542b2d7eb4738f4f23ba443f78b3780 FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8
FirebaseCoreExtension: e911052d59cd0da237a45d706fc0f81654f035c1
FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72
FirebaseFirestore: d8b76ca1feb4ca0b0f078c45f7d1bd8014a49ef1
FirebaseFirestoreInternal: 02341a9ba87f6309227b04685022a5e16307bbf7
FirebaseSharedSwift: 9d2fa84a46676302b89dbd5e6e62bce2fe376909
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
"gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8 "gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8
gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330 gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb

View file

@ -912,10 +912,10 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
final docs = snapshot.data!.docs; final docs = snapshot.data!.docs;
return ListView.builder( return ListView.builder(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
itemCount: docs.length, // <--- ECCO LA RIGA MAGICA AGGIUNTA!
itemBuilder: (context, index) { itemBuilder: (context, index) {
var data = docs[index].data() as Map<String, dynamic>; var data = docs[index].data() as Map<String, dynamic>;
// Ora controlliamo se l'ID del documento su Firebase è uguale al nostro ID segreto!
String? myUid = FirebaseAuth.instance.currentUser?.uid; String? myUid = FirebaseAuth.instance.currentUser?.uid;
bool isMe = docs[index].id == myUid; bool isMe = docs[index].id == myUid;

View file

@ -411,7 +411,7 @@ class _NeonActionButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) { if (themeType == AppThemeType.doodle) {
double tilt = (label == "UNISCITI") ? -0.015 : 0.02; double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02;
return Transform.rotate( return Transform.rotate(
angle: tilt, angle: tilt,
child: GestureDetector( child: GestureDetector(
@ -428,7 +428,13 @@ class _NeonActionButton extends StatelessWidget {
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)], boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)],
), ),
child: Center( child: Center(
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))), child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))),
),
),
), ),
), ),
), ),
@ -449,7 +455,13 @@ class _NeonActionButton extends StatelessWidget {
], ],
), ),
child: Center( child: Center(
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))), child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))),
),
),
), ),
), ),
); );
@ -512,7 +524,6 @@ 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;
@ -530,17 +541,20 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
String? _myRoomCode; String? _myRoomCode;
String _playerName = ''; String _playerName = '';
// Variabile per gestire l'effetto "sipario"
bool _isCreatingRoom = false;
int _selectedRadius = 4; int _selectedRadius = 4;
ArenaShape _selectedShape = ArenaShape.classic; ArenaShape _selectedShape = ArenaShape.classic;
bool _isTimeMode = true; bool _isTimeMode = true;
bool _isPublicRoom = true; bool _isPublicRoom = true;
bool _roomStarted = false; // Flag per capire se il gioco è effettivamente iniziato bool _roomStarted = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); // Attiviamo la sentinella WidgetsBinding.instance.addObserver(this);
_codeController = TextEditingController(); _codeController = TextEditingController();
_playerName = StorageService.instance.playerName; _playerName = StorageService.instance.playerName;
@ -553,13 +567,12 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); // Rimuoviamo la sentinella WidgetsBinding.instance.removeObserver(this);
_cleanupGhostRoom(); // Se l'utente chiude la schermata, spazziamo via la stanza _cleanupGhostRoom();
_codeController.dispose(); _codeController.dispose();
super.dispose(); super.dispose();
} }
// Intercetta quando l'app viene messa in background o chiusa!
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) {
@ -567,11 +580,10 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
} }
} }
// La funzione "Spazzino"
void _cleanupGhostRoom() { void _cleanupGhostRoom() {
if (_myRoomCode != null && !_roomStarted) { if (_myRoomCode != null && !_roomStarted) {
FirebaseFirestore.instance.collection('games').doc(_myRoomCode).delete(); FirebaseFirestore.instance.collection('games').doc(_myRoomCode).delete();
_myRoomCode = null; // Evitiamo che venga chiamata due volte _myRoomCode = null;
} }
} }
@ -692,7 +704,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
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! _roomStarted = true;
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);
@ -701,12 +713,11 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
} }
} }
// NUOVO: PopScope intercetta lo swipe indietro e il tasto back di Android
return PopScope( return PopScope(
canPop: false, canPop: false,
onPopInvoked: (didPop) { onPopInvoked: (didPop) {
if (didPop) return; if (didPop) return;
_cleanupGhostRoom(); // Spazza via la stanza se l'utente striscia per tornare indietro _cleanupGhostRoom();
Navigator.pop(context); Navigator.pop(context);
}, },
child: Dialog( child: Dialog(
@ -719,7 +730,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
const SizedBox(height: 20), const SizedBox(height: 20),
TextButton( TextButton(
onPressed: () { onPressed: () {
_cleanupGhostRoom(); // Spazza via la stanza se clicca ANNULLA _cleanupGhostRoom();
Navigator.pop(context); 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)]))),
@ -746,9 +757,9 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
bool isChaosUnlocked = true; bool isChaosUnlocked = true;
Color doodlePenColor = const Color(0xFF00008B); Color doodlePenColor = const Color(0xFF00008B);
// --- PANNELLO IMPOSTAZIONI STANZA ---
Widget hostPanel = Transform.rotate( Widget hostPanel = Transform.rotate(
angle: themeType == AppThemeType.doodle ? 0.01 : 0, angle: themeType == AppThemeType.doodle ? 0.01 : 0,
child: Container( child: Container(
@ -823,7 +834,9 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
Widget uiContent = SafeArea( Widget uiContent = SafeArea(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0), physics: const BouncingScrollPhysics(),
// Padding inferiore aumentato a 60 per evitare il taglio dei pulsanti
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 10.0, bottom: MediaQuery.of(context).padding.bottom + 60.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
@ -850,49 +863,76 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
), ),
], ],
), ),
const SizedBox(height: 20),
hostPanel,
const SizedBox(height: 15),
_NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType),
const SizedBox(height: 20),
Row(
children: [
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("OPPURE", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
],
),
const SizedBox(height: 20), const SizedBox(height: 20),
Transform.rotate( // --- L'EFFETTO SIPARIO CON ANIMATED SIZE ---
angle: themeType == AppThemeType.doodle ? 0.02 : 0, AnimatedSize(
child: Container( duration: const Duration(milliseconds: 300),
decoration: themeType == AppThemeType.doodle ? BoxDecoration( curve: Curves.easeInOut,
color: Colors.white, alignment: Alignment.topCenter,
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), bottomRight: Radius.circular(20), topRight: Radius.circular(5), bottomLeft: Radius.circular(5)), child: _isCreatingRoom
border: Border.all(color: theme.text, width: 2.5), ? Column( // MENU CREAZIONE (Aperto)
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(5, 5), blurRadius: 0)], crossAxisAlignment: CrossAxisAlignment.stretch,
) : BoxDecoration( children: [
boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 15, spreadRadius: 1)] hostPanel,
), const SizedBox(height: 15),
child: TextField( Row(
controller: _codeController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5, children: [
style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])), Expanded( // Entrambi in un Expanded "liscio" si dividono il 50% di spazio
decoration: InputDecoration( child: _NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType),
contentPadding: const EdgeInsets.symmetric(vertical: 12), ),
hintText: "CODICE", hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "", const SizedBox(width: 10),
filled: themeType != AppThemeType.doodle, Expanded( // Entrambi in un Expanded "liscio" si dividono il 50% di spazio
fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05), child: _NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType),
enabledBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2.0), borderRadius: BorderRadius.circular(15)), ),
focusedBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3.0), borderRadius: BorderRadius.circular(15)), ],
), ),
), ],
)
: Column( // MENU BASE (Chiuso)
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType),
const SizedBox(height: 20),
Row(
children: [
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("OPPURE", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
],
),
const SizedBox(height: 20),
Transform.rotate(
angle: themeType == AppThemeType.doodle ? 0.02 : 0,
child: Container(
decoration: themeType == AppThemeType.doodle ? BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), bottomRight: Radius.circular(20), topRight: Radius.circular(5), bottomLeft: Radius.circular(5)),
border: Border.all(color: theme.text, width: 2.5),
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(5, 5), blurRadius: 0)],
) : BoxDecoration(
boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 15, spreadRadius: 1)]
),
child: TextField(
controller: _codeController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])),
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(vertical: 12),
hintText: "CODICE", hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "",
filled: themeType != AppThemeType.doodle,
fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05),
enabledBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2.0), borderRadius: BorderRadius.circular(15)),
focusedBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3.0), borderRadius: BorderRadius.circular(15)),
),
),
),
),
const SizedBox(height: 15),
_NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType),
],
), ),
), ),
const SizedBox(height: 15),
_NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType),
const SizedBox(height: 25), const SizedBox(height: 25),
Row( Row(
@ -919,20 +959,14 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
); );
} }
DateTime now = DateTime.now(); // Tempo attuale DateTime now = DateTime.now();
String? myUid = FirebaseAuth.instance.currentUser?.uid; String? myUid = FirebaseAuth.instance.currentUser?.uid;
// FILTRO LOCALE E SCADENZA (15 MINUTI)
var docs = snapshot.data!.docs.where((doc) { var docs = snapshot.data!.docs.where((doc) {
var data = doc.data() as Map<String, dynamic>; var data = doc.data() as Map<String, dynamic>;
// 1. Deve essere pubblica
if (data['isPublic'] != true) return false; if (data['isPublic'] != true) return false;
// 2. Non devo vedere la mia stessa stanza
if (data['hostUid'] != null && data['hostUid'] == myUid) return false; 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?; Timestamp? createdAt = data['createdAt'] as Timestamp?;
if (createdAt != null) { if (createdAt != null) {
int ageInMinutes = now.difference(createdAt.toDate()).inMinutes; int ageInMinutes = now.difference(createdAt.toDate()).inMinutes;
@ -941,7 +975,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
return false; return false;
} }
} }
return true; // Se passa i test, mostrala! return true;
}).toList(); }).toList();
if (docs.isEmpty) { if (docs.isEmpty) {
@ -951,7 +985,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
); );
} }
// 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?;
@ -972,7 +1005,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
String shapeStr = data['shape'] ?? 'classic'; String shapeStr = data['shape'] ?? 'classic';
bool time = data['timeMode'] ?? true; bool time = data['timeMode'] ?? true;
// Formattazione del nome della forma
String prettyShape = "Rombo"; String prettyShape = "Rombo";
if (shapeStr == 'cross') prettyShape = "Croce"; if (shapeStr == 'cross') prettyShape = "Croce";
else if (shapeStr == 'donut') prettyShape = "Buco"; else if (shapeStr == 'donut') prettyShape = "Buco";

View file

@ -1,7 +1,7 @@
name: tetraq name: tetraq
description: A new Flutter project. description: A new Flutter project.
publish_to: 'none' publish_to: 'none'
version: 1.1.3+5 version: 1.1.4+6
environment: environment:
sdk: ^3.10.7 sdk: ^3.10.7