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 {
|
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()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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: [
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue