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
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) {
// Determiniamo se è una partita locale (no CPU e no Online)
bool isLocalMatch = !gameController.isOnline && !gameController.isVsCPU;
showDialog(
context: context,
builder: (ctx) => AlertDialog(
@ -80,7 +83,9 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
],
),
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)),
),
actions: [
@ -95,8 +100,10 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
onPressed: () {
// 1. Assegna la sconfitta!
StorageService.instance.addLoss();
// 1. Assegna la sconfitta SOLO se non è una partita in locale!
if (!isLocalMatch) {
StorageService.instance.addLoss();
}
// 2. Disconnette e pulisce
gameController.disconnectOnlineGame();
// 3. Chiude il dialog

View file

@ -3,6 +3,7 @@
// ===========================================================================
import 'dart:ui';
import 'dart:math'; // <--- AGGIUNTO PER IL RANDOM DELL'INVITO
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
@ -17,8 +18,9 @@ import '../../services/multiplayer_service.dart';
import '../../services/storage_service.dart';
import '../game/game_screen.dart';
import '../../widgets/cyber_border.dart';
import '../../widgets/painters.dart'; // <--- ECCO L'IMPORT MANCANTE!
import '../../widgets/painters.dart';
import 'lobby_widgets.dart';
import '../home/home_modals.dart'; // <--- AGGIUNTO PER IL DIALOG DELLE IMPOSTAZIONI
class LobbyScreen extends StatefulWidget {
final String? initialRoomCode;
@ -104,27 +106,62 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
}
}
Future<void> _createRoomAndInvite(String targetUid, String targetName) async {
if (_isLoading) return;
// --- NUOVA LOGICA: FLOW PER SFIDA DIRETTA (Allineato con HomeScreen) ---
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);
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
final rnd = Random();
String roomCode = String.fromCharCodes(Iterable.generate(5, (_) => chars.codeUnitAt(rnd.nextInt(chars.length))));
try {
String code = await _multiplayerService.createGameRoom(
_selectedRadius, _playerName, _selectedShape.name, _timeModeSetting, isPublic: _isPublicRoom
);
int gameSeed = rnd.nextInt(9999999);
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;
setState(() { _myRoomCode = code; _isLoading = false; _roomStarted = false; });
await FirebaseFirestore.instance.collection('invites').add({
'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));
_showWaitingDialog(code);
setState(() => _isLoading = false);
if (mounted) {
_showWaitingDialog(roomCode); // Usiamo il dialog interno della lobby
}
} 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 {
if (_isLoading) return;
@ -193,7 +230,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
)
)
: StreamBuilder<QuerySnapshot>(
// Interroghiamo Firebase solo per gli UID dei nostri preferiti (max 10 per limiti di Firestore)
stream: FirebaseFirestore.instance.collection('leaderboard')
.where(FieldPath.documentId, whereIn: favs.map((f) => f['uid']).take(10).toList())
.snapshots(),
@ -202,7 +238,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
return Center(child: CircularProgressIndicator(color: theme.playerBlue));
}
// Mappiamo i risultati di Firebase per un accesso rapido
Map<String, Map<String, dynamic>> liveData = {};
for (var doc in snapshot.data!.docs) {
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) {
Timestamp lastActive = liveData[uid]!['lastActive'];
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;
}
@ -245,7 +279,8 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
),
onPressed: () {
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))),
)
@ -273,6 +308,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
}
);
}
void _showWaitingDialog(String code) {
showDialog(
context: context,
@ -804,4 +840,4 @@ class FullScreenGridPainter extends CustomPainter {
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
}