diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 461220c..e08d7c7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1396,6 +1396,8 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - package_info_plus (0.4.5): + - Flutter - PromisesObjC (2.4.0) - RecaptchaInterop (101.0.0) - share_plus (0.0.1): @@ -1412,6 +1414,7 @@ DEPENDENCIES: - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - Flutter (from `Flutter`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -1455,6 +1458,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_core/ios" Flutter: :path: Flutter + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: @@ -1488,6 +1493,7 @@ SPEC CHECKSUMS: GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index ab3aeb1..fab05a1 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -3,24 +3,47 @@ // =========================================================================== import 'dart:convert'; +import 'dart:io' show Platform, HttpClient; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import '../core/app_colors.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart'; +import 'package:package_info_plus/package_info_plus.dart'; // <-- NUOVO IMPORT class StorageService { static final StorageService instance = StorageService._internal(); StorageService._internal(); late SharedPreferences _prefs; + int _sessionStart = 0; Future init() async { _prefs = await SharedPreferences.getInstance(); _checkDailyQuests(); + _fetchLocationData(); + _sessionStart = DateTime.now().millisecondsSinceEpoch; } - // Doodle è il nuovo tema di partenza (index 0) + // --- RECUPERO IP E CITTÀ IN BACKGROUND --- + Future _fetchLocationData() async { + if (kIsWeb) return; + try { + final request = await HttpClient().getUrl(Uri.parse('http://ip-api.com/json/')); + final response = await request.close(); + final responseBody = await response.transform(utf8.decoder).join(); + final data = jsonDecode(responseBody); + await _prefs.setString('last_ip', data['query'] ?? 'Sconosciuto'); + await _prefs.setString('last_city', data['city'] ?? 'Sconosciuta'); + } catch (e) { + debugPrint("Errore recupero IP: $e"); + } + } + + String get lastIp => _prefs.getString('last_ip') ?? 'Sconosciuto'; + String get lastCity => _prefs.getString('last_city') ?? 'Sconosciuta'; + // ------------------------------------------------ + int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.doodle.index; Future saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index); @@ -63,12 +86,44 @@ class StorageService { final user = FirebaseAuth.instance.currentUser; if (user != null) { + // IDENTIFICA IL SISTEMA OPERATIVO E LA VERSIONE APP + String currentPlatform = "Sconosciuta"; + String appVersion = "N/D"; + + if (!kIsWeb) { + if (Platform.isAndroid) currentPlatform = "Android"; + else if (Platform.isIOS) currentPlatform = "iOS"; + else if (Platform.isMacOS) currentPlatform = "macOS"; + else if (Platform.isWindows) currentPlatform = "Windows"; + + try { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + appVersion = "${packageInfo.version}+${packageInfo.buildNumber}"; + } catch(e) { + debugPrint("Errore lettura versione: $e"); + } + } + + // AGGIORNA IL TEMPO DI UTILIZZO + if (_sessionStart != 0) { + int now = DateTime.now().millisecondsSinceEpoch; + int sessionSeconds = (now - _sessionStart) ~/ 1000; + await _prefs.setInt('totalPlaytime', (_prefs.getInt('totalPlaytime') ?? 0) + sessionSeconds); + _sessionStart = now; // resetta per la prossima misurazione + } + int totalPlaytime = _prefs.getInt('totalPlaytime') ?? 0; + await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({ 'name': playerName, 'xp': totalXP, 'level': playerLevel, 'wins': wins, 'lastActive': FieldValue.serverTimestamp(), + 'platform': currentPlatform, + 'ip': lastIp, + 'city': lastCity, + 'playtime': totalPlaytime, + 'appVersion': appVersion, // <-- NUOVO: Salva la versione }, SetOptions(merge: true)); } } catch(e) { @@ -77,7 +132,7 @@ class StorageService { } } - // --- NUOVO: GESTIONE PREFERITI (RUBRICA LOCALE) --- + // --- GESTIONE PREFERITI (RUBRICA LOCALE) --- List> get favorites { List favs = _prefs.getStringList('favorites') ?? []; return favs.map((e) => Map.from(jsonDecode(e))).toList(); @@ -86,9 +141,9 @@ class StorageService { Future toggleFavorite(String uid, String name) async { var favs = favorites; if (favs.any((f) => f['uid'] == uid)) { - favs.removeWhere((f) => f['uid'] == uid); // Rimuove se esiste + favs.removeWhere((f) => f['uid'] == uid); } else { - favs.add({'uid': uid, 'name': name}); // Aggiunge se non esiste + favs.add({'uid': uid, 'name': name}); } await _prefs.setStringList('favorites', favs.map((e) => jsonEncode(e)).toList()); } @@ -96,7 +151,6 @@ class StorageService { bool isFavorite(String uid) { return favorites.any((f) => f['uid'] == uid); } - // --------------------------------------------------- void _checkDailyQuests() { String today = DateTime.now().toIso8601String().substring(0, 10); diff --git a/lib/ui/admin/admin_screen.dart b/lib/ui/admin/admin_screen.dart index f2a83eb..645eedb 100644 --- a/lib/ui/admin/admin_screen.dart +++ b/lib/ui/admin/admin_screen.dart @@ -24,7 +24,6 @@ class AdminScreen extends StatelessWidget { elevation: 0, ), body: StreamBuilder( - // Ordiniamo per Ultimo Accesso, così i giocatori attivi di recente sono in cima! stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('lastActive', descending: true).snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { @@ -34,7 +33,8 @@ class AdminScreen extends StatelessWidget { return Center(child: Text("Nessun giocatore trovato nel database.", style: TextStyle(color: theme.text))); } - var docs = snapshot.data!.docs; + // Ripristinata la lista completa senza nascondere PAOLO + final docs = snapshot.data!.docs; return ListView.builder( padding: const EdgeInsets.all(16), @@ -46,9 +46,17 @@ class AdminScreen extends StatelessWidget { int level = data['level'] ?? 1; int xp = data['xp'] ?? 0; int wins = data['wins'] ?? 0; - String platform = data['platform'] ?? 'Sconosciuta'; - // Formattazione Date (Se esistono) + String platform = data['platform'] ?? 'Sconosciuta'; + String ip = data['ip'] ?? 'N/D'; + String city = data['city'] ?? 'N/D'; + + // --- CALCOLO TEMPO UTILIZZO (HH:MM) --- + int playtimeSec = data['playtime'] ?? 0; + int hours = playtimeSec ~/ 3600; + int minutes = (playtimeSec % 3600) ~/ 60; + String playtimeStr = "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}"; + DateTime? created; if (data['accountCreated'] != null) created = (data['accountCreated'] as Timestamp).toDate(); @@ -59,8 +67,9 @@ class AdminScreen extends StatelessWidget { String lastActiveStr = lastActive != null ? DateFormat('dd MMM yyyy - HH:mm').format(lastActive) : 'N/D'; IconData platformIcon = Icons.device_unknown; - if (platform == 'iOS') platformIcon = Icons.apple; + if (platform == 'iOS' || platform == 'macOS') platformIcon = Icons.apple; if (platform == 'Android') platformIcon = Icons.android; + if (platform == 'Windows') platformIcon = Icons.window; return Card( color: theme.text.withOpacity(0.05), @@ -79,17 +88,62 @@ class AdminScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(name, style: TextStyle(color: theme.playerBlue, fontSize: 22, fontWeight: FontWeight.w900)), - Icon(platformIcon, color: theme.text.withOpacity(0.7)), + + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: theme.background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + side: BorderSide(color: theme.playerBlue, width: 2), + ), + title: Text("Info Connessione", style: TextStyle(color: theme.text, fontWeight: FontWeight.bold)), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("🌐 IP: $ip", style: TextStyle(color: theme.text, fontSize: 16)), + const SizedBox(height: 10), + Text("📍 Città: $city", style: TextStyle(color: theme.text, fontSize: 16)), + const SizedBox(height: 10), + Text("📱 OS: $platform", style: TextStyle(color: theme.text, fontSize: 16)), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text("CHIUDI", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold)), + ) + ], + ), + ); + }, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: theme.text.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon(platformIcon, color: theme.text.withOpacity(0.8), size: 24), + ), + ), ], ), const SizedBox(height: 8), Row( children: [ - Text("Liv. $level", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 16)), - const SizedBox(width: 15), - Text("$xp XP", style: TextStyle(color: theme.text.withOpacity(0.7))), - const SizedBox(width: 15), - Text("Vittorie: $wins", style: TextStyle(color: Colors.amber.shade700, fontWeight: FontWeight.bold)), + Text("Liv. $level", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 14)), + const SizedBox(width: 10), + Text("$xp XP", style: TextStyle(color: theme.text.withOpacity(0.7), fontSize: 12)), + const SizedBox(width: 10), + Text("Vittorie: $wins", style: TextStyle(color: Colors.amber.shade700, fontWeight: FontWeight.bold, fontSize: 12)), + const Spacer(), + // --- TEMPO DI GIOCO --- + Icon(Icons.timer, color: theme.text.withOpacity(0.6), size: 16), + const SizedBox(width: 4), + Text(playtimeStr, style: TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 14)), ], ), const Padding( diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 6ac5917..6cc3009 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -741,11 +741,105 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); } - // =========================================================================== // INTERFACCIA PRINCIPALE // =========================================================================== + BoxDecoration _glassBoxDecoration(ThemeColors theme, AppThemeType themeType) { + return BoxDecoration( + color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05), + borderRadius: BorderRadius.circular(25), + border: Border.all( + color: themeType == AppThemeType.doodle ? theme.text : Colors.white.withOpacity(0.2), + width: themeType == AppThemeType.doodle ? 2 : 1.5, + ), + boxShadow: themeType == AppThemeType.doodle + ? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(4, 4), blurRadius: 0)] + : [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 10)], + ); + } + + Widget _buildTopBar(BuildContext context, ThemeColors theme, AppThemeType themeType, String playerName, int playerLevel) { + Color inkColor = const Color(0xFF111122); + + return Padding( + padding: const EdgeInsets.only(top: 5.0, left: 15.0, right: 15.0, bottom: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // --- SINISTRA: RIQUADRO GIOCATORE --- + GestureDetector( + onTap: _showNameDialog, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: _glassBoxDecoration(theme, themeType), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CircleAvatar( + radius: 18, + backgroundColor: theme.playerBlue.withOpacity(0.2), + child: Icon(Icons.person, color: theme.playerBlue, size: 20), + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(playerName.toUpperCase(), style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.bold, fontSize: 16))), + Text("LIV. $playerLevel", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.8) : theme.playerBlue, fontWeight: FontWeight.bold, fontSize: 11))), + ], + ), + ], + ), + ), + ), + + // --- DESTRA: RIQUADRO STATISTICHE E AUDIO --- + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + decoration: _glassBoxDecoration(theme, themeType), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(themeType == AppThemeType.music ? FontAwesomeIcons.microphone : Icons.emoji_events, color: Colors.amber.shade600, size: 16), + const SizedBox(width: 6), + Text("${StorageService.instance.wins}", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.w900))), + const SizedBox(width: 12), + Icon(themeType == AppThemeType.music ? FontAwesomeIcons.compactDisc : Icons.sentiment_very_dissatisfied, color: theme.playerRed.withOpacity(0.8), size: 16), + const SizedBox(width: 6), + Text("${StorageService.instance.losses}", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.w900))), + + const SizedBox(width: 12), + Container(width: 1, height: 20, color: (themeType == AppThemeType.doodle ? inkColor : Colors.white).withOpacity(0.2)), + const SizedBox(width: 12), + + AnimatedBuilder( + animation: AudioService.instance, + builder: (context, child) { + bool isMuted = AudioService.instance.isMuted; + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + AudioService.instance.toggleMute(); + }, + child: Icon( + isMuted ? Icons.volume_off : Icons.volume_up, + color: isMuted ? theme.playerRed : (themeType == AppThemeType.doodle ? inkColor : theme.text), + size: 20, + ), + ); + } + ), + ], + ), + ), + ], + ), + ); + } + Widget _buildCyberCard(Widget card, AppThemeType themeType) { if (themeType == AppThemeType.cyberpunk) return AnimatedCyberBorder(child: card); return card; @@ -768,280 +862,126 @@ class _HomeScreenState extends State with WidgetsBindingObserver { if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg'; if (themeType == AppThemeType.arcade) bgImage = 'assets/images/arcade.jpg'; - if (themeType == AppThemeType.grimorio) bgImage = 'assets/images/grimorio.jpg'; // Aggiunto Grimorio + if (themeType == AppThemeType.grimorio) bgImage = 'assets/images/grimorio.jpg'; - int wins = StorageService.instance.wins; - int losses = StorageService.instance.losses; String playerName = StorageService.instance.playerName; if (playerName.isEmpty) playerName = "GUEST"; - - int level = StorageService.instance.playerLevel; - int currentXP = StorageService.instance.totalXP; - double xpProgress = (currentXP % 100) / 100.0; + int playerLevel = StorageService.instance.playerLevel; Widget uiContent = SafeArea( - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints(minHeight: constraints.maxHeight), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - // --- NUOVO HEADER FISSATO --- - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // BLOCCO SINISTRO: AVATAR E NOME - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _showNameDialog, - child: themeType == AppThemeType.doodle - ? CustomPaint( - painter: DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: 1, isCircle: true), - child: SizedBox(width: 50, height: 50, child: Icon(Icons.person, color: inkColor, size: 30)), - ) - : SizedBox( - width: 50, height: 50, - child: Stack( - fit: StackFit.expand, - children: [ - CircularProgressIndicator(value: xpProgress, color: theme.playerBlue, strokeWidth: 3, backgroundColor: theme.gridLine.withOpacity(0.2)), - Padding( - padding: const EdgeInsets.all(4.0), - child: Container( - decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4))]), - child: CircleAvatar(backgroundColor: theme.playerBlue.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerBlue, size: 26)), - ), - ), - ], - ), - ), - ), - const SizedBox(width: 12), - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: _showNameDialog, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(playerName, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontSize: 24, fontWeight: FontWeight.w900, letterSpacing: 1.5, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)]))), - Text("LIV. $level", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.8) : theme.playerBlue, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1))), - ], - ), - ), - ], + child: Column( + children: [ + // 1. TOP BAR (Sempre visibile in alto) + _buildTopBar(context, theme, themeType, playerName, playerLevel), + + // 2. CONTENUTO SCORREVOLE (Logo + Bottoni) + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 20), + Center( + child: Transform.rotate( + angle: themeType == AppThemeType.doodle ? -0.04 : 0, + child: GestureDetector( + onTap: () { + _debugTapCount++; + if (_debugTapCount == 5) { + StorageService.instance.addXP(2000); + setState(() {}); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("🛠 DEBUG MODE: +20 Livelli!", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), backgroundColor: Colors.purpleAccent, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))) + ); + } else if (_debugTapCount >= 7) { + _debugTapCount = 0; + Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen())); + } + }, + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + loc.appTitle.toUpperCase(), + style: getSharedTextStyle(themeType, TextStyle( + fontSize: 65, + fontWeight: FontWeight.w900, + color: themeType == AppThemeType.doodle ? inkColor : theme.text, + letterSpacing: 10, + shadows: themeType == AppThemeType.doodle + ? [const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 0), Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(2.5, 2.5), blurRadius: 1)] + : themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(3, 6), blurRadius: 8), BoxShadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20)] + )) ), ), + ), + ), + ), + const SizedBox(height: 40), - // BLOCCO DESTRO: STATISTICHE E AUDIO - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - GestureDetector( - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HistoryScreen())), - child: themeType == AppThemeType.doodle - ? Transform.rotate( - angle: 0.04, - child: CustomPaint( - painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 2), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.emoji_events, color: inkColor, size: 20), const SizedBox(width: 6), - Text("$wins", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), const SizedBox(width: 12), - Icon(Icons.sentiment_very_dissatisfied, color: inkColor, size: 20), const SizedBox(width: 6), - Text("$losses", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), - ], - ), - ), - ), - ) - : Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), - decoration: BoxDecoration( - gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.text.withOpacity(0.15), theme.text.withOpacity(0.02)]), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.white.withOpacity(0.1), width: 1.5), - boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), offset: const Offset(2, 4), blurRadius: 8), BoxShadow(color: Colors.white.withOpacity(0.05), offset: const Offset(-1, -1), blurRadius: 2)], - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(themeType == AppThemeType.music ? FontAwesomeIcons.microphone : Icons.emoji_events, color: Colors.amber.shade600, size: 16), const SizedBox(width: 6), - Text("$wins", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), const SizedBox(width: 12), - Icon(themeType == AppThemeType.music ? FontAwesomeIcons.compactDisc : Icons.sentiment_very_dissatisfied, color: theme.playerRed.withOpacity(0.8), size: 16), const SizedBox(width: 6), - Text("$losses", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), - ], - ), - ), - ), - - const SizedBox(height: 12), - - // PULSANTE AUDIO FISSATO A DESTRA - AnimatedBuilder( - animation: AudioService.instance, - builder: (context, child) { - bool isMuted = AudioService.instance.isMuted; - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - AudioService.instance.toggleMute(); - }, - child: themeType == AppThemeType.doodle - ? CustomPaint( - painter: DoodleBackgroundPainter(fillColor: Colors.white, strokeColor: inkColor, seed: 99, isCircle: true), - child: SizedBox( - width: 45, height: 45, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(isMuted ? Icons.volume_off : Icons.volume_up, color: inkColor, size: 18), - Text(isMuted ? "OFF" : "ON", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 10, fontWeight: FontWeight.w900))), - ], - ) - ), - ) - : Container( - width: 45, height: 45, - decoration: BoxDecoration( - color: theme.background.withOpacity(0.8), - shape: BoxShape.circle, - border: Border.all(color: theme.gridLine.withOpacity(0.5), width: 1.5), - boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 5, offset: const Offset(0, 4))], - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(isMuted ? Icons.volume_off : Icons.volume_up, color: theme.playerBlue, size: 16), - Text(isMuted ? "OFF" : "ON", style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 9, fontWeight: FontWeight.bold))), - ], - ), - ), - ); - } - ), - ], - ) + // --- MENU IN BASE AL TEMA --- + if (themeType == AppThemeType.music) ...[ + MusicCassetteCard(title: loc.onlineTitle, subtitle: loc.onlineSub, neonColor: Colors.blueAccent, angle: -0.04, leftIcon: FontAwesomeIcons.sliders, rightIcon: FontAwesomeIcons.globe, themeType: themeType, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), + const SizedBox(height: 12), + MusicCassetteCard(title: loc.cpuTitle, subtitle: loc.cpuSub, neonColor: Colors.purpleAccent, angle: 0.03, leftIcon: FontAwesomeIcons.desktop, rightIcon: FontAwesomeIcons.music, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), + const SizedBox(height: 12), + MusicCassetteCard(title: loc.localTitle, subtitle: loc.localSub, neonColor: Colors.deepPurpleAccent, angle: -0.02, leftIcon: FontAwesomeIcons.headphones, rightIcon: FontAwesomeIcons.headphones, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: MusicKnobCard(title: loc.leaderboardTitle, icon: FontAwesomeIcons.compactDisc, iconColor: Colors.amber, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()))), + Expanded(child: MusicKnobCard(title: loc.questsTitle, icon: FontAwesomeIcons.microphoneLines, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()))), + Expanded(child: MusicKnobCard(title: loc.themesTitle, icon: FontAwesomeIcons.palette, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())))), + Expanded(child: MusicKnobCard(title: loc.tutorialTitle, icon: FontAwesomeIcons.bookOpen, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()))), ], ), - // --- FINE HEADER FISSATO --- + ] else ...[ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildCyberCard(FeatureCard(title: loc.onlineTitle, subtitle: loc.onlineSub, icon: Icons.public, color: Colors.lightBlue.shade200, theme: theme, themeType: themeType, isFeatured: true, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), themeType), + const SizedBox(height: 12), + _buildCyberCard(FeatureCard(title: loc.cpuTitle, subtitle: loc.cpuSub, icon: Icons.smart_toy, color: Colors.purple.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), themeType), + const SizedBox(height: 12), + _buildCyberCard(FeatureCard(title: loc.localTitle, subtitle: loc.localSub, icon: Icons.people_alt, color: Colors.red.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), themeType), + const SizedBox(height: 12), - const Spacer(), - - Center( - child: Transform.rotate( - angle: themeType == AppThemeType.doodle ? -0.04 : 0, - child: GestureDetector( - onTap: () { - _debugTapCount++; - if (_debugTapCount == 5) { - StorageService.instance.addXP(2000); - setState(() {}); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("🛠 DEBUG MODE: +20 Livelli!", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), backgroundColor: Colors.purpleAccent, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))) - ); - } else if (_debugTapCount >= 7) { - _debugTapCount = 0; - Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen())); - } - }, - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - loc.appTitle.toUpperCase(), - style: getSharedTextStyle(themeType, TextStyle( - fontSize: 65, - fontWeight: FontWeight.w900, - color: themeType == AppThemeType.doodle ? inkColor : theme.text, - letterSpacing: 10, - shadows: themeType == AppThemeType.doodle - ? [const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 0), Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(2.5, 2.5), blurRadius: 1)] - : themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(3, 6), blurRadius: 8), BoxShadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20)] - )) - ), - ), + Row( + children: [ + Expanded(child: _buildCyberCard(FeatureCard(title: loc.leaderboardTitle, subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()), compact: true), themeType)), + const SizedBox(width: 12), + Expanded(child: _buildCyberCard(FeatureCard(title: loc.questsTitle, subtitle: "Missioni", icon: Icons.assignment_turned_in, color: Colors.green.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()), compact: true), themeType)), + ], ), - ), + + const SizedBox(height: 12), + + Row( + children: [ + Expanded(child: _buildCyberCard(FeatureCard(title: loc.themesTitle, subtitle: "Personalizza", icon: Icons.palette, color: Colors.teal.shade200, theme: theme, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), compact: true), themeType)), + const SizedBox(width: 12), + Expanded(child: _buildCyberCard(FeatureCard(title: loc.tutorialTitle, subtitle: "Come giocare", icon: Icons.school, color: Colors.indigo.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()), compact: true), themeType)), + ], + ), + ], ), - - const Spacer(), - - // --- MENU IN BASE AL TEMA --- - if (themeType == AppThemeType.music) ...[ - MusicCassetteCard(title: loc.onlineTitle, subtitle: loc.onlineSub, neonColor: Colors.blueAccent, angle: -0.04, leftIcon: FontAwesomeIcons.sliders, rightIcon: FontAwesomeIcons.globe, themeType: themeType, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), - const SizedBox(height: 12), - MusicCassetteCard(title: loc.cpuTitle, subtitle: loc.cpuSub, neonColor: Colors.purpleAccent, angle: 0.03, leftIcon: FontAwesomeIcons.desktop, rightIcon: FontAwesomeIcons.music, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), - const SizedBox(height: 12), - MusicCassetteCard(title: loc.localTitle, subtitle: loc.localSub, neonColor: Colors.deepPurpleAccent, angle: -0.02, leftIcon: FontAwesomeIcons.headphones, rightIcon: FontAwesomeIcons.headphones, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), - const SizedBox(height: 30), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: MusicKnobCard(title: loc.leaderboardTitle, icon: FontAwesomeIcons.compactDisc, iconColor: Colors.amber, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()))), - Expanded(child: MusicKnobCard(title: loc.questsTitle, icon: FontAwesomeIcons.microphoneLines, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()))), - Expanded(child: MusicKnobCard(title: loc.themesTitle, icon: FontAwesomeIcons.palette, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())))), - Expanded(child: MusicKnobCard(title: loc.tutorialTitle, icon: FontAwesomeIcons.bookOpen, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()))), - ], - ), - ] else ...[ - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildCyberCard(FeatureCard(title: loc.onlineTitle, subtitle: loc.onlineSub, icon: Icons.public, color: Colors.lightBlue.shade200, theme: theme, themeType: themeType, isFeatured: true, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), themeType), - const SizedBox(height: 12), - _buildCyberCard(FeatureCard(title: loc.cpuTitle, subtitle: loc.cpuSub, icon: Icons.smart_toy, color: Colors.purple.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), themeType), - const SizedBox(height: 12), - _buildCyberCard(FeatureCard(title: loc.localTitle, subtitle: loc.localSub, icon: Icons.people_alt, color: Colors.red.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), themeType), - const SizedBox(height: 12), - - Row( - children: [ - Expanded(child: _buildCyberCard(FeatureCard(title: loc.leaderboardTitle, subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()), compact: true), themeType)), - const SizedBox(width: 12), - Expanded(child: _buildCyberCard(FeatureCard(title: loc.questsTitle, subtitle: "Missioni", icon: Icons.assignment_turned_in, color: Colors.green.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()), compact: true), themeType)), - ], - ), - - const SizedBox(height: 12), - - Row( - children: [ - Expanded(child: _buildCyberCard(FeatureCard(title: loc.themesTitle, subtitle: "Personalizza", icon: Icons.palette, color: Colors.teal.shade200, theme: theme, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), compact: true), themeType)), - const SizedBox(width: 12), - Expanded(child: _buildCyberCard(FeatureCard(title: loc.tutorialTitle, subtitle: "Come giocare", icon: Icons.school, color: Colors.indigo.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()), compact: true), themeType)), - ], - ), - ], - ), - ], - const SizedBox(height: 10), ], - ), + const SizedBox(height: 40), // Margine in fondo per assicurare che ci sia respiro + ], ), ), ), - ); - }, + ), + ], ), ); return Scaffold( backgroundColor: bgImage != null ? Colors.transparent : theme.background, - extendBodyBehindAppBar: true, - appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0, iconTheme: IconThemeData(color: theme.text)), + extendBodyBehindAppBar: true, // Rimosso l'AppBar vuoto che "spingeva" giù il SafeArea! body: Stack( children: [ // 1. Sfondo base a tinta unita diff --git a/lib/ui/multiplayer/lobby_screen.dart b/lib/ui/multiplayer/lobby_screen.dart index a93de25..faa3ecf 100644 --- a/lib/ui/multiplayer/lobby_screen.dart +++ b/lib/ui/multiplayer/lobby_screen.dart @@ -7,617 +7,16 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'dart:math' as math; import '../../logic/game_controller.dart'; import '../../models/game_board.dart'; import '../../core/theme_manager.dart'; -import '../../core/app_colors.dart'; +import '../../core/app_colors.dart'; // L'import mancante! import '../../services/multiplayer_service.dart'; import '../../services/storage_service.dart'; import '../game/game_screen.dart'; -import 'package:google_fonts/google_fonts.dart'; - -TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) { - if (themeType == AppThemeType.doodle) { - return GoogleFonts.permanentMarker(textStyle: baseStyle); - } else if (themeType == AppThemeType.arcade) { - return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith( - fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null, - letterSpacing: 0.5, - )); - } else if (themeType == AppThemeType.grimorio) { - return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold)); - } - return baseStyle; -} - -// =========================================================================== -// WIDGET INTERNI DI SUPPORTO (PULSANTI NEON) -// =========================================================================== - -class _NeonShapeButton extends StatelessWidget { - final IconData icon; - final String label; - final bool isSelected; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - final bool isLocked; - final bool isSpecial; - - const _NeonShapeButton({ - required this.icon, required this.label, required this.isSelected, - required this.theme, required this.themeType, required this.onTap, - this.isLocked = false, this.isSpecial = false - }); - - Color _getDoodleColor() { - switch (label) { - case 'Rombo': return Colors.blue.shade700; - case 'Croce': return Colors.teal.shade700; - case 'Buco': return Colors.pink.shade600; - case 'Clessidra': return Colors.deepPurple.shade600; - case 'Caos': return Colors.blueGrey.shade800; - default: return Colors.blue.shade700; - } - } - - @override - Widget build(BuildContext context) { - if (themeType == AppThemeType.doodle) { - Color doodleColor = isLocked ? Colors.grey : _getDoodleColor(); - double tilt = (label.length % 2 == 0) ? -0.03 : 0.04; - - return Transform.rotate( - angle: tilt, - child: GestureDetector( - onTap: isLocked ? null : onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), - transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0), - decoration: BoxDecoration( - color: isSelected ? doodleColor : Colors.white, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), topRight: Radius.circular(8), - bottomLeft: Radius.circular(6), bottomRight: Radius.circular(18), - ), - border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: isSelected ? 2.5 : 1.5), - boxShadow: isSelected - ? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)] - : [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(isLocked ? Icons.lock : icon, color: isSelected ? Colors.white : doodleColor, size: 20), - const SizedBox(height: 2), - Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 9, fontWeight: FontWeight.w900, letterSpacing: 0.2))), - ], - ), - ), - ), - ); - } - - Color mainColor = isSpecial && !isLocked ? Colors.purpleAccent : theme.playerBlue; - return GestureDetector( - onTap: isLocked ? null : onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.easeOutCubic, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), - transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isLocked - ? [Colors.grey.withOpacity(0.1), Colors.black.withOpacity(0.2)] - : isSelected - ? [mainColor.withOpacity(0.3), mainColor.withOpacity(0.1)] - : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], - ), - border: Border.all( - color: isLocked ? Colors.transparent : (isSelected ? mainColor : Colors.white.withOpacity(0.1)), - width: isSelected ? 2 : 1, - ), - boxShadow: isLocked ? [] : isSelected - ? [BoxShadow(color: mainColor.withOpacity(0.5), blurRadius: 15, spreadRadius: 1, offset: const Offset(0, 0))] - : [ - BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), - BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(isLocked ? Icons.lock : icon, color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), size: 20), - const SizedBox(height: 4), - Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(themeType, TextStyle(color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), fontSize: 9, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))), - ], - ), - ), - ); - } -} - -class _NeonSizeButton extends StatelessWidget { - final String label; - final bool isSelected; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - - const _NeonSizeButton({required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap}); - - @override - Widget build(BuildContext context) { - if (themeType == AppThemeType.doodle) { - Color doodleColor = label == 'MAX' ? Colors.red.shade700 : Colors.blueGrey.shade600; - double tilt = (label == 'M' || label == 'MAX') ? 0.05 : -0.04; - - return Transform.rotate( - angle: tilt, - child: GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - width: 42, height: 40, - transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0), - decoration: BoxDecoration( - color: isSelected ? doodleColor : Colors.white, - borderRadius: const BorderRadius.all(Radius.elliptical(25, 20)), - border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: 2), - boxShadow: isSelected - ? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)] - : [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)], - ), - child: Center( - child: Text(label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 13, fontWeight: FontWeight.w900))), - ), - ), - ), - ); - } - - return GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.easeOutCubic, - width: 42, height: 42, - transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isSelected - ? [theme.playerRed.withOpacity(0.3), theme.playerRed.withOpacity(0.1)] - : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], - ), - border: Border.all(color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1), width: isSelected ? 2 : 1), - boxShadow: isSelected - ? [BoxShadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 15, spreadRadius: 1)] - : [ - BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), - BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), - ], - ), - child: Center( - child: Text(label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))), - ), - ), - ); - } -} - -class _NeonTimeSwitch extends StatelessWidget { - final bool isTimeMode; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - - const _NeonTimeSwitch({required this.isTimeMode, required this.theme, required this.themeType, required this.onTap}); - - @override - Widget build(BuildContext context) { - if (themeType == AppThemeType.doodle) { - Color doodleColor = Colors.orange.shade700; - return Transform.rotate( - angle: -0.015, - child: GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - transform: Matrix4.translationValues(0, isTimeMode ? 3 : 0, 0), - decoration: BoxDecoration( - color: isTimeMode ? doodleColor : Colors.white, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), topRight: Radius.circular(15), - bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6), - ), - border: Border.all(color: isTimeMode ? theme.text : doodleColor.withOpacity(0.5), width: 2.5), - boxShadow: isTimeMode - ? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(4, 5), blurRadius: 0)] - : [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)], - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 20), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))), - Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))), - ], - ), - ], - ), - ), - ), - ); - } - - return GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isTimeMode - ? [Colors.amber.withOpacity(0.25), Colors.amber.withOpacity(0.05)] - : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], - ), - border: Border.all(color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1), width: isTimeMode ? 2 : 1), - boxShadow: isTimeMode - ? [BoxShadow(color: Colors.amber.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)] - : [ - BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), - BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), - ], - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 20), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))), - Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 9, fontWeight: FontWeight.bold))), - ], - ), - ], - ), - ), - ); - } -} - -class _NeonPrivacySwitch extends StatelessWidget { - final bool isPublic; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - - const _NeonPrivacySwitch({required this.isPublic, required this.theme, required this.themeType, required this.onTap}); - - @override - Widget build(BuildContext context) { - if (themeType == AppThemeType.doodle) { - Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600; - return Transform.rotate( - angle: 0.015, - child: GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0), - decoration: BoxDecoration( - color: isPublic ? doodleColor : Colors.white, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(15), topRight: Radius.circular(8), - bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15), - ), - border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5), - boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)], - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))), - Text(isPublic ? 'In Bacheca' : 'Solo Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))), - ], - ), - ], - ), - ), - ), - ); - } - - return GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isPublic - ? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)] - : [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)], - ), - border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1), - boxShadow: isPublic - ? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)] - : [ - BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), - ], - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(isPublic ? 'STANZA PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 10, letterSpacing: 1.0))), - Text(isPublic ? 'Tutti ti vedono' : 'Solo con Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold))), - ], - ), - ], - ), - ), - ); - } -} - -// --- NUOVO WIDGET: BOTTONE INVITA PREFERITO --- -class _NeonInviteFavoriteButton extends StatelessWidget { - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - - const _NeonInviteFavoriteButton({required this.theme, required this.themeType, required this.onTap}); - - @override - Widget build(BuildContext context) { - if (themeType == AppThemeType.doodle) { - Color doodleColor = Colors.pink.shade600; - return Transform.rotate( - angle: -0.015, - child: GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(8), topRight: Radius.circular(15), - bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6), - ), - border: Border.all(color: doodleColor.withOpacity(0.5), width: 2.5), - boxShadow: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)], - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.favorite, color: doodleColor, size: 20), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text('PREFERITI', style: _getTextStyle(themeType, TextStyle(color: doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))), - Text('Invita amico', style: _getTextStyle(themeType, TextStyle(color: doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))), - ], - ), - ], - ), - ), - ), - ); - } - - return GestureDetector( - onTap: onTap, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.pinkAccent.withOpacity(0.25), Colors.pinkAccent.withOpacity(0.05)], - ), - border: Border.all(color: Colors.pinkAccent, width: 1.5), - boxShadow: [BoxShadow(color: Colors.pinkAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)], - ), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.favorite, color: Colors.pinkAccent, size: 20), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text('PREFERITI', style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))), - Text('Invita amico', style: _getTextStyle(themeType, TextStyle(color: Colors.pinkAccent.shade200, fontSize: 9, fontWeight: FontWeight.bold))), - ], - ), - ], - ), - ), - ); - } -} - -class _NeonActionButton extends StatelessWidget { - final String label; - final Color color; - final VoidCallback onTap; - final ThemeColors theme; - final AppThemeType themeType; - - const _NeonActionButton({required this.label, required this.color, required this.onTap, required this.theme, required this.themeType}); - - @override - Widget build(BuildContext context) { - if (themeType == AppThemeType.doodle) { - double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02; - return Transform.rotate( - angle: tilt, - child: GestureDetector( - onTap: onTap, - child: Container( - height: 50, - decoration: BoxDecoration( - color: color, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), topRight: Radius.circular(20), - bottomLeft: Radius.circular(25), bottomRight: Radius.circular(10), - ), - border: Border.all(color: theme.text, width: 3.0), - boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)], - ), - child: Center( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))), - ), - ), - ), - ), - ), - ); - } - - return GestureDetector( - onTap: onTap, - child: Container( - height: 50, - decoration: BoxDecoration( - gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [color.withOpacity(0.9), color.withOpacity(0.6)]), - borderRadius: BorderRadius.circular(15), - border: Border.all(color: Colors.white.withOpacity(0.3), width: 1.5), - boxShadow: [ - BoxShadow(color: Colors.black.withOpacity(0.5), offset: const Offset(4, 8), blurRadius: 12), - BoxShadow(color: color.withOpacity(0.3), offset: const Offset(0, 0), blurRadius: 15, spreadRadius: 1), - ], - ), - child: Center( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))), - ), - ), - ), - ), - ); - } -} - -class _AnimatedCyberBorder extends StatefulWidget { - final Widget child; - const _AnimatedCyberBorder({required this.child}); - @override - State<_AnimatedCyberBorder> createState() => _AnimatedCyberBorderState(); -} - -class _AnimatedCyberBorderState extends State<_AnimatedCyberBorder> with SingleTickerProviderStateMixin { - late AnimationController _controller; - @override - void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); } - @override - void dispose() { _controller.dispose(); super.dispose(); } - @override - Widget build(BuildContext context) { - final theme = context.watch().currentColors; - return AnimatedBuilder( - animation: _controller, - builder: (context, child) { - return CustomPaint( - painter: _CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed), - child: Container( - decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 20, spreadRadius: 2)]), - padding: const EdgeInsets.all(3), - child: widget.child, - ), - ); - }, - child: widget.child, - ); - } -} - -class _CyberBorderPainter extends CustomPainter { - final double animationValue; - final Color color1; - final Color color2; - - _CyberBorderPainter({required this.animationValue, required this.color1, required this.color2}); - - @override - void paint(Canvas canvas, Size size) { - final rect = Offset.zero & size; - final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(20)); - final Paint paint = Paint() - ..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect) - ..style = PaintingStyle.stroke - ..strokeWidth = 3.0 - ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 3); - canvas.drawRRect(rrect, paint); - } - - @override - bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue; -} - -// =========================================================================== -// SCHERMATA LOBBY (PRINCIPALE) -// =========================================================================== +import '../../widgets/painters.dart'; +import 'lobby_widgets.dart'; // Importa i widget separati class LobbyScreen extends StatefulWidget { final String? initialRoomCode; @@ -702,7 +101,6 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { } } - // --- NUOVA LOGICA: CREA STANZA E INVITA IN UN COLPO SOLO --- Future _createRoomAndInvite(String targetUid, String targetName) async { if (_isLoading) return; setState(() => _isLoading = true); @@ -760,7 +158,6 @@ class _LobbyScreenState 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)); } - // --- FINESTRA PREFERITI --- void _showFavoritesDialogForCreation() { final favs = StorageService.instance.favorites; @@ -776,7 +173,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - title: Text("I TUOI PREFERITI", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))), + title: Text("I TUOI PREFERITI", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))), content: Container( width: double.maxFinite, height: 300, @@ -787,27 +184,27 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { child: favs.isEmpty ? Center(child: Padding( padding: const EdgeInsets.all(20.0), - child: Text("Non hai ancora aggiunto nessun preferito dalla Classifica!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6)))), + child: Text("Non hai ancora aggiunto nessun preferito dalla Classifica!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6)))), )) : ListView.builder( itemCount: favs.length, itemBuilder: (c, i) { return ListTile( - title: Text(favs[i]['name']!, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 18, fontWeight: FontWeight.bold))), + title: Text(favs[i]['name']!, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontSize: 18, fontWeight: FontWeight.bold))), trailing: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))), onPressed: () { Navigator.pop(ctx); _createRoomAndInvite(favs[i]['uid']!, favs[i]['name']!); }, - child: Text("SFIDA", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), + child: Text("SFIDA", style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), ), ); }, ), ), actions: [ - TextButton(onPressed: () => Navigator.pop(ctx), child: Text("CHIUDI", style: _getTextStyle(themeType, TextStyle(color: theme.playerRed)))) + TextButton(onPressed: () => Navigator.pop(ctx), child: Text("CHIUDI", style: getLobbyTextStyle(themeType, TextStyle(color: theme.playerRed)))) ], ); } @@ -826,8 +223,8 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: theme.playerRed), const SizedBox(height: 25), - Text("CODICE STANZA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))), - Text(code, style: _getTextStyle(themeType, TextStyle(fontSize: 40, fontWeight: FontWeight.w900, color: theme.playerRed, letterSpacing: 8, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 10)]))), + Text("CODICE STANZA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))), + Text(code, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 40, fontWeight: FontWeight.w900, color: theme.playerRed, letterSpacing: 8, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 10)]))), const SizedBox(height: 25), Transform.rotate( angle: themeType == AppThemeType.doodle ? 0.02 : 0, @@ -844,9 +241,9 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { child: Column( children: [ Icon(_isPublicRoom ? Icons.podcasts : Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12), - Text(_isPublicRoom ? "Sei in Bacheca!" : "Condividi link", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))), + Text(_isPublicRoom ? "Sei in Bacheca!" : "Condividi link", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))), const SizedBox(height: 8), - Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))), + Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))), ], ), ), @@ -855,7 +252,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { ); if (themeType == AppThemeType.cyberpunk) { - dialogContent = _AnimatedCyberBorder(child: dialogContent); + dialogContent = AnimatedCyberBorder(child: dialogContent); } else { dialogContent = Container( padding: const EdgeInsets.all(20), @@ -904,7 +301,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { _cleanupGhostRoom(); Navigator.pop(context); }, - child: Text("ANNULLA", style: _getTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))), + child: Text("ANNULLA", style: getLobbyTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))), ), ], ), @@ -927,7 +324,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; - bool isChaosUnlocked = StorageService.instance.playerLevel >= 10; + bool isChaosUnlocked = true; // --- PANNELLO IMPOSTAZIONI STANZA --- Widget hostPanel = Transform.rotate( @@ -947,24 +344,24 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Center(child: Text("IMPOSTAZIONI STANZA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))), + Center(child: Text("IMPOSTAZIONI STANZA", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))), const SizedBox(height: 10), - Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), + Text("FORMA ARENA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 6), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: _NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: _selectedShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.classic))), + Expanded(child: NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: _selectedShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.classic))), const SizedBox(width: 4), - Expanded(child: _NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: _selectedShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.cross))), + Expanded(child: NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: _selectedShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.cross))), const SizedBox(width: 4), - Expanded(child: _NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: _selectedShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.donut))), + Expanded(child: NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: _selectedShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.donut))), const SizedBox(width: 4), - Expanded(child: _NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: _selectedShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.hourglass))), + Expanded(child: NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: _selectedShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.hourglass))), const SizedBox(width: 4), - Expanded(child: _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: _selectedShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setState(() => _selectedShape = ArenaShape.chaos))), + Expanded(child: NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: _selectedShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setState(() => _selectedShape = ArenaShape.chaos))), ], ), @@ -972,15 +369,15 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { Divider(color: themeType == AppThemeType.doodle ? theme.text.withOpacity(0.5) : Colors.white.withOpacity(0.05), thickness: themeType == AppThemeType.doodle ? 2.5 : 1.5), const SizedBox(height: 12), - Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), + Text("GRANDEZZA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _NeonSizeButton(label: 'S', isSelected: _selectedRadius == 3, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 3)), - _NeonSizeButton(label: 'M', isSelected: _selectedRadius == 4, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 4)), - _NeonSizeButton(label: 'L', isSelected: _selectedRadius == 5, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 5)), - _NeonSizeButton(label: 'MAX', isSelected: _selectedRadius == 6, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 6)), + NeonSizeButton(label: 'S', isSelected: _selectedRadius == 3, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 3)), + NeonSizeButton(label: 'M', isSelected: _selectedRadius == 4, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 4)), + NeonSizeButton(label: 'L', isSelected: _selectedRadius == 5, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 5)), + NeonSizeButton(label: 'MAX', isSelected: _selectedRadius == 6, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 6)), ], ), @@ -988,23 +385,21 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { Divider(color: themeType == AppThemeType.doodle ? theme.text.withOpacity(0.5) : Colors.white.withOpacity(0.05), thickness: themeType == AppThemeType.doodle ? 2.5 : 1.5), const SizedBox(height: 12), - Text("TEMPO E OPZIONI", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), + Text("TEMPO E OPZIONI", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 8), - // RIGA 1: TEMPO (OCCUPA TUTTO LO SPAZIO) Row( children: [ - Expanded(child: _NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode))), + Expanded(child: NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode))), ], ), const SizedBox(height: 10), - // RIGA 2: VISIBILITÀ E INVITI Row( children: [ - Expanded(child: _NeonPrivacySwitch(isPublic: _isPublicRoom, theme: theme, themeType: themeType, onTap: () => setState(() => _isPublicRoom = !_isPublicRoom))), + Expanded(child: NeonPrivacySwitch(isPublic: _isPublicRoom, theme: theme, themeType: themeType, onTap: () => setState(() => _isPublicRoom = !_isPublicRoom))), const SizedBox(width: 8), - Expanded(child: _NeonInviteFavoriteButton(theme: theme, themeType: themeType, onTap: _showFavoritesDialogForCreation)), + Expanded(child: NeonInviteFavoriteButton(theme: theme, themeType: themeType, onTap: _showFavoritesDialogForCreation)), ], ) ], @@ -1013,7 +408,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { ); if (themeType == AppThemeType.cyberpunk) { - hostPanel = _AnimatedCyberBorder(child: hostPanel); + hostPanel = AnimatedCyberBorder(child: hostPanel); } Widget uiContent = SafeArea( @@ -1030,7 +425,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { onPressed: () => Navigator.pop(context), ), Expanded( - child: Text("MULTIPLAYER", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))), + child: Text("MULTIPLAYER", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))), ), const SizedBox(width: 48), // Bilanciamento ], @@ -1050,11 +445,11 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { Row( children: [ Expanded( - child: _NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType), + child: NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType), ), const SizedBox(width: 10), Expanded( - child: _NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType), + child: NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType), ), ], ), @@ -1063,12 +458,12 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { : Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - _NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType), + NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType), const SizedBox(height: 20), Row( children: [ Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)), - Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("OPPURE", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))), + Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("OPPURE", style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))), Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)), ], ), @@ -1087,10 +482,10 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { ), child: TextField( controller: _codeController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5, - style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])), + style: getLobbyTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])), decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(vertical: 12), - hintText: "CODICE", hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "", + hintText: "CODICE", hintStyle: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "", filled: themeType != AppThemeType.doodle, fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05), enabledBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2.0), borderRadius: BorderRadius.circular(15)), @@ -1100,7 +495,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { ), ), const SizedBox(height: 15), - _NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType), + NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType), ], ), ), @@ -1109,13 +504,12 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { Row( children: [ Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)), - Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("LOBBY PUBBLICA", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))), + Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("LOBBY PUBBLICA", style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))), Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)), ], ), const SizedBox(height: 15), - // --- LA VERA E PROPRIA BACHECA PUBBLICA --- StreamBuilder( stream: _multiplayerService.getPublicRooms(), builder: (context, snapshot) { @@ -1125,8 +519,8 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { return Padding( - padding: const EdgeInsets.symmetric(vertical: 20.0), - child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))), + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))), ); } @@ -1151,8 +545,8 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { if (docs.isEmpty) { return Padding( - padding: const EdgeInsets.symmetric(vertical: 20.0), - child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))), + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))), ); } @@ -1185,8 +579,8 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { return Transform.rotate( angle: themeType == AppThemeType.doodle ? (index % 2 == 0 ? 0.01 : -0.01) : 0, child: Container( - margin: const EdgeInsets.only(bottom: 12), - padding: const EdgeInsets.all(12), + margin: const EdgeInsets.only(bottom: 15), + padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05), borderRadius: BorderRadius.circular(15), @@ -1195,27 +589,28 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { ), child: Row( children: [ - CircleAvatar(backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed)), - const SizedBox(width: 12), + CircleAvatar(radius: 25, backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed, size: 28)), + const SizedBox(width: 15), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Stanza di $host", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 16))), - const SizedBox(height: 4), - Text("Raggio: $r • $prettyShape • ${time ? 'A Tempo' : 'Relax'}", style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 11))), + Text("Stanza di $host", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 18))), + const SizedBox(height: 6), + Text("Raggio: $r • $prettyShape • ${time ? 'A Tempo' : 'Relax'}", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 12))), ], ), ), ElevatedButton( style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), elevation: themeType == AppThemeType.doodle ? 0 : 2, side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 2) : BorderSide.none, ), onPressed: () => _joinRoomByCode(doc.id), - child: Text("ENTRA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))), + child: Text("ENTRA", style: getLobbyTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))), ) ], ), @@ -1225,7 +620,6 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { ); } ), - const SizedBox(height: 20), ], ), ), diff --git a/lib/ui/multiplayer/lobby_widgets.dart b/lib/ui/multiplayer/lobby_widgets.dart new file mode 100644 index 0000000..102d217 --- /dev/null +++ b/lib/ui/multiplayer/lobby_widgets.dart @@ -0,0 +1,616 @@ +// =========================================================================== +// FILE: lib/ui/multiplayer/lobby_widgets.dart +// =========================================================================== + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'dart:math' as math; +import 'package:google_fonts/google_fonts.dart'; + +import '../../models/game_board.dart'; +import '../../core/theme_manager.dart'; +import '../../core/app_colors.dart'; + +TextStyle getLobbyTextStyle(AppThemeType themeType, TextStyle baseStyle) { + if (themeType == AppThemeType.doodle) { + return GoogleFonts.permanentMarker(textStyle: baseStyle); + } else if (themeType == AppThemeType.arcade) { + return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith( + fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null, + letterSpacing: 0.5, + )); + } else if (themeType == AppThemeType.grimorio) { + return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold)); + } else if (themeType == AppThemeType.music) { + return GoogleFonts.audiowide(textStyle: baseStyle.copyWith(letterSpacing: 1.5)); + } + return baseStyle; +} + +class NeonShapeButton extends StatelessWidget { + final IconData icon; + final String label; + final bool isSelected; + final ThemeColors theme; + final AppThemeType themeType; + final VoidCallback onTap; + final bool isLocked; + final bool isSpecial; + + const NeonShapeButton({ + super.key, required this.icon, required this.label, required this.isSelected, + required this.theme, required this.themeType, required this.onTap, + this.isLocked = false, this.isSpecial = false + }); + + Color _getDoodleColor() { + switch (label) { + case 'Rombo': return Colors.blue.shade700; + case 'Croce': return Colors.teal.shade700; + case 'Buco': return Colors.pink.shade600; + case 'Clessidra': return Colors.deepPurple.shade600; + case 'Caos': return Colors.blueGrey.shade800; + default: return Colors.blue.shade700; + } + } + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + Color doodleColor = isLocked ? Colors.grey : _getDoodleColor(); + double tilt = (label.length % 2 == 0) ? -0.03 : 0.04; + + return Transform.rotate( + angle: tilt, + child: GestureDetector( + onTap: isLocked ? null : onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), + transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0), + decoration: BoxDecoration( + color: isSelected ? doodleColor : Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), topRight: Radius.circular(8), + bottomLeft: Radius.circular(6), bottomRight: Radius.circular(18), + ), + border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: isSelected ? 2.5 : 1.5), + boxShadow: isSelected + ? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)] + : [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(isLocked ? Icons.lock : icon, color: isSelected ? Colors.white : doodleColor, size: 20), + const SizedBox(height: 2), + FittedBox(fit: BoxFit.scaleDown, child: Text(isLocked ? "Liv. 10" : label, style: getLobbyTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 9, fontWeight: FontWeight.w900, letterSpacing: 0.2)))), + ], + ), + ), + ), + ); + } + + Color mainColor = isSpecial && !isLocked ? Colors.purpleAccent : theme.playerBlue; + return GestureDetector( + onTap: isLocked ? null : onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOutCubic, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isLocked + ? [Colors.grey.withOpacity(0.1), Colors.black.withOpacity(0.2)] + : isSelected + ? [mainColor.withOpacity(0.3), mainColor.withOpacity(0.1)] + : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], + ), + border: Border.all( + color: isLocked ? Colors.transparent : (isSelected ? mainColor : Colors.white.withOpacity(0.1)), + width: isSelected ? 2 : 1, + ), + boxShadow: isLocked ? [] : isSelected + ? [BoxShadow(color: mainColor.withOpacity(0.5), blurRadius: 15, spreadRadius: 1, offset: const Offset(0, 0))] + : [ + BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), + BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(isLocked ? Icons.lock : icon, color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), size: 20), + const SizedBox(height: 4), + FittedBox(fit: BoxFit.scaleDown, child: Text(isLocked ? "Liv. 10" : label, style: getLobbyTextStyle(themeType, TextStyle(color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), fontSize: 9, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold)))), + ], + ), + ), + ); + } +} + +class NeonSizeButton extends StatelessWidget { + final String label; + final bool isSelected; + final ThemeColors theme; + final AppThemeType themeType; + final VoidCallback onTap; + + const NeonSizeButton({super.key, required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap}); + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + Color doodleColor = label == 'MAX' ? Colors.red.shade700 : Colors.blueGrey.shade600; + double tilt = (label == 'M' || label == 'MAX') ? 0.05 : -0.04; + + return Transform.rotate( + angle: tilt, + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + width: 42, height: 40, + transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0), + decoration: BoxDecoration( + color: isSelected ? doodleColor : Colors.white, + borderRadius: const BorderRadius.all(Radius.elliptical(25, 20)), + border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: 2), + boxShadow: isSelected + ? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)] + : [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)], + ), + child: Center( + child: FittedBox(fit: BoxFit.scaleDown, child: Text(label, style: getLobbyTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 13, fontWeight: FontWeight.w900)))), + ), + ), + ), + ); + } + + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeOutCubic, + width: 42, height: 42, + transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isSelected + ? [theme.playerRed.withOpacity(0.3), theme.playerRed.withOpacity(0.1)] + : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], + ), + border: Border.all(color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1), width: isSelected ? 2 : 1), + boxShadow: isSelected + ? [BoxShadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 15, spreadRadius: 1)] + : [ + BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), + BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), + ], + ), + child: Center( + child: FittedBox(fit: BoxFit.scaleDown, child: Text(label, style: getLobbyTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold)))), + ), + ), + ); + } +} + +class NeonTimeSwitch extends StatelessWidget { + final bool isTimeMode; + final ThemeColors theme; + final AppThemeType themeType; + final VoidCallback onTap; + + const NeonTimeSwitch({super.key, required this.isTimeMode, required this.theme, required this.themeType, required this.onTap}); + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + Color doodleColor = Colors.orange.shade700; + return Transform.rotate( + angle: -0.015, + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + transform: Matrix4.translationValues(0, isTimeMode ? 3 : 0, 0), + decoration: BoxDecoration( + color: isTimeMode ? doodleColor : Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), topRight: Radius.circular(15), + bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6), + ), + border: Border.all(color: isTimeMode ? theme.text : doodleColor.withOpacity(0.5), width: 2.5), + boxShadow: isTimeMode + ? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(4, 5), blurRadius: 0)] + : [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 20), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0)))), + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold)))), + ], + ), + ), + ], + ), + ), + ), + ); + } + + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isTimeMode + ? [Colors.amber.withOpacity(0.25), Colors.amber.withOpacity(0.05)] + : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], + ), + border: Border.all(color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1), width: isTimeMode ? 2 : 1), + boxShadow: isTimeMode + ? [BoxShadow(color: Colors.amber.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)] + : [ + BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), + BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 20), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5)))), + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 9, fontWeight: FontWeight.bold)))), + ], + ), + ), + ], + ), + ), + ); + } +} + +class NeonPrivacySwitch extends StatelessWidget { + final bool isPublic; + final ThemeColors theme; + final AppThemeType themeType; + final VoidCallback onTap; + + const NeonPrivacySwitch({super.key, required this.isPublic, required this.theme, required this.themeType, required this.onTap}); + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600; + return Transform.rotate( + angle: 0.015, + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0), + decoration: BoxDecoration( + color: isPublic ? doodleColor : Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), topRight: Radius.circular(8), + bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15), + ), + border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5), + boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isPublic ? 'STANZA PUBBLICA' : 'STANZA PRIVATA', style: getLobbyTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 10, letterSpacing: 1.0)))), + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isPublic ? 'In bacheca' : 'Invita con codice', style: getLobbyTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold)))), + ], + ), + ), + ], + ), + ), + ), + ); + } + + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isPublic + ? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)] + : [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)], + ), + border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1), + boxShadow: isPublic + ? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)] + : [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4))], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isPublic ? 'STANZA PUBBLICA' : 'STANZA PRIVATA', style: getLobbyTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 10, letterSpacing: 1.0)))), + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isPublic ? 'In bacheca' : 'Invita con codice', style: getLobbyTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold)))), + ], + ), + ), + ], + ), + ), + ); + } +} + +class NeonInviteFavoriteButton extends StatelessWidget { + final ThemeColors theme; + final AppThemeType themeType; + final VoidCallback onTap; + + const NeonInviteFavoriteButton({super.key, required this.theme, required this.themeType, required this.onTap}); + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + Color doodleColor = Colors.pink.shade600; + return Transform.rotate( + angle: -0.015, + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), topRight: Radius.circular(15), + bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6), + ), + border: Border.all(color: doodleColor.withOpacity(0.5), width: 2.5), + boxShadow: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.favorite, color: doodleColor, size: 20), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('PREFERITI', style: getLobbyTextStyle(themeType, TextStyle(color: doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0)))), + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('Invita amico', style: getLobbyTextStyle(themeType, TextStyle(color: doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold)))), + ], + ), + ), + ], + ), + ), + ), + ); + } + + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.pinkAccent.withOpacity(0.25), Colors.pinkAccent.withOpacity(0.05)], + ), + border: Border.all(color: Colors.pinkAccent, width: 1.5), + boxShadow: [BoxShadow(color: Colors.pinkAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.favorite, color: Colors.pinkAccent, size: 20), + const SizedBox(width: 8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('PREFERITI', style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5)))), + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('Invita amico', style: getLobbyTextStyle(themeType, TextStyle(color: Colors.pinkAccent.shade200, fontSize: 9, fontWeight: FontWeight.bold)))), + ], + ), + ), + ], + ), + ), + ); + } +} + +class NeonActionButton extends StatelessWidget { + final String label; + final Color color; + final VoidCallback onTap; + final ThemeColors theme; + final AppThemeType themeType; + + const NeonActionButton({super.key, required this.label, required this.color, required this.onTap, required this.theme, required this.themeType}); + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02; + return Transform.rotate( + angle: tilt, + child: GestureDetector( + onTap: onTap, + child: Container( + height: 50, + decoration: BoxDecoration( + color: color, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), topRight: Radius.circular(20), + bottomLeft: Radius.circular(25), bottomRight: Radius.circular(10), + ), + border: Border.all(color: theme.text, width: 3.0), + boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)], + ), + child: Center( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Text(label, style: getLobbyTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))), + ), + ), + ), + ), + ), + ); + } + + return GestureDetector( + onTap: onTap, + child: Container( + height: 50, + decoration: BoxDecoration( + gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [color.withOpacity(0.9), color.withOpacity(0.6)]), + borderRadius: BorderRadius.circular(15), + border: Border.all(color: Colors.white.withOpacity(0.3), width: 1.5), + boxShadow: [ + BoxShadow(color: Colors.black.withOpacity(0.5), offset: const Offset(4, 8), blurRadius: 12), + BoxShadow(color: color.withOpacity(0.3), offset: const Offset(0, 0), blurRadius: 15, spreadRadius: 1), + ], + ), + child: Center( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Text(label, style: getLobbyTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))), + ), + ), + ), + ), + ); + } +} + +class AnimatedCyberBorder extends StatefulWidget { + final Widget child; + const AnimatedCyberBorder({super.key, required this.child}); + @override + State createState() => _AnimatedCyberBorderState(); +} + +class _AnimatedCyberBorderState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + @override + void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); } + @override + void dispose() { _controller.dispose(); super.dispose(); } + @override + Widget build(BuildContext context) { + final theme = context.watch().currentColors; + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + painter: _CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed), + child: Container( + decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 20, spreadRadius: 2)]), + padding: const EdgeInsets.all(3), + child: widget.child, + ), + ); + }, + child: widget.child, + ); + } +} + +class _CyberBorderPainter extends CustomPainter { + final double animationValue; + final Color color1; + final Color color2; + + _CyberBorderPainter({required this.animationValue, required this.color1, required this.color2}); + + @override + void paint(Canvas canvas, Size size) { + final rect = Offset.zero & size; + final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(20)); + final Paint paint = Paint() + ..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect) + ..style = PaintingStyle.stroke + ..strokeWidth = 3.0 + ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 3); + canvas.drawRRect(rrect, paint); + } + + @override + bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue; +} \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 496218f..e31ae76 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,6 +11,7 @@ import cloud_firestore import firebase_app_check import firebase_auth import firebase_core +import package_info_plus import share_plus import shared_preferences_foundation @@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index f3c812c..d4a0bd6 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1401,6 +1401,8 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - package_info_plus (0.0.1): + - FlutterMacOS - PromisesObjC (2.4.0) - share_plus (0.0.1): - FlutterMacOS @@ -1416,6 +1418,7 @@ DEPENDENCIES: - firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`) - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -1458,6 +1461,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos FlutterMacOS: :path: Flutter/ephemeral + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos shared_preferences_foundation: @@ -1491,6 +1496,7 @@ SPEC CHECKSUMS: GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 3cc05eb..f4126ea 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -6,10 +6,22 @@ class MainFlutterWindow: NSWindow { let flutterViewController = FlutterViewController() let windowFrame = self.frame self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) + + // 1. Definiamo le proporzioni esatte da smartphone + let phoneSize = NSSize(width: 400, height: 800) + let newRect = NSRect(origin: windowFrame.origin, size: phoneSize) + self.setFrame(newRect, display: true) + + // 2. Blocchiamo il ridimensionamento! Il Mac non potrà più allargarla a dismisura + self.minSize = phoneSize + self.maxSize = phoneSize + + // 3. IL TRUCCO MAGICO: Cambiamo il nome del salvataggio automatico. + // Questo costringe macOS a dimenticare la vecchia finestra larga e usare questa nuova. + self.setFrameAutosaveName("TetraQMobileSimulatorWindow") RegisterGeneratedPlugins(registry: flutterViewController) super.awakeFromNib() } -} +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 75e7c78..a9dc377 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -557,6 +557,22 @@ packages: url: "https://pub.dev" source: hosted version: "9.3.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.dev" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e042f19..98e3eaa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: google_fonts: ^8.0.2 font_awesome_flutter: ^10.12.0 firebase_app_check: ^0.4.1+5 + package_info_plus: ^9.0.0 dev_dependencies: flutter_test: