Auto-sync: 20260320_230000
This commit is contained in:
parent
931cfa0d5a
commit
209027b221
5 changed files with 73 additions and 17 deletions
|
|
@ -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()),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,12 +89,12 @@ class StorageService {
|
|||
syncLeaderboard();
|
||||
}
|
||||
|
||||
// --- SINCRONIZZAZIONE BLINDATA: SOLO UTENTI REGISTRATI ---
|
||||
// --- SINCRONIZZAZIONE LEADERBOARD AGGIORNATA ---
|
||||
Future<void> 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<String, dynamic> 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");
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic>;
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -64,7 +64,8 @@ class _HomeScreenState extends State<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<FavoriteOnlinePopup> 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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue