Auto-sync: 20260321_110000

This commit is contained in:
Paolo 2026-03-21 11:00:05 +01:00
parent 5b99d5f0bb
commit c990085df7
4 changed files with 37 additions and 41 deletions

View file

@ -122,7 +122,7 @@ class QuestsDialog extends StatelessWidget {
// 2. DIALOGO CLASSIFICA (LEADERBOARD) CON CALLBACK SFIDA // 2. DIALOGO CLASSIFICA (LEADERBOARD) CON CALLBACK SFIDA
// =========================================================================== // ===========================================================================
class LeaderboardDialog extends StatelessWidget { class LeaderboardDialog extends StatelessWidget {
final Function(String uid, String name)? onChallenge; // <-- Aggiunto Callback per inviare i dati alla HomeScreen final Function(String uid, String name)? onChallenge;
const LeaderboardDialog({super.key, this.onChallenge}); const LeaderboardDialog({super.key, this.onChallenge});
@ -165,7 +165,8 @@ class LeaderboardDialog extends StatelessWidget {
final filteredDocs = rawDocs.where((doc) { final filteredDocs = rawDocs.where((doc) {
var data = doc.data() as Map<String, dynamic>; var data = doc.data() as Map<String, dynamic>;
String name = (data['name'] ?? '').toString().toUpperCase(); String name = (data['name'] ?? '').toString().toUpperCase();
return name != 'PAOLO'; // Nascondiamo PIPPO dalla classifica
return name != 'PIPPO';
}).toList(); }).toList();
if (filteredDocs.isEmpty) { if (filteredDocs.isEmpty) {
@ -224,7 +225,6 @@ class LeaderboardDialog extends StatelessWidget {
themeType: themeType, themeType: themeType,
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
// Chiama la funzione passata dalla HomeScreen!
if (onChallenge != null) { if (onChallenge != null) {
onChallenge!(doc.id, playerName); onChallenge!(doc.id, playerName);
} }

View file

@ -19,7 +19,6 @@ import '../../l10n/app_localizations.dart';
import '../../widgets/painters.dart'; import '../../widgets/painters.dart';
import '../../widgets/cyber_border.dart'; import '../../widgets/cyber_border.dart';
import '../game/game_screen.dart'; import '../game/game_screen.dart';
import '../multiplayer/lobby_screen.dart';
import '../multiplayer/lobby_widgets.dart'; import '../multiplayer/lobby_widgets.dart';
class HomeModals { class HomeModals {
@ -62,25 +61,16 @@ class HomeModals {
final fakeEmail = "${name.toLowerCase().replaceAll(' ', '')}@tetraq.game"; final fakeEmail = "${name.toLowerCase().replaceAll(' ', '')}@tetraq.game";
final currentUser = FirebaseAuth.instance.currentUser; final currentUser = FirebaseAuth.instance.currentUser;
// CATTURIAMO IL FANTASMA: Se siamo in un account anonimo provvisorio, ci salviamo il suo ID
final ghostUid = (currentUser != null && currentUser.isAnonymous) ? currentUser.uid : null; final ghostUid = (currentUser != null && currentUser.isAnonymous) ? currentUser.uid : null;
try { try {
if (isLogin) { if (isLogin) {
// ==========================================
// 1. TENTA IL LOGIN UFFICIALE
// ==========================================
// --- LA MOSSA DEL NINJA: ELIMINIAMO IL GHOST PRIMA DEL LOGIN ---
// Lo eliminiamo ORA perché abbiamo ancora i permessi dell'utente anonimo.
if (ghostUid != null) { if (ghostUid != null) {
await FirebaseFirestore.instance.collection('leaderboard').doc(ghostUid).delete().catchError((e) => null); await FirebaseFirestore.instance.collection('leaderboard').doc(ghostUid).delete().catchError((e) => null);
} }
// Ora effettuiamo l'accesso
await FirebaseAuth.instance.signInWithEmailAndPassword(email: fakeEmail, password: password); await FirebaseAuth.instance.signInWithEmailAndPassword(email: fakeEmail, password: password);
// Recupera i dati storici dal Cloud per sovrascrivere quelli in locale
final doc = await FirebaseFirestore.instance.collection('leaderboard').doc(FirebaseAuth.instance.currentUser!.uid).get(); final doc = await FirebaseFirestore.instance.collection('leaderboard').doc(FirebaseAuth.instance.currentUser!.uid).get();
if (doc.exists) { if (doc.exists) {
final data = doc.data() as Map<String, dynamic>; final data = doc.data() as Map<String, dynamic>;
@ -95,22 +85,16 @@ class HomeModals {
} }
} else { } else {
// ==========================================
// 2. TENTA LA REGISTRAZIONE / UPGRADE
// ==========================================
if (currentUser != null && currentUser.isAnonymous) { if (currentUser != null && currentUser.isAnonymous) {
// L'utente aveva un account anonimo, lo promuoviamo ad account con password
final credential = EmailAuthProvider.credential(email: fakeEmail, password: password); final credential = EmailAuthProvider.credential(email: fakeEmail, password: password);
await currentUser.linkWithCredential(credential); await currentUser.linkWithCredential(credential);
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Profilo Cloud protetto con successo!"), backgroundColor: Colors.green)); if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Profilo Cloud protetto con successo!"), backgroundColor: Colors.green));
} else { } else {
// Se per caso si trova senza nessun account, ne creiamo uno nuovo
await FirebaseAuth.instance.createUserWithEmailAndPassword(email: fakeEmail, password: password); await FirebaseAuth.instance.createUserWithEmailAndPassword(email: fakeEmail, password: password);
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Account creato con successo!"), backgroundColor: Colors.green)); if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Account creato con successo!"), backgroundColor: Colors.green));
} }
} }
// Salva il nome e lancia il sync
await StorageService.instance.savePlayerName(name); await StorageService.instance.savePlayerName(name);
StorageService.instance.syncLeaderboard(); StorageService.instance.syncLeaderboard();
@ -123,7 +107,6 @@ class HomeModals {
msg = "Nome già registrato!\nSe sei tu, clicca su ACCEDI."; msg = "Nome già registrato!\nSe sei tu, clicca su ACCEDI.";
} else if (e.code == 'user-not-found' || e.code == 'wrong-password' || e.code == 'invalid-credential') { } else if (e.code == 'user-not-found' || e.code == 'wrong-password' || e.code == 'invalid-credential') {
msg = "Nome o Password errati!"; msg = "Nome o Password errati!";
// Siccome avevamo cancellato il ghost prima, se il login fallisce lo ricreiamo per non perdere i dati locali
if (isLogin && ghostUid != null) StorageService.instance.syncLeaderboard(); if (isLogin && ghostUid != null) StorageService.instance.syncLeaderboard();
} else if (e.code == 'requires-recent-login') { } else if (e.code == 'requires-recent-login') {
msg = "Errore di sessione. Riavvia l'app."; msg = "Errore di sessione. Riavvia l'app.";
@ -534,7 +517,6 @@ class HomeModals {
), ),
const SizedBox(height: 25), Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), const SizedBox(height: 20), const SizedBox(height: 25), Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), const SizedBox(height: 20),
// TEMPO È ORA ESCLUSIVO PER IL MULTIPLAYER
Text("TEMPO", style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), const SizedBox(height: 10), Text("TEMPO", style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), const SizedBox(height: 10),
Row( Row(
children: [ children: [

View file

@ -106,7 +106,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
_checkClipboardForInvite(); _checkClipboardForInvite();
_listenToFavoritesOnline(); _listenToFavoritesOnline();
} else if (state == AppLifecycleState.detached) { } else if (state == AppLifecycleState.detached) {
// --- FIX BUG WHATSAPP: Rimossa l'eliminazione della stanza durante lo stato "paused" ---
_cleanupGhostRoom(); _cleanupGhostRoom();
} }
} }
@ -581,8 +580,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
onTap: () async { onTap: () async {
_debugTapCount++; _debugTapCount++;
// CHEAT LOCALE VIVO SOLO IN DEBUG MODE // CHEAT LOCALE VIVO SOLO IN DEBUG MODE (ORA CON PIPPO!)
if (kDebugMode && playerName.toUpperCase() == 'PAOLO' && _debugTapCount == 5) { if (kDebugMode && playerName.toUpperCase() == 'PIPPO' && _debugTapCount == 5) {
StorageService.instance.addXP(2000); StorageService.instance.addXP(2000);
setState(() {}); setState(() {});
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -593,7 +592,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
else if (_debugTapCount >= 7) { else if (_debugTapCount >= 7) {
_debugTapCount = 0; _debugTapCount = 0;
if (kDebugMode && playerName.toUpperCase() == 'PAOLO') { if (kDebugMode && playerName.toUpperCase() == 'PIPPO') {
Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen())); Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen()));
} else { } else {
bool isAdmin = await StorageService.instance.isUserAdmin(); bool isAdmin = await StorageService.instance.isUserAdmin();

View file

@ -7,7 +7,7 @@ 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 'package:firebase_auth/firebase_auth.dart';
import 'package:tetraq/l10n/app_localizations.dart'; // <-- IMPORT DEL DIZIONARIO! import 'package:tetraq/l10n/app_localizations.dart';
import '../../logic/game_controller.dart'; import '../../logic/game_controller.dart';
import '../../models/game_board.dart'; import '../../models/game_board.dart';
@ -16,8 +16,8 @@ import '../../core/app_colors.dart';
import '../../services/multiplayer_service.dart'; 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/painters.dart';
import '../../widgets/cyber_border.dart'; import '../../widgets/cyber_border.dart';
import '../../widgets/painters.dart'; // <--- ECCO L'IMPORT MANCANTE!
import 'lobby_widgets.dart'; import 'lobby_widgets.dart';
class LobbyScreen extends StatefulWidget { class LobbyScreen extends StatefulWidget {
@ -45,7 +45,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
String _timeModeSetting = 'fixed'; String _timeModeSetting = 'fixed';
bool _isPublicRoom = true; bool _isPublicRoom = true;
bool _roomStarted = false; bool _roomStarted = false;
@override @override
@ -220,17 +219,17 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (context) { builder: (dialogContext) {
final theme = context.watch<ThemeManager>().currentColors; final theme = dialogContext.watch<ThemeManager>().currentColors;
final themeType = context.read<ThemeManager>().currentThemeType; final themeType = dialogContext.read<ThemeManager>().currentThemeType;
final loc = AppLocalizations.of(context)!; final loc = AppLocalizations.of(context)!;
Widget dialogContent = Column( Widget dialogContent = Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
CircularProgressIndicator(color: theme.playerRed), const SizedBox(height: 25), CircularProgressIndicator(color: theme.playerRed), const SizedBox(height: 25),
Text(loc.codeHint, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))), Text(loc.codeHint, style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))),
Text(code, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 40, fontWeight: FontWeight.w900, color: theme.playerRed, letterSpacing: 8, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 10)]))), Text(code, style: getSharedTextStyle(themeType, TextStyle(fontSize: 40, fontWeight: FontWeight.w900, color: theme.playerRed, letterSpacing: 8, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 10)]))),
const SizedBox(height: 25), const SizedBox(height: 25),
Transform.rotate( Transform.rotate(
angle: themeType == AppThemeType.doodle ? 0.02 : 0, angle: themeType == AppThemeType.doodle ? 0.02 : 0,
@ -247,9 +246,9 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
child: Column( child: Column(
children: [ children: [
Icon(_isPublicRoom ? Icons.podcasts : Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12), Icon(_isPublicRoom ? Icons.podcasts : Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12),
Text(_isPublicRoom ? "Sei in Bacheca!" : "Condividi link", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))), Text(_isPublicRoom ? "Sei in Bacheca!" : "Invito inviato", textAlign: TextAlign.center, style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))),
const SizedBox(height: 8), const SizedBox(height: 8),
Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))), Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Attendi che il tuo amico accetti la sfida. Non chiudere questa finestra.", textAlign: TextAlign.center, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))),
], ],
), ),
), ),
@ -274,13 +273,13 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
return StreamBuilder<DocumentSnapshot>( return StreamBuilder<DocumentSnapshot>(
stream: _multiplayerService.listenToRoom(code), stream: _multiplayerService.listenToRoom(code),
builder: (context, snapshot) { builder: (ctx, snapshot) {
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; _roomStarted = true;
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pop(context); Navigator.pop(ctx);
context.read<GameController>().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _timeModeSetting); context.read<GameController>().startNewGame(_selectedRadius, isOnline: true, roomCode: code, isHost: true, shape: _selectedShape, timeMode: _timeModeSetting);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const GameScreen())); Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => const GameScreen()));
}); });
@ -292,7 +291,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
onPopInvoked: (didPop) { onPopInvoked: (didPop) {
if (didPop) return; if (didPop) return;
_cleanupGhostRoom(); _cleanupGhostRoom();
Navigator.pop(context); Navigator.pop(ctx);
}, },
child: Dialog( child: Dialog(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@ -305,9 +304,9 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
TextButton( TextButton(
onPressed: () { onPressed: () {
_cleanupGhostRoom(); _cleanupGhostRoom();
Navigator.pop(context); Navigator.pop(ctx);
}, },
child: Text(loc.btnCancel.toUpperCase(), style: getLobbyTextStyle(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(loc.btnCancel.toUpperCase(), style: getSharedTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))),
), ),
], ],
), ),
@ -350,7 +349,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
final themeManager = context.watch<ThemeManager>(); final themeManager = context.watch<ThemeManager>();
final themeType = themeManager.currentThemeType; final themeType = themeManager.currentThemeType;
final theme = themeManager.currentColors; final theme = themeManager.currentColors;
final loc = AppLocalizations.of(context)!; // <-- CHIAMATA AL DIZIONARIO final loc = AppLocalizations.of(context)!;
String? bgImage; String? bgImage;
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
@ -731,4 +730,20 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
), ),
); );
} }
}
class FullScreenGridPainter extends CustomPainter {
final Color gridColor;
FullScreenGridPainter(this.gridColor);
@override
void paint(Canvas canvas, Size size) {
final Paint paperGridPaint = Paint()..color = gridColor..strokeWidth = 1.0..style = PaintingStyle.stroke;
double paperStep = 20.0;
for (double i = 0; i <= size.width; i += paperStep) canvas.drawLine(Offset(i, 0), Offset(i, size.height), paperGridPaint);
for (double i = 0; i <= size.height; i += paperStep) canvas.drawLine(Offset(0, i), Offset(size.width, i), paperGridPaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
} }