From 8caecd401e1b07fef53292da0129f235c1898d4e Mon Sep 17 00:00:00 2001 From: Paolo Date: Sun, 15 Mar 2026 16:00:01 +0100 Subject: [PATCH] Auto-sync: 20260315_160000 --- .firebase/hosting.cHVibGlj.cache | 2 +- lib/services/storage_service.dart | 28 +++- lib/ui/admin/admin_screen.dart | 45 ++++-- lib/ui/game/game_screen.dart | 36 ++--- lib/ui/home/home_screen.dart | 99 +++++++------ lib/ui/multiplayer/lobby_screen.dart | 57 +++++--- lib/widgets/music_theme_widgets.dart | 137 ++++++++++++++---- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + macos/Podfile.lock | 6 + public/index.html | 115 ++++----------- pubspec.lock | 24 +++ pubspec.yaml | 1 + 12 files changed, 336 insertions(+), 216 deletions(-) diff --git a/.firebase/hosting.cHVibGlj.cache b/.firebase/hosting.cHVibGlj.cache index 40f0243..448d542 100644 --- a/.firebase/hosting.cHVibGlj.cache +++ b/.firebase/hosting.cHVibGlj.cache @@ -1,2 +1,2 @@ -index.html,1773344753424,0d5d4b835a7d632ad11d249230a15561286f2bfcd1da8305c6fb294d37e5da09 404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3 +index.html,1773586765860,5737ce966fa8786becaf7f36a32992cf44102fb3a217c226c30576c993b33e63 diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index fab05a1..4c72d07 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -9,7 +9,8 @@ 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 +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:device_info_plus/device_info_plus.dart'; // <-- NUOVO IMPORT class StorageService { static final StorageService instance = StorageService._internal(); @@ -89,19 +90,39 @@ class StorageService { // IDENTIFICA IL SISTEMA OPERATIVO E LA VERSIONE APP String currentPlatform = "Sconosciuta"; String appVersion = "N/D"; + String deviceModel = "Sconosciuto"; // <-- NUOVO: MODELLO HARDWARE if (!kIsWeb) { + // Leggi Piattaforma base if (Platform.isAndroid) currentPlatform = "Android"; else if (Platform.isIOS) currentPlatform = "iOS"; else if (Platform.isMacOS) currentPlatform = "macOS"; else if (Platform.isWindows) currentPlatform = "Windows"; + // Leggi Versione App try { PackageInfo packageInfo = await PackageInfo.fromPlatform(); appVersion = "${packageInfo.version}+${packageInfo.buildNumber}"; } catch(e) { debugPrint("Errore lettura versione: $e"); } + + // Leggi Modello Hardware + try { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + if (Platform.isAndroid) { + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + deviceModel = "${androidInfo.manufacturer} ${androidInfo.model}"; // Es. samsung SM-S928B + } else if (Platform.isIOS) { + IosDeviceInfo iosInfo = await deviceInfo.iosInfo; + deviceModel = iosInfo.utsname.machine; // Es. iPhone13,2 + } else if (Platform.isMacOS) { + MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo; + deviceModel = macInfo.model; // Es. MacBookPro16,1 + } + } catch(e) { + debugPrint("Errore lettura hardware: $e"); + } } // AGGIORNA IL TEMPO DI UTILIZZO @@ -109,7 +130,7 @@ class StorageService { 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 + _sessionStart = now; } int totalPlaytime = _prefs.getInt('totalPlaytime') ?? 0; @@ -123,7 +144,8 @@ class StorageService { 'ip': lastIp, 'city': lastCity, 'playtime': totalPlaytime, - 'appVersion': appVersion, // <-- NUOVO: Salva la versione + 'appVersion': appVersion, + 'deviceModel': deviceModel, // Salva il modello hardware }, SetOptions(merge: true)); } } catch(e) { diff --git a/lib/ui/admin/admin_screen.dart b/lib/ui/admin/admin_screen.dart index 645eedb..a73d702 100644 --- a/lib/ui/admin/admin_screen.dart +++ b/lib/ui/admin/admin_screen.dart @@ -33,7 +33,6 @@ class AdminScreen extends StatelessWidget { return Center(child: Text("Nessun giocatore trovato nel database.", style: TextStyle(color: theme.text))); } - // Ripristinata la lista completa senza nascondere PAOLO final docs = snapshot.data!.docs; return ListView.builder( @@ -50,8 +49,9 @@ class AdminScreen extends StatelessWidget { String platform = data['platform'] ?? 'Sconosciuta'; String ip = data['ip'] ?? 'N/D'; String city = data['city'] ?? 'N/D'; + String appVersion = data['appVersion'] ?? 'N/D'; + String deviceModel = data['deviceModel'] ?? 'N/D'; - // --- CALCOLO TEMPO UTILIZZO (HH:MM) --- int playtimeSec = data['playtime'] ?? 0; int hours = playtimeSec ~/ 3600; int minutes = (playtimeSec % 3600) ~/ 60; @@ -109,6 +109,8 @@ class AdminScreen extends StatelessWidget { Text("πŸ“ CittΓ : $city", style: TextStyle(color: theme.text, fontSize: 16)), const SizedBox(height: 10), Text("πŸ“± OS: $platform", style: TextStyle(color: theme.text, fontSize: 16)), + const SizedBox(height: 10), + Text("πŸ’» Hardware: $deviceModel", style: TextStyle(color: theme.text, fontSize: 16)), ], ), actions: [ @@ -140,7 +142,6 @@ class AdminScreen extends StatelessWidget { 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)), @@ -150,22 +151,36 @@ class AdminScreen extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), + // QUI È DOVE AVVENIVA IL CRASH! Ora usiamo Expanded e FittedBox Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Registrato il:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10)), - Text(createdStr, style: TextStyle(color: theme.text, fontSize: 12, fontWeight: FontWeight.bold)), - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FittedBox(fit: BoxFit.scaleDown, child: Text("Registrato il:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10))), + FittedBox(fit: BoxFit.scaleDown, child: Text(createdStr, style: TextStyle(color: theme.text, fontSize: 12, fontWeight: FontWeight.bold))), + ], + ), ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text("Ultimo Accesso:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10)), - Text(lastActiveStr, style: TextStyle(color: Colors.green, fontSize: 12, fontWeight: FontWeight.bold)), - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + FittedBox(fit: BoxFit.scaleDown, child: Text("Versione App:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10))), + FittedBox(fit: BoxFit.scaleDown, child: Text("v. $appVersion", style: TextStyle(color: theme.playerBlue, fontSize: 12, fontWeight: FontWeight.bold))), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + FittedBox(fit: BoxFit.scaleDown, child: Text("Ultimo Accesso:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10))), + FittedBox(fit: BoxFit.scaleDown, child: Text(lastActiveStr, style: TextStyle(color: Colors.green, fontSize: 12, fontWeight: FontWeight.bold))), + ], + ), ), ], ) diff --git a/lib/ui/game/game_screen.dart b/lib/ui/game/game_screen.dart index 2ce4542..a8ad080 100644 --- a/lib/ui/game/game_screen.dart +++ b/lib/ui/game/game_screen.dart @@ -328,7 +328,6 @@ class _GameScreenState extends State with TickerProviderStateMixin { Expanded( child: Center( child: Padding( - // PADDING RIDOTTO AL MINIMO: permette alla griglia di guadagnare pixel preziosi per allargarsi/alzarsi! padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0), child: LayoutBuilder( builder: (context, constraints) { @@ -344,7 +343,6 @@ class _GameScreenState extends State with TickerProviderStateMixin { width: actualWidth, height: actualHeight, child: Stack( children: [ - // --- IL VERO SFONDO SFOCATO SAGOMATO (ORA PER TUTTI I TEMI) --- Positioned.fill( child: ClipPath( clipper: _ArenaClipper(gameController.board), @@ -387,7 +385,6 @@ class _GameScreenState extends State with TickerProviderStateMixin { ), Padding( - // PADDING RIDOTTO IN BASSO: 10 al posto di 20, per far esplodere la griglia verticalmente padding: const EdgeInsets.only(bottom: 10.0, left: 20.0, right: 20.0, top: 5.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -437,10 +434,8 @@ class _GameScreenState extends State with TickerProviderStateMixin { backgroundColor: themeType == AppThemeType.doodle ? Colors.white : (bgImage != null ? Colors.transparent : theme.background), body: Stack( children: [ - // 1. Sfondo base a tinta unita Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background), - // 2. Griglia a quadretti (Doodle Theme) if (themeType == AppThemeType.doodle) Positioned.fill( child: CustomPaint( @@ -448,19 +443,21 @@ class _GameScreenState extends State with TickerProviderStateMixin { ), ), - // 3. Immagine di Sfondo if (bgImage != null) Positioned.fill( - child: Image.asset( - bgImage, - fit: BoxFit.cover, - alignment: Alignment.center, - color: themeType == AppThemeType.doodle ? Colors.white.withOpacity(0.5) : null, - colorBlendMode: themeType == AppThemeType.doodle ? BlendMode.lighten : null, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(bgImage!), + fit: BoxFit.cover, + colorFilter: themeType == AppThemeType.doodle + ? ColorFilter.mode(Colors.white.withOpacity(0.5), BlendMode.lighten) + : null, + ), + ), ), ), - // 4. Patina scura (Cyberpunk, Music, Arcade e Grimorio) if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio)) Positioned.fill( child: Container( @@ -473,18 +470,14 @@ class _GameScreenState extends State with TickerProviderStateMixin { ), ), - // 5. Effetto "Furia" o Timeout if (gameController.isTimeMode && !gameController.isCPUThinking && !gameController.isGameOver && gameController.timeLeft > 0 && gameController.timeLeft <= 5 && !gameController.isSetupPhase) Positioned.fill(child: BlitzBackgroundEffect(timeLeft: gameController.timeLeft, color: theme.playerRed, themeType: themeType)), - // 6. Testo degli Eventi if (gameController.effectText.isNotEmpty) Positioned.fill(child: SpecialEventBackgroundEffect(text: gameController.effectText, color: gameController.effectColor, themeType: themeType)), - // 7. Il Gioco Vero e Proprio Positioned.fill(child: gameContent), - // 8. Schermata Passaggio Dispositivo if (gameController.isSetupPhase && !_hideJokerMessage) Positioned.fill( child: Container( @@ -503,7 +496,6 @@ class _GameScreenState extends State with TickerProviderStateMixin { ), ), - // 9. Effetti di Vittoria if (gameController.isGameOver && gameController.board.scoreRed != gameController.board.scoreBlue) Positioned.fill(child: IgnorePointer(child: WinnerVFXOverlay(winnerColor: gameController.board.scoreRed > gameController.board.scoreBlue ? theme.playerRed : theme.playerBlue, themeType: themeType))), ], @@ -543,7 +535,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { } // =========================================================================== -// CLIPPER MAGICO: Ritaglia l'effetto sfocatura sull'esatta forma dell'arena +// CLIPPER MAGICO E ALTRI WIDGETS // =========================================================================== class _ArenaClipper extends CustomClipper { final GameBoard board; @@ -568,9 +560,7 @@ class _ArenaClipper extends CustomClipper { } return path; } - - @override - bool shouldReclip(covariant _ArenaClipper oldClipper) => true; + @override bool shouldReclip(covariant _ArenaClipper oldClipper) => true; } class _Particle { @@ -636,7 +626,6 @@ class _WinnerVFXOverlayState extends State with SingleTickerPr } }); } - @override void dispose() { _vfxController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return CustomPaint(painter: _VFXPainter(particles: _particles, themeType: widget.themeType), child: Container()); } } @@ -679,7 +668,6 @@ class _BouncingEmoji extends StatefulWidget { final String emoji; const _BouncingEmoji({required this.emoji}); @override State<_BouncingEmoji> createState() => _BouncingEmojiState(); } - class _BouncingEmojiState extends State<_BouncingEmoji> with SingleTickerProviderStateMixin { late AnimationController _ctrl; late Animation _anim; @override void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 500))..repeat(reverse: true); _anim = Tween(begin: -10, end: 10).animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut)); } diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 6cc3009..2fa6d2d 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -747,10 +747,19 @@ class _HomeScreenState extends State with WidgetsBindingObserver { BoxDecoration _glassBoxDecoration(ThemeColors theme, AppThemeType themeType) { return BoxDecoration( - color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05), + color: themeType == AppThemeType.doodle ? Colors.white : null, + // Sfumatura bianca inserita come richiesto! + gradient: themeType == AppThemeType.doodle ? null : LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Colors.white.withOpacity(0.25), + Colors.white.withOpacity(0.05), + ], + ), borderRadius: BorderRadius.circular(25), border: Border.all( - color: themeType == AppThemeType.doodle ? theme.text : Colors.white.withOpacity(0.2), + color: themeType == AppThemeType.doodle ? theme.text : Colors.white.withOpacity(0.3), width: themeType == AppThemeType.doodle ? 2 : 1.5, ), boxShadow: themeType == AppThemeType.doodle @@ -763,6 +772,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { Color inkColor = const Color(0xFF111122); return Padding( + // Padding ridotto al minimo padding: const EdgeInsets.only(top: 5.0, left: 15.0, right: 15.0, bottom: 10.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -815,6 +825,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { Container(width: 1, height: 20, color: (themeType == AppThemeType.doodle ? inkColor : Colors.white).withOpacity(0.2)), const SizedBox(width: 12), + // --- ICONA VOLUME REINTEGRATA --- AnimatedBuilder( animation: AudioService.instance, builder: (context, child) { @@ -868,13 +879,16 @@ class _HomeScreenState extends State with WidgetsBindingObserver { if (playerName.isEmpty) playerName = "GUEST"; int playerLevel = StorageService.instance.playerLevel; + final double screenHeight = MediaQuery.of(context).size.height; + final double vScale = (screenHeight / 920.0).clamp(0.50, 1.0); + Widget uiContent = SafeArea( child: Column( children: [ // 1. TOP BAR (Sempre visibile in alto) _buildTopBar(context, theme, themeType, playerName, playerLevel), - // 2. CONTENUTO SCORREVOLE (Logo + Bottoni) + // 2. CONTENUTO SCORREVOLE (Logo + Bottoni) con altezze dinamiche! Expanded( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), @@ -883,22 +897,24 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 20), + SizedBox(height: 20 * vScale), 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())); + if (playerName.toUpperCase() == 'PAOLO') { + _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( @@ -906,29 +922,31 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Text( loc.appTitle.toUpperCase(), style: getSharedTextStyle(themeType, TextStyle( - fontSize: 65, + fontSize: 65 * vScale, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? inkColor : theme.text, - letterSpacing: 10, + letterSpacing: 10 * vScale, 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)] + // Ombra chiara se il testo Γ¨ scuro + ? [const Shadow(color: Colors.white, offset: Offset(2.5, 2.5), blurRadius: 2), const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 2)] + // Ombra scura e visibile per tutti gli altri temi + : [Shadow(color: Colors.black.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 8), Shadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20)] )) ), ), ), ), ), - const SizedBox(height: 40), + SizedBox(height: 40 * vScale), // --- 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), + SizedBox(height: 12 * vScale), 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), + SizedBox(height: 12 * vScale), 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), + SizedBox(height: 30 * vScale), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -943,11 +961,11 @@ class _HomeScreenState extends State with WidgetsBindingObserver { 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), + SizedBox(height: 12 * vScale), _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), + SizedBox(height: 12 * vScale), _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), + SizedBox(height: 12 * vScale), Row( children: [ @@ -957,7 +975,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ], ), - const SizedBox(height: 12), + SizedBox(height: 12 * vScale), Row( children: [ @@ -969,7 +987,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ], ), ], - const SizedBox(height: 40), // Margine in fondo per assicurare che ci sia respiro + SizedBox(height: 40 * vScale), ], ), ), @@ -981,31 +999,30 @@ class _HomeScreenState extends State with WidgetsBindingObserver { return Scaffold( backgroundColor: bgImage != null ? Colors.transparent : theme.background, - extendBodyBehindAppBar: true, // Rimosso l'AppBar vuoto che "spingeva" giΓΉ il SafeArea! + extendBodyBehindAppBar: true, body: Stack( children: [ - // 1. Sfondo base a tinta unita Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background), - - // 2. Immagine di Sfondo per tutti i temi che la supportano if (bgImage != null) Positioned.fill( - child: Image.asset( - bgImage, - fit: BoxFit.cover, - alignment: Alignment.center, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(bgImage!), + fit: BoxFit.cover, + colorFilter: themeType == AppThemeType.doodle + ? ColorFilter.mode(Colors.white.withOpacity(0.5), BlendMode.lighten) + : null, + ), + ), ), ), - - // 3. Griglia a righe incrociate per il doodle if (themeType == AppThemeType.doodle) Positioned.fill( child: CustomPaint( painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)), ), ), - - // 4. Patina scura (Cyberpunk, Music, Arcade e Grimorio) if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio)) Positioned.fill( child: Container( @@ -1017,8 +1034,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ), ), ), - - // 5. Cavi musicali (Tema Musica) if (themeType == AppThemeType.music) Positioned.fill( child: IgnorePointer( @@ -1027,8 +1042,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ), ), ), - - // 6. UI Positioned.fill(child: uiContent), ], ), diff --git a/lib/ui/multiplayer/lobby_screen.dart b/lib/ui/multiplayer/lobby_screen.dart index faa3ecf..97cb467 100644 --- a/lib/ui/multiplayer/lobby_screen.dart +++ b/lib/ui/multiplayer/lobby_screen.dart @@ -631,31 +631,50 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { body: Stack( children: [ Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background), - if (bgImage != null) - Positioned.fill( - child: Image.asset( - bgImage, - fit: BoxFit.cover, - alignment: Alignment.center, - ), - ), - if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio)) - Positioned.fill( - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [Colors.black.withOpacity(0.6), Colors.black.withOpacity(0.9)] - ) - ), - ), - ), + if (themeType == AppThemeType.doodle) Positioned.fill( child: CustomPaint( painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)), ), ), + + if (bgImage != null) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(bgImage!), + fit: BoxFit.cover, + colorFilter: themeType == AppThemeType.doodle + ? ColorFilter.mode(Colors.white.withOpacity(0.5), BlendMode.lighten) + : null, + ), + ), + ), + ), + + if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio)) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, end: Alignment.bottomCenter, + colors: [Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.8)] + ) + ), + ), + ), + + if (themeType == AppThemeType.music) + Positioned.fill( + child: IgnorePointer( + child: CustomPaint( + painter: AudioCablesPainter(), + ), + ), + ), + Positioned.fill(child: uiContent), ], ), diff --git a/lib/widgets/music_theme_widgets.dart b/lib/widgets/music_theme_widgets.dart index a3936bf..d2c252e 100644 --- a/lib/widgets/music_theme_widgets.dart +++ b/lib/widgets/music_theme_widgets.dart @@ -16,53 +16,101 @@ class MusicCassetteCard extends StatelessWidget { final VoidCallback onTap; final AppThemeType themeType; - const MusicCassetteCard({super.key, required this.title, required this.subtitle, required this.neonColor, required this.angle, required this.leftIcon, required this.rightIcon, required this.onTap, required this.themeType}); + const MusicCassetteCard({ + super.key, + required this.title, + required this.subtitle, + required this.neonColor, + required this.angle, + required this.leftIcon, + required this.rightIcon, + required this.onTap, + required this.themeType + }); @override Widget build(BuildContext context) { + // Calcoliamo la scala in base all'altezza dello schermo per strizzare la cassetta + final double screenHeight = MediaQuery.of(context).size.height; + final double vScale = (screenHeight / 850.0).clamp(0.65, 1.0); + return Transform.rotate( angle: angle, child: GestureDetector( onTap: onTap, child: Container( - // Aumentato leggermente l'altezza a 125 per evitare l'overflow - height: 125, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), padding: const EdgeInsets.all(12), + height: 125 * vScale, // Altezza dinamica! + margin: EdgeInsets.symmetric(vertical: 8 * vScale, horizontal: 10), + padding: EdgeInsets.all(12 * vScale), decoration: BoxDecoration( - color: const Color(0xFF22222A), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 2), - boxShadow: [ BoxShadow(color: neonColor.withOpacity(0.5), blurRadius: 25, spreadRadius: 2), const BoxShadow(color: Colors.black54, offset: Offset(5, 10), blurRadius: 15) ] + color: const Color(0xFF22222A), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.black87, width: 2), + boxShadow: [ + BoxShadow(color: neonColor.withOpacity(0.5), blurRadius: 25, spreadRadius: 2), + const BoxShadow(color: Colors.black54, offset: Offset(5, 10), blurRadius: 15) + ] ), child: Column( children: [ Expanded( child: Container( - decoration: BoxDecoration(color: neonColor.withOpacity(0.15), borderRadius: BorderRadius.circular(4), border: Border.all(color: neonColor.withOpacity(0.5), width: 1.5)), + decoration: BoxDecoration( + color: neonColor.withOpacity(0.15), + borderRadius: BorderRadius.circular(4), + border: Border.all(color: neonColor.withOpacity(0.5), width: 1.5) + ), child: Row( children: [ - Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(leftIcon, color: neonColor, size: 28)), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12 * vScale), + child: Icon(leftIcon, color: neonColor, size: 28 * vScale) + ), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, - // Aggiunto minAxisSize per far stare il contenuto stretto mainAxisSize: MainAxisSize.min, children: [ - Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w900, shadows: [Shadow(color: neonColor, blurRadius: 10)]))))), - Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(subtitle, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold))))), + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white, fontSize: 20 * vScale, fontWeight: FontWeight.w900, shadows: [Shadow(color: neonColor, blurRadius: 10)]))) + ) + ), + Flexible( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text(subtitle, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white70, fontSize: 11 * vScale, fontWeight: FontWeight.bold))) + ) + ), ], ), ), - Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(rightIcon, color: neonColor, size: 28)), + Padding( + padding: EdgeInsets.symmetric(horizontal: 12 * vScale), + child: Icon(rightIcon, color: neonColor, size: 28 * vScale) + ), ], ), ), ), - const SizedBox(height: 10), + SizedBox(height: 10 * vScale), Container( - height: 35, width: 180, decoration: BoxDecoration(color: const Color(0xFF0D0D12), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white24, width: 1)), + height: 35 * vScale, + width: 180 * vScale, + decoration: BoxDecoration( + color: const Color(0xFF0D0D12), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.white24, width: 1) + ), child: Stack( alignment: Alignment.center, children: [ - Container(height: 2, width: 120, color: const Color(0xFF333333)), - Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildSpool(), _buildSpool() ]), + Container(height: 2, width: 120 * vScale, color: const Color(0xFF333333)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ _buildSpool(vScale), _buildSpool(vScale) ] + ), ], ), ), @@ -73,10 +121,22 @@ class MusicCassetteCard extends StatelessWidget { ); } - Widget _buildSpool() { + Widget _buildSpool(double vScale) { return Container( - width: 26, height: 26, decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white70, border: Border.all(color: Colors.black87, width: 5)), - child: Center(child: Container(width: 6, height: 6, decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.black))), + width: 26 * vScale, + height: 26 * vScale, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white70, + border: Border.all(color: Colors.black87, width: 5 * vScale) + ), + child: Center( + child: Container( + width: 6 * vScale, + height: 6 * vScale, + decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.black) + ) + ), ); } } @@ -88,40 +148,61 @@ class MusicKnobCard extends StatelessWidget { final AppThemeType themeType; final Color? iconColor; - const MusicKnobCard({super.key, required this.title, required this.icon, required this.onTap, required this.themeType, this.iconColor}); + const MusicKnobCard({ + super.key, + required this.title, + required this.icon, + required this.onTap, + required this.themeType, + this.iconColor + }); @override Widget build(BuildContext context) { + // Adattiamo anche le manopole in base all'altezza dello schermo + final double screenHeight = MediaQuery.of(context).size.height; + final double vScale = (screenHeight / 850.0).clamp(0.65, 1.0); + return GestureDetector( onTap: onTap, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( - width: 65, height: 65, + width: 65 * vScale, + height: 65 * vScale, decoration: BoxDecoration( - shape: BoxShape.circle, color: const Color(0xFF222222), border: Border.all(color: const Color(0xFF111111), width: 2), - boxShadow: const [BoxShadow(color: Colors.black87, blurRadius: 10, offset: Offset(2, 6)), BoxShadow(color: Colors.white12, blurRadius: 2, offset: Offset(-1, -1))], + shape: BoxShape.circle, + color: const Color(0xFF222222), + border: Border.all(color: const Color(0xFF111111), width: 2), + boxShadow: const [ + BoxShadow(color: Colors.black87, blurRadius: 10, offset: Offset(2, 6)), + BoxShadow(color: Colors.white12, blurRadius: 2, offset: Offset(-1, -1)) + ], ), child: Padding( - padding: const EdgeInsets.all(6.0), + padding: EdgeInsets.all(6.0 * vScale), child: Container( decoration: BoxDecoration( - shape: BoxShape.circle, border: Border.all(color: Colors.black54, width: 1), + shape: BoxShape.circle, + border: Border.all(color: Colors.black54, width: 1), gradient: const SweepGradient(colors: [Color(0xFF555555), Color(0xFFAAAAAA), Color(0xFF555555), Color(0xFF222222), Color(0xFF555555)]), ), child: Padding( - padding: const EdgeInsets.all(4.0), + padding: EdgeInsets.all(4.0 * vScale), child: Container( decoration: const BoxDecoration(shape: BoxShape.circle, color: Color(0xFF1A1A1A)), - child: Center(child: Icon(icon, color: iconColor ?? Colors.white70, size: 20)), + child: Center(child: Icon(icon, color: iconColor ?? Colors.white70, size: 20 * vScale)), ), ), ), ), ), - const SizedBox(height: 10), - FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.0)))), + SizedBox(height: 10 * vScale), + FittedBox( + fit: BoxFit.scaleDown, + child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white70, fontSize: 11 * vScale, fontWeight: FontWeight.bold, letterSpacing: 1.0))) + ), ], ), ); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e31ae76..df55aed 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,6 +8,7 @@ import Foundation import app_links import audioplayers_darwin import cloud_firestore +import device_info_plus import firebase_app_check import firebase_auth import firebase_core @@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index d4a0bd6..499324c 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1207,6 +1207,8 @@ PODS: - Firebase/Firestore (~> 12.9.0) - firebase_core - FlutterMacOS + - device_info_plus (0.0.1): + - FlutterMacOS - Firebase/AppCheck (12.9.0): - Firebase/CoreOnly - FirebaseAppCheck (~> 12.9.0) @@ -1414,6 +1416,7 @@ DEPENDENCIES: - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) - cloud_firestore (from `Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos`) + - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - firebase_app_check (from `Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos`) - firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`) - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) @@ -1453,6 +1456,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos cloud_firestore: :path: Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos + device_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos firebase_app_check: :path: Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos firebase_auth: @@ -1475,6 +1480,7 @@ SPEC CHECKSUMS: audioplayers_darwin: 761f2948df701d05b5db603220c384fb55720012 BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508 cloud_firestore: a2a9382e6cc4dd07345748b904b3b194ea46be44 + device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 Firebase: 065f2bb395062046623036d8e6dc857bc2521d56 firebase_app_check: 1ea404b52b0910bf632b1ea2e00ceb8d1730cb44 firebase_auth: 8db6796451d9aa44d4cc49b3e757865c65ce170f diff --git a/public/index.html b/public/index.html index 4237f9e..6f4e891 100644 --- a/public/index.html +++ b/public/index.html @@ -1,89 +1,38 @@ - - - - - Welcome to Firebase Hosting + + + + - - - - - - - - - - - - - - + Gioca a TetraQ! + + - - - -
-

Welcome

-

Firebase Hosting Setup Complete

-

You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!

- Open Hosting Documentation -
-

Firebase SDK Loading…

+ - - - + // Se Γ¨ Android + if (/android/i.test(userAgent)) { + window.location.href = "https://play.google.com/store/apps/details?id=com.amastra.tetraq"; + return; + } + + // Se Γ¨ da PC (o non riconosciuto), lo mandiamo alla tua pagina Google Sites + window.location.href = "https://sites.google.com/view/tetraq/home-page"; + } + + + +

+ Apertura in corso... πŸš€ +

+ + \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index a9dc377..b535e12 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -233,6 +233,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c" + url: "https://pub.dev" + source: hosted + version: "12.3.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" fake_async: dependency: transitive description: @@ -890,6 +906,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 98e3eaa..4d4b213 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: font_awesome_flutter: ^10.12.0 firebase_app_check: ^0.4.1+5 package_info_plus: ^9.0.0 + device_info_plus: ^12.3.0 dev_dependencies: flutter_test: