Auto-sync: 20260324_150000

This commit is contained in:
Paolo 2026-03-24 15:00:01 +01:00
parent 027c41a75c
commit 88f1520b5b
4 changed files with 64 additions and 21 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
ios/.DS_Store vendored

Binary file not shown.

View file

@ -57,8 +57,11 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
@override @override
void dispose() { _blinkController.dispose(); super.dispose(); } void dispose() { _blinkController.dispose(); super.dispose(); }
// --- NUOVO DIALOG: CONFERMA USCITA (ANTI-FUGA) --- // --- DIALOG: CONFERMA USCITA (ANTI-FUGA) AGGIORNATO ---
void _showExitConfirmationDialog(BuildContext context, GameController gameController, ThemeColors theme, AppThemeType themeType) { void _showExitConfirmationDialog(BuildContext context, GameController gameController, ThemeColors theme, AppThemeType themeType) {
// Determiniamo se è una partita locale (no CPU e no Online)
bool isLocalMatch = !gameController.isOnline && !gameController.isVsCPU;
showDialog( showDialog(
context: context, context: context,
builder: (ctx) => AlertDialog( builder: (ctx) => AlertDialog(
@ -80,7 +83,9 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
], ],
), ),
content: Text( content: Text(
"Se esci ora, la partita verrà registrata automaticamente come una SCONFITTA.\n\nSei sicuro di voler fuggire?", isLocalMatch
? "Sei sicuro di voler interrompere la partita locale in corso?" // Testo per partite in locale
: "Se esci ora, la partita verrà registrata automaticamente come una SCONFITTA.\n\nSei sicuro di voler fuggire?", // Testo minaccioso per partite classificate
style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 15, height: 1.4)), style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 15, height: 1.4)),
), ),
actions: [ actions: [
@ -95,8 +100,10 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
), ),
onPressed: () { onPressed: () {
// 1. Assegna la sconfitta! // 1. Assegna la sconfitta SOLO se non è una partita in locale!
StorageService.instance.addLoss(); if (!isLocalMatch) {
StorageService.instance.addLoss();
}
// 2. Disconnette e pulisce // 2. Disconnette e pulisce
gameController.disconnectOnlineGame(); gameController.disconnectOnlineGame();
// 3. Chiude il dialog // 3. Chiude il dialog

View file

@ -3,6 +3,7 @@
// =========================================================================== // ===========================================================================
import 'dart:ui'; import 'dart:ui';
import 'dart:math'; // <--- AGGIUNTO PER IL RANDOM DELL'INVITO
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';
@ -17,8 +18,9 @@ import '../../services/multiplayer_service.dart';
import '../../services/storage_service.dart'; import '../../services/storage_service.dart';
import '../game/game_screen.dart'; import '../game/game_screen.dart';
import '../../widgets/cyber_border.dart'; import '../../widgets/cyber_border.dart';
import '../../widgets/painters.dart'; // <--- ECCO L'IMPORT MANCANTE! import '../../widgets/painters.dart';
import 'lobby_widgets.dart'; import 'lobby_widgets.dart';
import '../home/home_modals.dart'; // <--- AGGIUNTO PER IL DIALOG DELLE IMPOSTAZIONI
class LobbyScreen extends StatefulWidget { class LobbyScreen extends StatefulWidget {
final String? initialRoomCode; final String? initialRoomCode;
@ -104,27 +106,62 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
} }
} }
Future<void> _createRoomAndInvite(String targetUid, String targetName) async { // --- NUOVA LOGICA: FLOW PER SFIDA DIRETTA (Allineato con HomeScreen) ---
if (_isLoading) return; void _startDirectChallengeFlow(String targetUid, String targetName) {
HomeModals.showChallengeSetupDialog(
context,
targetName,
(int radius, ArenaShape shape, String timeMode) {
_executeSendChallenge(targetUid, targetName, radius, shape, timeMode);
}
);
}
Future<void> _executeSendChallenge(String targetUid, String targetName, int radius, ArenaShape shape, String timeMode) async {
setState(() => _isLoading = true); setState(() => _isLoading = true);
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
final rnd = Random();
String roomCode = String.fromCharCodes(Iterable.generate(5, (_) => chars.codeUnitAt(rnd.nextInt(chars.length))));
try { try {
String code = await _multiplayerService.createGameRoom( int gameSeed = rnd.nextInt(9999999);
_selectedRadius, _playerName, _selectedShape.name, _timeModeSetting, isPublic: _isPublicRoom
);
await _multiplayerService.sendInvite(targetUid, code, _playerName); await FirebaseFirestore.instance.collection('games').doc(roomCode).set({
'status': 'waiting',
'hostName': StorageService.instance.playerName,
'hostUid': FirebaseAuth.instance.currentUser?.uid,
'radius': radius,
'shape': shape.name,
'timeMode': timeMode,
'isPublic': false,
'createdAt': FieldValue.serverTimestamp(),
'players': [FirebaseAuth.instance.currentUser?.uid],
'turn': 0,
'moves': [],
'seed': gameSeed,
});
if (!mounted) return; await FirebaseFirestore.instance.collection('invites').add({
setState(() { _myRoomCode = code; _isLoading = false; _roomStarted = false; }); 'toUid': targetUid,
'fromName': StorageService.instance.playerName,
'roomCode': roomCode,
'timestamp': FieldValue.serverTimestamp(),
});
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Sfida inviata a $targetName!"), backgroundColor: Colors.green)); setState(() => _isLoading = false);
_showWaitingDialog(code);
if (mounted) {
_showWaitingDialog(roomCode); // Usiamo il dialog interno della lobby
}
} catch (e) { } catch (e) {
if (mounted) { setState(() => _isLoading = false); _showError("Errore durante la creazione della partita."); } setState(() => _isLoading = false);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Errore: $e", style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red));
}
} }
} }
// -----------------------------------------------------------------------
Future<void> _joinRoomByCode(String code) async { Future<void> _joinRoomByCode(String code) async {
if (_isLoading) return; if (_isLoading) return;
@ -193,7 +230,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
) )
) )
: StreamBuilder<QuerySnapshot>( : StreamBuilder<QuerySnapshot>(
// Interroghiamo Firebase solo per gli UID dei nostri preferiti (max 10 per limiti di Firestore)
stream: FirebaseFirestore.instance.collection('leaderboard') stream: FirebaseFirestore.instance.collection('leaderboard')
.where(FieldPath.documentId, whereIn: favs.map((f) => f['uid']).take(10).toList()) .where(FieldPath.documentId, whereIn: favs.map((f) => f['uid']).take(10).toList())
.snapshots(), .snapshots(),
@ -202,7 +238,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
return Center(child: CircularProgressIndicator(color: theme.playerBlue)); return Center(child: CircularProgressIndicator(color: theme.playerBlue));
} }
// Mappiamo i risultati di Firebase per un accesso rapido
Map<String, Map<String, dynamic>> liveData = {}; Map<String, Map<String, dynamic>> liveData = {};
for (var doc in snapshot.data!.docs) { for (var doc in snapshot.data!.docs) {
liveData[doc.id] = doc.data() as Map<String, dynamic>; liveData[doc.id] = doc.data() as Map<String, dynamic>;
@ -219,7 +254,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
if (liveData.containsKey(uid) && liveData[uid]!['lastActive'] != null) { if (liveData.containsKey(uid) && liveData[uid]!['lastActive'] != null) {
Timestamp lastActive = liveData[uid]!['lastActive']; Timestamp lastActive = liveData[uid]!['lastActive'];
int diffInSeconds = DateTime.now().difference(lastActive.toDate()).inSeconds; int diffInSeconds = DateTime.now().difference(lastActive.toDate()).inSeconds;
// Se ha fatto un'azione negli ultimi 3 minuti, lo consideriamo online
if (diffInSeconds.abs() < 180) isOnline = true; if (diffInSeconds.abs() < 180) isOnline = true;
} }
@ -245,7 +279,8 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
), ),
onPressed: () { onPressed: () {
Navigator.pop(ctx); Navigator.pop(ctx);
_createRoomAndInvite(uid, name); // --- ORA USA IL FLUSSO SICURO CON LE IMPOSTAZIONI ---
_startDirectChallengeFlow(uid, name);
}, },
child: Text("SFIDA", style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), child: Text("SFIDA", style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
) )
@ -273,6 +308,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
} }
); );
} }
void _showWaitingDialog(String code) { void _showWaitingDialog(String code) {
showDialog( showDialog(
context: context, context: context,