Auto-sync: 20260315_033147

This commit is contained in:
Paolo 2026-03-15 03:32:09 +01:00
parent 8ef703c082
commit ea54bf3a12

View file

@ -11,6 +11,9 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
import 'package:app_links/app_links.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../logic/game_controller.dart';
import '../../core/theme_manager.dart';
import '../../core/app_colors.dart';
@ -198,10 +201,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
void _showError(String message) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message, style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red)); }
// ===========================================================================
// DIALOGHI IN-FILE
// ===========================================================================
void _showWaitingDialog(String code) {
showDialog(
context: context,
@ -331,8 +330,14 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
);
}
// --- FINESTRA DI REGISTRAZIONE/LOGIN ---
void _showNameDialog() {
final TextEditingController nameController = TextEditingController(text: StorageService.instance.playerName);
final TextEditingController passController = TextEditingController();
bool isLoadingAuth = false;
bool _obscurePassword = true;
String _errorMessage = "";
showDialog(
context: context, barrierDismissible: false, barrierColor: Colors.black.withOpacity(0.8),
builder: (context) {
@ -340,67 +345,235 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
final theme = themeManager.currentColors; final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122); final loc = AppLocalizations.of(context)!;
return StatefulBuilder(
builder: (context, setStateDialog) {
Future<void> handleAuth(bool isLogin) async {
final name = nameController.text.trim();
final password = passController.text.trim();
setStateDialog(() {
_errorMessage = "";
isLoadingAuth = true;
});
if (name.isEmpty || password.isEmpty) {
setStateDialog(() {
_errorMessage = "Inserisci Nome e Password!";
isLoadingAuth = false;
});
return;
}
if (password.length < 6) {
setStateDialog(() {
_errorMessage = "La password deve avere almeno 6 caratteri!";
isLoadingAuth = false;
});
return;
}
final fakeEmail = "${name.toLowerCase().replaceAll(' ', '')}@tetraq.game";
try {
if (isLogin) {
await FirebaseAuth.instance.signInWithEmailAndPassword(email: fakeEmail, password: password);
final doc = await FirebaseFirestore.instance.collection('leaderboard').doc(FirebaseAuth.instance.currentUser!.uid).get();
if (doc.exists) {
final data = doc.data() as Map<String, dynamic>;
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('totalXP', data['xp'] ?? 0);
await prefs.setInt('wins', data['wins'] ?? 0);
await prefs.setInt('losses', data['losses'] ?? 0);
}
if (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 (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Account creato con successo!"), backgroundColor: Colors.green));
}
await StorageService.instance.savePlayerName(name);
if (mounted) Navigator.of(context).pop();
setState(() {});
} 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!";
}
setStateDialog(() {
_errorMessage = msg;
isLoadingAuth = false;
});
} catch (e) {
setStateDialog(() {
_errorMessage = "Errore imprevisto: $e";
isLoadingAuth = false;
});
}
}
Widget dialogContent = themeType == AppThemeType.doodle
? CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 100),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 25.0),
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 28, letterSpacing: 2.0)), textAlign: TextAlign.center),
const SizedBox(height: 20),
Text('Scegli il tuo nome da battaglia', style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 18)), textAlign: TextAlign.center),
const SizedBox(height: 30),
Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 24, letterSpacing: 2.0)), textAlign: TextAlign.center),
const SizedBox(height: 10),
Text('Scegli Nome e Password.\nTi serviranno per recuperare gli XP!', style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 13)), textAlign: TextAlign.center),
const SizedBox(height: 15),
TextField(
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 36, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(hintText: loc.nameHint, hintStyle: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.3), letterSpacing: 4)), filled: false, counterText: "", enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: inkColor, width: 3)), focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.red.shade200, width: 5))),
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 8,
style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 4)),
decoration: InputDecoration(
hintText: loc.nameHint,
hintStyle: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.3), letterSpacing: 4)),
filled: false, counterText: "",
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: inkColor, width: 3)),
focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.red.shade200, width: 5))
),
const SizedBox(height: 40),
GestureDetector(
onTap: () {
final name = nameController.text.trim();
if (name.isNotEmpty) { StorageService.instance.savePlayerName(name); Navigator.of(context).pop(); setState(() {}); }
},
),
const SizedBox(height: 8),
TextField(
controller: passController, obscureText: _obscurePassword, textAlign: TextAlign.center, maxLength: 20,
style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 20, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(
hintText: "PASSWORD",
hintStyle: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.3), letterSpacing: 4)),
filled: false, counterText: "",
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: inkColor, width: 3)),
focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.red.shade200, width: 5)),
suffixIcon: IconButton(
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off, color: inkColor.withOpacity(0.6)),
onPressed: () { setStateDialog(() { _obscurePassword = !_obscurePassword; }); },
),
),
),
const SizedBox(height: 15),
// MESSAGGIO DI ERRORE
if (_errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(_errorMessage, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.red, fontSize: 14, fontWeight: FontWeight.bold)), textAlign: TextAlign.center),
),
Text("💡 Nota: Non serve una vera email. Usa una password facile da ricordare!", style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.6), fontSize: 11, height: 1.3)), textAlign: TextAlign.center),
const SizedBox(height: 15),
isLoadingAuth
? CircularProgressIndicator(color: inkColor)
: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => handleAuth(true),
child: CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 101),
child: Container(width: double.infinity, height: 60, alignment: Alignment.center, child: Text(loc.saveAndPlay, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 1.5)))),
painter: DoodleBackgroundPainter(fillColor: Colors.blue.shade200, strokeColor: inkColor, seed: 101),
child: Container(height: 45, alignment: Alignment.center, child: Text("ACCEDI", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1.5)))),
),
),
),
const SizedBox(width: 10),
Expanded(
child: GestureDetector(
onTap: () => handleAuth(false),
child: CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 102),
child: Container(height: 45, alignment: Alignment.center, child: Text("REGISTRATI", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1.5)))),
),
),
),
],
),
],
),
),
),
)
: Container(
decoration: BoxDecoration(color: theme.background, borderRadius: BorderRadius.circular(25), border: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 20, spreadRadius: 5)]),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30.0, horizontal: 25.0),
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 24, letterSpacing: 1.5)), textAlign: TextAlign.center),
const SizedBox(height: 20),
Text('Scegli il tuo nome da battaglia per sfidare i tuoi amici online.', style: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.8), fontSize: 16)), textAlign: TextAlign.center),
const SizedBox(height: 40),
Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 1.5)), textAlign: TextAlign.center),
const SizedBox(height: 10),
Text('Scegli Nome e Password.\nTi serviranno per recuperare gli XP!', style: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.8), fontSize: 13)), textAlign: TextAlign.center),
const SizedBox(height: 15),
TextField(
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(hintText: loc.nameHint, hintStyle: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 4)), filled: true, fillColor: theme.text.withOpacity(0.05), counterText: "", enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2), borderRadius: BorderRadius.circular(15)), focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3), borderRadius: BorderRadius.circular(15))),
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 8,
style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 4)),
decoration: InputDecoration(
hintText: loc.nameHint,
hintStyle: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 4)),
filled: true, fillColor: theme.text.withOpacity(0.05), counterText: "",
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2), borderRadius: BorderRadius.circular(15)),
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3), borderRadius: BorderRadius.circular(15))
),
const SizedBox(height: 40),
SizedBox(
width: double.infinity, height: 55,
),
const SizedBox(height: 10),
TextField(
controller: passController, obscureText: _obscurePassword, textAlign: TextAlign.center, maxLength: 20,
style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 20, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(
hintText: "PASSWORD",
hintStyle: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 4)),
filled: true, fillColor: theme.text.withOpacity(0.05), counterText: "",
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2), borderRadius: BorderRadius.circular(15)),
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3), borderRadius: BorderRadius.circular(15)),
suffixIcon: IconButton(
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off, color: theme.text.withOpacity(0.6)),
onPressed: () { setStateDialog(() { _obscurePassword = !_obscurePassword; }); },
),
),
),
const SizedBox(height: 15),
// MESSAGGIO DI ERRORE
if (_errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(_errorMessage, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.redAccent, fontSize: 14, fontWeight: FontWeight.bold)), textAlign: TextAlign.center),
),
Text("💡 Nota: Non serve una vera email. Usa una password facile da ricordare!", style: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 11, height: 1.3)), textAlign: TextAlign.center),
const SizedBox(height: 20),
isLoadingAuth
? CircularProgressIndicator(color: theme.playerBlue)
: Row(
children: [
Expanded(
child: SizedBox(
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.text.withOpacity(0.1), foregroundColor: theme.text, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), side: BorderSide(color: theme.playerBlue, width: 1.5)),
onPressed: () => handleAuth(true),
child: Text("ACCEDI", style: getSharedTextStyle(themeType, const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.0))),
),
),
),
const SizedBox(width: 10),
Expanded(
child: SizedBox(
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () {
final name = nameController.text.trim();
if (name.isNotEmpty) { StorageService.instance.savePlayerName(name); Navigator.of(context).pop(); setState(() {}); }
},
child: Text(loc.saveAndPlay, style: getSharedTextStyle(themeType, const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 1.5))),
onPressed: () => handleAuth(false),
child: Text("REGISTRATI", style: getSharedTextStyle(themeType, const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.0))),
),
),
),
],
),
],
),
),
@ -411,6 +584,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent);
},
);
},
);
}
void _showMatchSetupDialog(bool isVsCPU) {
@ -864,7 +1039,9 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
);
return Scaffold(
backgroundColor: Colors.transparent,
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
extendBodyBehindAppBar: true,
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0, iconTheme: IconThemeData(color: theme.text)),
body: Stack(
children: [
// 1. Sfondo base a tinta unita