diff --git a/lib/main.dart b/lib/main.dart index fd47b5a..0c30402 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -33,7 +33,11 @@ void main() async { ); try { - await FirebaseAuth.instance.signInAnonymously(); + // --- 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(); + } } catch (e) { debugPrint("Errore Auth: $e"); } @@ -68,7 +72,6 @@ class TetraQApp extends StatelessWidget { localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, - // Nessun modificatore const richiesto qui. home: UpdateWrapper(child: HomeScreen()), ); } diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 38802b2..f404a38 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -89,12 +89,12 @@ class StorageService { syncLeaderboard(); } - // --- SINCRONIZZAZIONE BLINDATA: SOLO UTENTI REGISTRATI --- + // --- SINCRONIZZAZIONE LEADERBOARD AGGIORNATA --- Future syncLeaderboard() async { try { 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; String name = playerName; @@ -102,14 +102,22 @@ class StorageService { String targetUid = user.uid; - await FirebaseFirestore.instance.collection('leaderboard').doc(targetUid).set({ + // Prepara i dati base + Map dataToSave = { 'name': name, 'xp': totalXP, 'level': playerLevel, 'wins': wins, 'losses': losses, '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) { debugPrint("Errore durante la sincronizzazione della classifica: $e"); diff --git a/lib/ui/admin/admin_screen.dart b/lib/ui/admin/admin_screen.dart index a73d702..947b703 100644 --- a/lib/ui/admin/admin_screen.dart +++ b/lib/ui/admin/admin_screen.dart @@ -57,13 +57,14 @@ class AdminScreen extends StatelessWidget { int minutes = (playtimeSec % 3600) ~/ 60; String playtimeStr = "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}"; + // Recupero della data di creazione dell'account DateTime? created; if (data['accountCreated'] != null) created = (data['accountCreated'] as Timestamp).toDate(); DateTime? lastActive; 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'; IconData platformIcon = Icons.device_unknown; @@ -151,7 +152,6 @@ class AdminScreen extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), - // QUI È DOVE AVVENIVA IL CRASH! Ora usiamo Expanded e FittedBox Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/ui/home/home_modals.dart b/lib/ui/home/home_modals.dart index 064846d..c01922f 100644 --- a/lib/ui/home/home_modals.dart +++ b/lib/ui/home/home_modals.dart @@ -60,10 +60,27 @@ class HomeModals { } 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 { 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); + + // Recupera i dati storici dal Cloud per sovrascrivere quelli in locale final doc = await FirebaseFirestore.instance.collection('leaderboard').doc(FirebaseAuth.instance.currentUser!.uid).get(); if (doc.exists) { final data = doc.data() as Map; @@ -71,24 +88,50 @@ class HomeModals { await prefs.setInt('totalXP', data['xp'] ?? 0); await prefs.setInt('wins', data['wins'] ?? 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)); + } else { + StorageService.instance.syncLeaderboard(); + if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Bentornato $name! Profilo ripristinato."), backgroundColor: Colors.green)); } - if (context.mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Bentornato $name! Dati sincronizzati."), backgroundColor: Colors.green)); + } else { - 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)); + // ========================================== + // 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); + 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); + StorageService.instance.syncLeaderboard(); + if (context.mounted) Navigator.of(dialogContext).pop(); onSuccess(); } on FirebaseAuthException catch (e) { String msg = "Errore di autenticazione."; - if (e.code == 'email-already-in-use') msg = "Nome occupato!\nSe è il tuo account, clicca su ACCEDI."; - else if (e.code == 'user-not-found' || e.code == 'wrong-password' || e.code == 'invalid-credential') msg = "Nome o Password errati!"; + if (e.code == 'email-already-in-use' || e.code == 'credential-already-in-use') { + 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; }); } catch (e) { 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) { bool isSel = value == current; return Expanded( diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 3bdb984..afdf21a 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -64,7 +64,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver { super.initState(); WidgetsBinding.instance.addObserver(this); 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, () { StorageService.instance.syncLeaderboard(); _listenToInvites(); @@ -80,7 +81,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { _initDeepLinks(); _listenToFavoritesOnline(); } - void _checkThemeSafety() { String themeStr = StorageService.instance.getTheme(); bool exists = AppThemeType.values.any((e) => e.toString() == themeStr); @@ -198,9 +198,10 @@ class _HomeScreenState extends State with WidgetsBindingObserver { late OverlayEntry entry; bool removed = false; + // --- FIX OVERLAP POPUP: Più in basso (85) --- entry = OverlayEntry( builder: (context) => Positioned( - top: MediaQuery.of(context).padding.top + 15, + top: MediaQuery.of(context).padding.top + 85, left: 20, right: 20, child: FavoriteOnlinePopup( @@ -790,6 +791,8 @@ class _FavoriteOnlinePopupState extends State with SingleTi position: _offsetAnimation, child: Material( color: Colors.transparent, + // --- FIX OVERLAP POPUP: Aggiunta Elevation Altissima (100) --- + elevation: 100, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration(