Auto-sync: 20260320_230000

This commit is contained in:
Paolo 2026-03-20 23:00:01 +01:00
parent 931cfa0d5a
commit 209027b221
5 changed files with 73 additions and 17 deletions

View file

@ -33,7 +33,11 @@ void main() async {
); );
try { try {
// --- BUG FIX: Creiamo l'account anonimo SOLO se non c'è una sessione attiva ---
// In questo modo, una volta fatto il login, non verrai più buttato fuori al riavvio!
if (FirebaseAuth.instance.currentUser == null) {
await FirebaseAuth.instance.signInAnonymously(); await FirebaseAuth.instance.signInAnonymously();
}
} catch (e) { } catch (e) {
debugPrint("Errore Auth: $e"); debugPrint("Errore Auth: $e");
} }
@ -68,7 +72,6 @@ class TetraQApp extends StatelessWidget {
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
// Nessun modificatore const richiesto qui.
home: UpdateWrapper(child: HomeScreen()), home: UpdateWrapper(child: HomeScreen()),
); );
} }

View file

@ -89,12 +89,12 @@ class StorageService {
syncLeaderboard(); syncLeaderboard();
} }
// --- SINCRONIZZAZIONE BLINDATA: SOLO UTENTI REGISTRATI --- // --- SINCRONIZZAZIONE LEADERBOARD AGGIORNATA ---
Future<void> syncLeaderboard() async { Future<void> syncLeaderboard() async {
try { try {
final user = FirebaseAuth.instance.currentUser; final user = FirebaseAuth.instance.currentUser;
// BLOCCO TOTALE: Se non sei loggato con la password, niente database! // BLOCCO TOTALE: Se non sei loggato, niente database!
if (user == null) return; if (user == null) return;
String name = playerName; String name = playerName;
@ -102,14 +102,22 @@ class StorageService {
String targetUid = user.uid; String targetUid = user.uid;
await FirebaseFirestore.instance.collection('leaderboard').doc(targetUid).set({ // Prepara i dati base
Map<String, dynamic> dataToSave = {
'name': name, 'name': name,
'xp': totalXP, 'xp': totalXP,
'level': playerLevel, 'level': playerLevel,
'wins': wins, 'wins': wins,
'losses': losses, 'losses': losses,
'lastActive': FieldValue.serverTimestamp(), 'lastActive': FieldValue.serverTimestamp(),
}, SetOptions(merge: true)); };
// IL TRUCCO: Aggiungiamo la data di registrazione estraendola da Firebase Auth!
if (user.metadata.creationTime != null) {
dataToSave['accountCreated'] = Timestamp.fromDate(user.metadata.creationTime!);
}
await FirebaseFirestore.instance.collection('leaderboard').doc(targetUid).set(dataToSave, SetOptions(merge: true));
} catch (e) { } catch (e) {
debugPrint("Errore durante la sincronizzazione della classifica: $e"); debugPrint("Errore durante la sincronizzazione della classifica: $e");

View file

@ -57,13 +57,14 @@ class AdminScreen extends StatelessWidget {
int minutes = (playtimeSec % 3600) ~/ 60; int minutes = (playtimeSec % 3600) ~/ 60;
String playtimeStr = "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}"; String playtimeStr = "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}";
// Recupero della data di creazione dell'account
DateTime? created; DateTime? created;
if (data['accountCreated'] != null) created = (data['accountCreated'] as Timestamp).toDate(); if (data['accountCreated'] != null) created = (data['accountCreated'] as Timestamp).toDate();
DateTime? lastActive; DateTime? lastActive;
if (data['lastActive'] != null) lastActive = (data['lastActive'] as Timestamp).toDate(); if (data['lastActive'] != null) lastActive = (data['lastActive'] as Timestamp).toDate();
String createdStr = created != null ? DateFormat('dd MMM yyyy').format(created) : 'N/D'; String createdStr = created != null ? DateFormat('dd MMM yyyy - HH:mm').format(created) : 'N/D';
String lastActiveStr = lastActive != null ? DateFormat('dd MMM yyyy - HH:mm').format(lastActive) : 'N/D'; String lastActiveStr = lastActive != null ? DateFormat('dd MMM yyyy - HH:mm').format(lastActive) : 'N/D';
IconData platformIcon = Icons.device_unknown; IconData platformIcon = Icons.device_unknown;
@ -151,7 +152,6 @@ class AdminScreen extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 8.0), padding: EdgeInsets.symmetric(vertical: 8.0),
child: Divider(), child: Divider(),
), ),
// QUI È DOVE AVVENIVA IL CRASH! Ora usiamo Expanded e FittedBox
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View file

@ -60,10 +60,27 @@ class HomeModals {
} }
final fakeEmail = "${name.toLowerCase().replaceAll(' ', '')}@tetraq.game"; final fakeEmail = "${name.toLowerCase().replaceAll(' ', '')}@tetraq.game";
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;
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) {
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>;
@ -71,24 +88,50 @@ class HomeModals {
await prefs.setInt('totalXP', data['xp'] ?? 0); await prefs.setInt('totalXP', data['xp'] ?? 0);
await prefs.setInt('wins', data['wins'] ?? 0); await prefs.setInt('wins', data['wins'] ?? 0);
await prefs.setInt('losses', data['losses'] ?? 0); await prefs.setInt('losses', data['losses'] ?? 0);
}
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Bentornato $name! Dati sincronizzati."), backgroundColor: Colors.green)); if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Bentornato $name! Dati sincronizzati."), backgroundColor: Colors.green));
} else { } else {
StorageService.instance.syncLeaderboard();
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Bentornato $name! Profilo ripristinato."), backgroundColor: Colors.green));
}
} else {
// ==========================================
// 2. TENTA LA REGISTRAZIONE / UPGRADE
// ==========================================
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);
await currentUser.linkWithCredential(credential);
if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Profilo Cloud protetto con successo!"), backgroundColor: Colors.green));
} 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();
if (context.mounted) Navigator.of(dialogContext).pop(); if (context.mounted) Navigator.of(dialogContext).pop();
onSuccess(); onSuccess();
} on FirebaseAuthException catch (e) { } on FirebaseAuthException catch (e) {
String msg = "Errore di autenticazione."; String msg = "Errore di autenticazione.";
if (e.code == 'email-already-in-use') msg = "Nome occupato!\nSe è il tuo account, clicca su ACCEDI."; if (e.code == 'email-already-in-use' || e.code == 'credential-already-in-use') {
else if (e.code == 'user-not-found' || e.code == 'wrong-password' || e.code == 'invalid-credential') msg = "Nome o Password errati!"; 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') {
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();
} else if (e.code == 'requires-recent-login') {
msg = "Errore di sessione. Riavvia l'app.";
}
setStateDialog(() { errorMessage = msg; isLoadingAuth = false; }); setStateDialog(() { errorMessage = msg; isLoadingAuth = false; });
} catch (e) { } catch (e) {
setStateDialog(() { errorMessage = "Errore imprevisto: $e"; isLoadingAuth = false; }); setStateDialog(() { errorMessage = "Errore imprevisto: $e"; isLoadingAuth = false; });
if (isLogin && ghostUid != null) StorageService.instance.syncLeaderboard();
} }
} }
@ -216,7 +259,6 @@ class HomeModals {
); );
} }
// --- SELETTORE DEL TEMPO A 3 OPZIONI ---
static Widget _buildTimeOption(String label, String sub, String value, String current, ThemeColors theme, AppThemeType type, VoidCallback onTap) { static Widget _buildTimeOption(String label, String sub, String value, String current, ThemeColors theme, AppThemeType type, VoidCallback onTap) {
bool isSel = value == current; bool isSel = value == current;
return Expanded( return Expanded(

View file

@ -64,7 +64,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (FirebaseAuth.instance.currentUser == null) { // MODIFICA QUI: Invece di currentUser == null, controlliamo se il nome è vuoto!
if (StorageService.instance.playerName.isEmpty) {
HomeModals.showNameDialog(context, () { HomeModals.showNameDialog(context, () {
StorageService.instance.syncLeaderboard(); StorageService.instance.syncLeaderboard();
_listenToInvites(); _listenToInvites();
@ -80,7 +81,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
_initDeepLinks(); _initDeepLinks();
_listenToFavoritesOnline(); _listenToFavoritesOnline();
} }
void _checkThemeSafety() { void _checkThemeSafety() {
String themeStr = StorageService.instance.getTheme(); String themeStr = StorageService.instance.getTheme();
bool exists = AppThemeType.values.any((e) => e.toString() == themeStr); bool exists = AppThemeType.values.any((e) => e.toString() == themeStr);
@ -198,9 +198,10 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
late OverlayEntry entry; late OverlayEntry entry;
bool removed = false; bool removed = false;
// --- FIX OVERLAP POPUP: Più in basso (85) ---
entry = OverlayEntry( entry = OverlayEntry(
builder: (context) => Positioned( builder: (context) => Positioned(
top: MediaQuery.of(context).padding.top + 15, top: MediaQuery.of(context).padding.top + 85,
left: 20, left: 20,
right: 20, right: 20,
child: FavoriteOnlinePopup( child: FavoriteOnlinePopup(
@ -790,6 +791,8 @@ class _FavoriteOnlinePopupState extends State<FavoriteOnlinePopup> with SingleTi
position: _offsetAnimation, position: _offsetAnimation,
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
// --- FIX OVERLAP POPUP: Aggiunta Elevation Altissima (100) ---
elevation: 100,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(