From ea54bf3a12f7ceb0e2cf6efa0cd839348e091fce Mon Sep 17 00:00:00 2001 From: Paolo Date: Sun, 15 Mar 2026 03:32:09 +0100 Subject: [PATCH] Auto-sync: 20260315_033147 --- lib/ui/home/home_screen.dart | 323 +++++++++++++++++++++++++++-------- 1 file changed, 250 insertions(+), 73 deletions(-) diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index e01699f..6ac5917 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -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 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 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,75 +345,245 @@ class _HomeScreenState extends State with WidgetsBindingObserver { final theme = themeManager.currentColors; final themeType = themeManager.currentThemeType; Color inkColor = const Color(0xFF111122); final loc = AppLocalizations.of(context)!; - Widget dialogContent = themeType == AppThemeType.doodle - ? CustomPaint( - painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 100), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 25.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), - 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))), - ), - const SizedBox(height: 40), - GestureDetector( - onTap: () { - final name = nameController.text.trim(); - if (name.isNotEmpty) { StorageService.instance.savePlayerName(name); Navigator.of(context).pop(); setState(() {}); } - }, - 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)))), - ), - ), - ], - ), - ), - ) - : 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( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 30.0, horizontal: 25.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), - 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))), - ), - const SizedBox(height: 40), - SizedBox( - width: double.infinity, height: 55, - 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))), - ), - ), - ], - ), - ), - ), - ); + return StatefulBuilder( + builder: (context, setStateDialog) { - if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) dialogContent = AnimatedCyberBorder(child: dialogContent); - return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent); + Future 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; + 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: 20.0, horizontal: 20.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + 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: 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: 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.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: 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: 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: 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: 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: () => handleAuth(false), + child: Text("REGISTRATI", style: getSharedTextStyle(themeType, const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.0))), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ); + + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) dialogContent = AnimatedCyberBorder(child: dialogContent); + return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent); + }, + ); }, ); } @@ -864,7 +1039,9 @@ class _HomeScreenState extends State 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