diff --git a/.DS_Store b/.DS_Store index 413fbec..2cd10c5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/assets/audio/bgm/Music_Loop.mp3 b/assets/audio/bgm/Music_Loop.mp3 new file mode 100644 index 0000000..2ce9fb3 Binary files /dev/null and b/assets/audio/bgm/Music_Loop.mp3 differ diff --git a/assets/images/music_bg.jpg b/assets/images/music_bg.jpg new file mode 100644 index 0000000..c79636e Binary files /dev/null and b/assets/images/music_bg.jpg differ diff --git a/lib/.DS_Store b/lib/.DS_Store index 45c664f..0dbee05 100644 Binary files a/lib/.DS_Store and b/lib/.DS_Store differ diff --git a/lib/core/app_colors.dart b/lib/core/app_colors.dart index 56c4a96..aa2d31c 100644 --- a/lib/core/app_colors.dart +++ b/lib/core/app_colors.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio } +enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio, music } // <-- Aggiunto 'music' class ThemeColors { final Color background; @@ -49,6 +49,15 @@ class AppColors { playerRed: Color(0xFFE91E63), playerBlue: Color(0xFF4FC3F7), text: Color(0xFFFFF3E0), ); + // --- NUOVO TEMA MUSICA --- + static const ThemeColors music = ThemeColors( + background: Color(0xFF120B29), // Viola scuro (stile Synthwave) + gridLine: Color(0xFF6A1B9A), // Viola elettrico + playerRed: Color(0xFFFF2A6D), // Rosa acceso + playerBlue: Color(0xFF05D5FF), // Ciano + text: Color(0xFFE0E0E0), + ); + static ThemeColors getTheme(AppThemeType type) { switch (type) { case AppThemeType.doodle: return doodle; @@ -56,6 +65,7 @@ class AppColors { case AppThemeType.cyberpunk: return cyberpunk; case AppThemeType.arcade: return arcade; case AppThemeType.grimorio: return grimorio; + case AppThemeType.music: return music; } } } @@ -68,6 +78,7 @@ class ThemeIcons { case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip; case AppThemeType.arcade: return FontAwesomeIcons.coins; case AppThemeType.grimorio: return FontAwesomeIcons.crown; + case AppThemeType.music: return FontAwesomeIcons.compactDisc; // CD/Vinile per i punti } } @@ -78,6 +89,7 @@ class ThemeIcons { case AppThemeType.cyberpunk: return FontAwesomeIcons.bug; case AppThemeType.arcade: return FontAwesomeIcons.ghost; case AppThemeType.grimorio: return FontAwesomeIcons.hatWizard; + case AppThemeType.music: return FontAwesomeIcons.volumeXmark; // Muto/Errore per la bomba } } @@ -88,6 +100,7 @@ class ThemeIcons { case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired; case AppThemeType.arcade: return FontAwesomeIcons.shuffle; case AppThemeType.grimorio: return FontAwesomeIcons.hurricane; + case AppThemeType.music: return FontAwesomeIcons.sliders; // Fader da DJ } } @@ -98,6 +111,7 @@ class ThemeIcons { case AppThemeType.cyberpunk: return FontAwesomeIcons.robot; case AppThemeType.arcade: return FontAwesomeIcons.gamepad; case AppThemeType.grimorio: return FontAwesomeIcons.masksTheater; + case AppThemeType.music: return FontAwesomeIcons.headphones; // Cuffie per il Jolly } } @@ -108,14 +122,17 @@ class ThemeIcons { case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved; case AppThemeType.arcade: return FontAwesomeIcons.powerOff; case AppThemeType.grimorio: return FontAwesomeIcons.meteor; + case AppThemeType.music: return FontAwesomeIcons.pause; // Pausa per il blocco vuoto } } static IconData ice(AppThemeType type) { + if (type == AppThemeType.music) return FontAwesomeIcons.music; // Nota musicale ghiacciata return FontAwesomeIcons.snowflake; } static IconData multiplier(AppThemeType type) { + if (type == AppThemeType.music) return FontAwesomeIcons.forwardFast; // Fast Forward per il x2 return FontAwesomeIcons.bolt; } } \ No newline at end of file diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index b49f706..3f09fee 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -60,6 +60,9 @@ class AudioService extends ChangeNotifier { case AppThemeType.grimorio: audioPath = 'audio/bgm/Grimorio_Astral.mp3'; break; + case AppThemeType.music: + audioPath = 'audio/bgm/Music_Loop.mp3'; // <-- DEVI INSERIRE QUESTO FILE IN ASSETS + break; } if (audioPath.isNotEmpty) { @@ -80,6 +83,7 @@ class AudioService extends ChangeNotifier { String file = ''; switch (theme) { case AppThemeType.arcade: + case AppThemeType.music: // Usiamo l'effetto arcade o cyber per la musica file = 'minimal_line.wav'; break; case AppThemeType.doodle: case AppThemeType.wood: @@ -103,6 +107,7 @@ class AudioService extends ChangeNotifier { String file = ''; switch (theme) { case AppThemeType.arcade: + case AppThemeType.music: file = 'minimal_box.wav'; break; case AppThemeType.doodle: case AppThemeType.wood: diff --git a/lib/ui/game/game_screen.dart b/lib/ui/game/game_screen.dart index 186206b..d790a34 100644 --- a/lib/ui/game/game_screen.dart +++ b/lib/ui/game/game_screen.dart @@ -25,6 +25,8 @@ TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) { )); } 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; } @@ -79,7 +81,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { if (myName.isEmpty) myName = "TU"; String nameRed = controller.isOnline ? controller.onlineHostName.toUpperCase() : myName; - String nameBlue = controller.isOnline ? controller.onlineGuestName.toUpperCase() : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU"); + String nameBlue = controller.isOnline ? controller.onlineGuestName.toUpperCase() : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? "VERDE" : "BLU"); if (controller.isVsCPU) nameBlue = "CPU"; String winnerText = ""; Color winnerColor = theme.text; @@ -119,7 +121,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { color: Colors.green.withOpacity(0.15), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.greenAccent, width: 1.5), - boxShadow: themeType == AppThemeType.cyberpunk ? [const BoxShadow(color: Colors.greenAccent, blurRadius: 10, spreadRadius: -5)] : [], + boxShadow: (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) ? [const BoxShadow(color: Colors.greenAccent, blurRadius: 10, spreadRadius: -5)] : [], ), child: Text("+ ${controller.lastMatchXP} XP", style: _getTextStyle(themeType, const TextStyle(color: Colors.greenAccent, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: 1.5))), ), @@ -194,7 +196,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { titleText = "Nascondi il tuo Jolly!"; subtitleText = "(Tocca qui per nascondere)"; } else { - String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? "VERDE" : "BLU"); + String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? "VERDE" : "BLU"); titleText = "TURNO GIOCATORE $pName"; subtitleText = "Passa il dispositivo.\nL'avversario NON deve guardare!\n\n(Tocca qui quando sei pronto)"; } @@ -204,7 +206,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.yellowAccent : theme.playerBlue, size: 50), + Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.yellowAccent : theme.playerBlue, size: 50), const SizedBox(height: 15), Text( titleText, @@ -229,8 +231,8 @@ class _GameScreenState extends State with TickerProviderStateMixin { ), ); - if (themeType == AppThemeType.cyberpunk) { - return Container(decoration: BoxDecoration(color: Colors.black.withOpacity(0.9), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.yellowAccent, width: 2), boxShadow: [const BoxShadow(color: Colors.yellowAccent, blurRadius: 15, spreadRadius: 0)]), child: content); + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) { + return Container(decoration: BoxDecoration(color: Colors.black.withOpacity(0.9), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.purpleAccent, width: 2), boxShadow: [BoxShadow(color: Colors.purpleAccent.withOpacity(0.6), blurRadius: 15, spreadRadius: 0)]), child: content); } else if (themeType == AppThemeType.doodle) { return Container(decoration: BoxDecoration(color: const Color(0xFFF9F9F9), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 3), boxShadow: const [BoxShadow(color: Colors.black26, offset: Offset(6, 6))]), child: content); } else if (themeType == AppThemeType.wood) { @@ -289,8 +291,10 @@ class _GameScreenState extends State with TickerProviderStateMixin { String? bgImage; if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg'; if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; + if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; + if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg'; - Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : Colors.black; + Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.white : Colors.black; Widget emojiBar = const SizedBox(); if (gameController.isOnline && !gameController.isGameOver) { @@ -298,9 +302,9 @@ class _GameScreenState extends State with TickerProviderStateMixin { emojiBar = Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration( - color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8), + color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(30), - border: Border.all(color: themeType == AppThemeType.cyberpunk ? theme.playerBlue.withOpacity(0.3) : Colors.white24, width: 2), + border: Border.all(color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music ? theme.playerBlue.withOpacity(0.3) : Colors.white24, width: 2), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -382,10 +386,10 @@ class _GameScreenState extends State with TickerProviderStateMixin { Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.4), offset: const Offset(0, 4), blurRadius: 5)]), child: TextButton.icon( - style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))), - icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : theme.text, size: 20), + style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))), + icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, size: 20), onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(context); }, - label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))), + label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))), ), ), ], @@ -406,23 +410,25 @@ class _GameScreenState extends State with TickerProviderStateMixin { canPop: true, onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); }, child: Scaffold( - // L'impostazione dello sfondo dipende dal tema - backgroundColor: themeType == AppThemeType.aurora ? Colors.transparent : (bgImage != null ? Colors.transparent : theme.background), + backgroundColor: bgImage != null ? Colors.transparent : theme.background, body: Container( - // GRADIENTE AURORA IN BACKGROUND! - decoration: themeType == AppThemeType.aurora - ? const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF2A0845), Color(0xFFDD2476), Color(0xFFFF512F)], - ), - ) - : (bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover, colorFilter: themeType == AppThemeType.doodle ? ColorFilter.mode(Colors.white.withOpacity(0.7), BlendMode.lighten) : null)) : null), + decoration: bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover, colorFilter: themeType == AppThemeType.doodle ? ColorFilter.mode(Colors.white.withOpacity(0.7), BlendMode.lighten) : null)) : null, child: CustomPaint( - // painter rimosso per supportare il nuovo sfondo a gradiente + painter: themeType == AppThemeType.doodle ? FullScreenGridPainter(Colors.black.withOpacity(0.06)) : null, child: Stack( children: [ + if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music)) + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, end: Alignment.bottomCenter, + colors: [Colors.black.withOpacity(0.3), Colors.black.withOpacity(0.8)] + ) + ), + ), + ), + 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)), @@ -434,7 +440,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { if (gameController.isSetupPhase && !_hideJokerMessage) Positioned.fill( child: Container( - color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora + color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.black.withOpacity(0.98) : theme.background.withOpacity(0.98), child: Center( @@ -520,7 +526,7 @@ class _WinnerVFXOverlayState extends State with SingleTickerPr } void _initParticles(Size screenSize) { - int particleCount = widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.aurora ? 150 : 100; + int particleCount = widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.music ? 150 : 100; if (widget.themeType == AppThemeType.arcade) particleCount = 80; if (widget.themeType == AppThemeType.grimorio) particleCount = 120; @@ -530,7 +536,7 @@ class _WinnerVFXOverlayState extends State with SingleTickerPr else if (widget.themeType == AppThemeType.wood) { palette = [Colors.orangeAccent, Colors.yellow, Colors.red, Colors.white]; } else if (widget.themeType == AppThemeType.arcade) { palette = [widget.winnerColor, Colors.white, Colors.greenAccent]; } else if (widget.themeType == AppThemeType.grimorio) { palette = [widget.winnerColor, Colors.deepPurpleAccent, Colors.white]; } - else if (widget.themeType == AppThemeType.aurora) { palette = [widget.winnerColor, Colors.white, Colors.orangeAccent, Colors.pinkAccent]; } + else if (widget.themeType == AppThemeType.music) { palette.add(Colors.pinkAccent); palette.add(Colors.cyanAccent); } for (int i = 0; i < particleCount; i++) { double speed = _rand.nextDouble() * 20 + 5; @@ -543,7 +549,7 @@ class _WinnerVFXOverlayState extends State with SingleTickerPr setState(() { for (var p in _particles) { p.x += p.vx; p.y += p.vy; - if (widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.aurora) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; } + if (widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.music) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; } else if (widget.themeType == AppThemeType.wood) { p.vy -= 0.2; p.x += math.sin(p.y * 0.05) * 2; } else if (widget.themeType == AppThemeType.arcade) { p.vy += 0.3; p.spin = 0; p.angle = 0; } else if (widget.themeType == AppThemeType.grimorio) { p.vy -= 0.1; p.x += math.sin(p.y * 0.02) * 1.5; p.size *= 0.995; } @@ -566,7 +572,7 @@ class _VFXPainter extends CustomPainter { for (var p in particles) { if (p.size < 0.5) continue; final paint = Paint()..color = p.color..style = PaintingStyle.fill; - if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.aurora) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); } + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); } canvas.save(); canvas.translate(p.x, p.y); canvas.rotate(p.angle); if (themeType == AppThemeType.doodle) { @@ -603,7 +609,6 @@ class _BouncingEmojiState extends State<_BouncingEmoji> with SingleTickerProvide @override Widget build(BuildContext context) { return AnimatedBuilder(animation: _anim, builder: (ctx, child) => Transform.translate(offset: Offset(0, _anim.value), child: Container(padding: const EdgeInsets.all(8), decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle, boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 5)]), child: Text(widget.emoji, style: const TextStyle(fontSize: 32))))); } } -// Mantengo la definizione per evitare buchi, ma ora Aurora usa il LinearGradient! class FullScreenGridPainter extends CustomPainter { final Color gridColor; FullScreenGridPainter(this.gridColor); @override void paint(Canvas canvas, Size size) { final Paint paperGridPaint = Paint()..color = gridColor..strokeWidth = 1.0..style = PaintingStyle.stroke; double paperStep = 20.0; for (double i = 0; i <= size.width; i += paperStep) canvas.drawLine(Offset(i, 0), Offset(i, size.height), paperGridPaint); for (double i = 0; i <= size.height; i += paperStep) canvas.drawLine(Offset(0, i), Offset(size.width, i), paperGridPaint); } diff --git a/lib/ui/game/score_board.dart b/lib/ui/game/score_board.dart index d4a0206..be08260 100644 --- a/lib/ui/game/score_board.dart +++ b/lib/ui/game/score_board.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -// Import separati e puliti +import 'package:google_fonts/google_fonts.dart'; import '../../logic/game_controller.dart'; import '../../models/game_board.dart'; import '../../core/theme_manager.dart'; @@ -12,6 +12,20 @@ import '../../services/audio_service.dart'; import '../../core/app_colors.dart'; import '../../services/storage_service.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; +} + class ScoreBoard extends StatefulWidget { const ScoreBoard({super.key}); @@ -50,7 +64,7 @@ class _ScoreBoardState extends State { return Container( padding: const EdgeInsets.only(top: 10, bottom: 20, left: 20, right: 20), decoration: BoxDecoration( - color: theme.background.withOpacity(0.95), + color: themeType == AppThemeType.doodle ? theme.background : theme.background.withOpacity(0.95), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), @@ -66,20 +80,26 @@ class _ScoreBoardState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _PlayerScore(color: theme.playerRed, score: redScore, isTurn: isRedTurn, textColor: theme.text, title: nameRed), + _PlayerScore(color: theme.playerRed, score: redScore, isTurn: isRedTurn, textColor: theme.text, title: nameRed, themeType: themeType), Column( mainAxisSize: MainAxisSize.min, children: [ Text( "TETRAQ", - style: TextStyle( + style: _getTextStyle(themeType, TextStyle( fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 4, - shadows: [Shadow(color: Colors.black.withOpacity(0.3), offset: const Offset(1, 2), blurRadius: 2)] - ) + shadows: themeType == AppThemeType.doodle + ? [ + // EFFETTO RILIEVO (Luce in alto a sx, ombra in basso a dx) + const Shadow(color: Colors.white, offset: Offset(-1.5, -1.5), blurRadius: 1), + Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(1.5, 1.5), blurRadius: 2), + ] + : [Shadow(color: Colors.black.withOpacity(0.3), offset: const Offset(1, 2), blurRadius: 2)] + )) ), IconButton( icon: Icon(isMuted ? Icons.volume_off : Icons.volume_up, color: theme.text.withOpacity(0.7)), @@ -92,7 +112,7 @@ class _ScoreBoardState extends State { ], ), - _PlayerScore(color: theme.playerBlue, score: blueScore, isTurn: !isRedTurn, textColor: theme.text, title: nameBlue), + _PlayerScore(color: theme.playerBlue, score: blueScore, isTurn: !isRedTurn, textColor: theme.text, title: nameBlue, themeType: themeType), ], ), ); @@ -105,15 +125,16 @@ class _PlayerScore extends StatelessWidget { final bool isTurn; final Color textColor; final String title; + final AppThemeType themeType; - const _PlayerScore({required this.color, required this.score, required this.isTurn, required this.textColor, required this.title}); + const _PlayerScore({required this.color, required this.score, required this.isTurn, required this.textColor, required this.title, required this.themeType}); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ - Text(title, style: TextStyle(fontWeight: FontWeight.bold, color: isTurn ? color : textColor.withOpacity(0.5), fontSize: 12)), + Text(title, style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: isTurn ? color : textColor.withOpacity(0.5), fontSize: 12))), const SizedBox(height: 5), AnimatedContainer( duration: const Duration(milliseconds: 300), @@ -126,7 +147,7 @@ class _PlayerScore extends StatelessWidget { BoxShadow(color: color.withOpacity(0.5), offset: const Offset(0, 4), blurRadius: 6) ] : [], ), - child: Text('$score', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: isTurn ? Colors.white : textColor.withOpacity(0.5))), + child: Text('$score', style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: isTurn ? Colors.white : textColor.withOpacity(0.5)))), ), ], ); diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 1499cdb..ce6e9fb 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -11,6 +11,7 @@ import 'dart:math' as math; import 'package:google_fonts/google_fonts.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // Aggiunto per le nuove icone musicali import '../../logic/game_controller.dart'; import '../../core/theme_manager.dart'; @@ -36,6 +37,8 @@ TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) { )); } 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; } @@ -535,7 +538,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ), ); - if (themeType == AppThemeType.cyberpunk) dialogContent = _AnimatedCyberBorder(child: dialogContent); + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) dialogContent = _AnimatedCyberBorder(child: dialogContent); return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent); }, ); @@ -754,8 +757,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver { decoration: BoxDecoration( gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]), borderRadius: BorderRadius.circular(25), - border: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5), - boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))], + border: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5), + boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))], ), child: SingleChildScrollView( physics: const BouncingScrollPhysics(), @@ -851,7 +854,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ), ); - if (themeType == AppThemeType.cyberpunk) { + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) { dialogContent = _AnimatedCyberBorder(child: dialogContent); } @@ -1053,7 +1056,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ), ); - if (themeType == AppThemeType.cyberpunk) content = _AnimatedCyberBorder(child: content); + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) content = _AnimatedCyberBorder(child: content); return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: content); } ); @@ -1134,8 +1137,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver { decoration: BoxDecoration( gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.background.withOpacity(0.95), theme.background.withOpacity(0.8)]), borderRadius: BorderRadius.circular(25), - border: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5), - boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))], + border: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5), + boxShadow: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 20, offset: const Offset(4, 10))], ), child: SingleChildScrollView( physics: const BouncingScrollPhysics(), @@ -1182,7 +1185,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ), ); - if (themeType == AppThemeType.cyberpunk) dialogContent = _AnimatedCyberBorder(child: dialogContent); + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) dialogContent = _AnimatedCyberBorder(child: dialogContent); return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), child: dialogContent); }, ); @@ -1192,7 +1195,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { if (themeType == AppThemeType.cyberpunk) { return _AnimatedCyberBorder(child: card); } - return card; + return card; // La card musicale ha già i propri effetti integrati } @override @@ -1208,8 +1211,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver { String? bgImage; if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg'; - if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; + if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg'; int wins = StorageService.instance.wins; int losses = StorageService.instance.losses; @@ -1306,9 +1309,9 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ), child: Row( children: [ - Icon(Icons.emoji_events, color: Colors.amber.shade600, size: 20), const SizedBox(width: 6), + Icon(themeType == AppThemeType.music ? FontAwesomeIcons.microphone : Icons.emoji_events, color: Colors.amber.shade600, size: 16), const SizedBox(width: 6), Text("$wins", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), const SizedBox(width: 12), - Icon(Icons.sentiment_very_dissatisfied, color: theme.playerRed.withOpacity(0.8), size: 20), const SizedBox(width: 6), + 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: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), ], ), @@ -1322,7 +1325,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { Center( child: Transform.rotate( angle: themeType == AppThemeType.doodle ? -0.04 : 0, - // --- IL TRUCCO DELLO SVILUPPATORE PROTETTO --- child: GestureDetector( onTap: () { _debugTapCount++; @@ -1330,16 +1332,10 @@ class _HomeScreenState extends State with WidgetsBindingObserver { StorageService.instance.addXP(2000); setState(() {}); ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text("🛠 DEBUG MODE: +20 Livelli!", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), - backgroundColor: Colors.purpleAccent, - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), - ) + SnackBar(content: Text("🛠 DEBUG MODE: +20 Livelli!", style: _getTextStyle(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; // Resetta il contatore - // APRE LA DASHBOARD SEGRETA! + _debugTapCount = 0; Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen())); } }, @@ -1352,7 +1348,12 @@ class _HomeScreenState extends State with WidgetsBindingObserver { fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? inkColor : theme.text, letterSpacing: 10, - shadows: themeType == AppThemeType.doodle || themeType == AppThemeType.arcade ? [] : [ + 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), ] @@ -1365,36 +1366,68 @@ class _HomeScreenState extends State with WidgetsBindingObserver { const Spacer(), - // --- NUOVA LISTA BOTTONI --- - 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), + // --- GESTIONE DEI 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: _showLeaderboardDialog)), + Expanded(child: _MusicKnobCard(title: loc.questsTitle, icon: FontAwesomeIcons.microphoneLines, themeType: themeType, onTap: _showDailyQuestsDialog)), + 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: _showTutorialDialog)), + ], + ), + ] else ...[ + // --- LISTA BOTTONI ORIGINALE --- + 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: _showLeaderboardDialog, 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: _showDailyQuestsDialog, compact: true), themeType)), - ], - ), + 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: _showLeaderboardDialog, 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: _showDailyQuestsDialog, compact: true), themeType)), + ], + ), - const SizedBox(height: 12), + 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: _showTutorialDialog, compact: true), themeType)), - ], - ), - ], - ), + 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: _showTutorialDialog, compact: true), themeType)), + ], + ), + ], + ), + ], const SizedBox(height: 10), ], ), @@ -1407,27 +1440,43 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); return Scaffold( - backgroundColor: bgImage != null ? Colors.transparent : theme.background, + backgroundColor: themeType == AppThemeType.doodle ? Colors.white : (bgImage != null ? Colors.transparent : theme.background), body: Stack( children: [ - Container(color: theme.background), + Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background), + if (themeType == AppThemeType.doodle) + Positioned.fill( + child: CustomPaint( + painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)), + ), + ), if (bgImage != null) Positioned.fill( child: Image.asset(bgImage, fit: BoxFit.cover, alignment: Alignment.center), ), if (bgImage != null) Positioned.fill( - child: themeType == AppThemeType.cyberpunk + child: (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) ? Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [Colors.black.withOpacity(0.2), Colors.black.withOpacity(0.7)] + colors: [Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.8)] ) ), ) : const SizedBox(), ), + + if (themeType == AppThemeType.music) + Positioned.fill( + child: IgnorePointer( + child: CustomPaint( + painter: _AudioCablesPainter(), + ), + ), + ), + Positioned.fill(child: uiContent), ], ), @@ -1460,6 +1509,249 @@ class _TutorialStep extends StatelessWidget { } } +// --- WIDGETS SPECIFICI TEMA MUSICA --- + +class _MusicCassetteCard extends StatelessWidget { + final String title; + final String subtitle; + final Color neonColor; + final double angle; + final IconData leftIcon; + final IconData rightIcon; + final VoidCallback onTap; + final AppThemeType themeType; + + const _MusicCassetteCard({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) { + return Transform.rotate( + angle: angle, + child: GestureDetector( + onTap: onTap, + child: Container( + height: 120, + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFF22222A), // Materic plastic + 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: [ + // Cassette Label + 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), + ), + child: Row( + children: [ + Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(leftIcon, color: neonColor, size: 28)), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: _getTextStyle(themeType, TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.w900, shadows: [Shadow(color: neonColor, blurRadius: 10)])))), + const SizedBox(height: 2), + FittedBox(fit: BoxFit.scaleDown, child: Text(subtitle, style: _getTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 12, fontWeight: FontWeight.bold)))), + ], + ), + ), + Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(rightIcon, color: neonColor, size: 28)), + ], + ), + ), + ), + const SizedBox(height: 10), + // Spools window + Container( + height: 35, + width: 180, + decoration: BoxDecoration( + color: const Color(0xFF0D0D12), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.white24, width: 1), + ), + child: Stack( + alignment: Alignment.center, + children: [ + // Tape line connecting spools + Container(height: 2, width: 120, color: const Color(0xFF333333)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildSpool(), + _buildSpool(), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildSpool() { + 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), + ) + ), + ); + } +} + +class _MusicKnobCard extends StatelessWidget { + final String title; + final IconData icon; + final VoidCallback onTap; + final AppThemeType themeType; + final Color? iconColor; + + const _MusicKnobCard({required this.title, required this.icon, required this.onTap, required this.themeType, this.iconColor}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 65, height: 65, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: const Color(0xFF222222), // Base plastic + 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), + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: const SweepGradient( + colors: [ + Color(0xFF555555), Color(0xFFAAAAAA), Color(0xFF555555), + Color(0xFF222222), Color(0xFF555555) + ], + ), + border: Border.all(color: Colors.black54, width: 1), + ), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Container( + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Color(0xFF1A1A1A), + ), + child: Center( + child: Icon(icon, color: iconColor ?? Colors.white70, size: 20), + ), + ), + ), + ), + ), + ), + const SizedBox(height: 10), + FittedBox( + fit: BoxFit.scaleDown, + child: Text(title, style: _getTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.0))), + ), + ], + ), + ); + } +} + +class _AudioCablesPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = const Color(0xFF151515) + ..style = PaintingStyle.stroke + ..strokeWidth = 8.0 + ..strokeCap = StrokeCap.round; + + final highlight = Paint() + ..color = const Color(0xFF3A3A3A) + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..strokeCap = StrokeCap.round; + + void drawCable(Path path) { + canvas.drawPath(path, paint); + canvas.drawPath(path, highlight); + } + + // Cable 1 + Path c1 = Path()..moveTo(-20, size.height * 0.2)..quadraticBezierTo(100, size.height * 0.25, 50, size.height * 0.4)..quadraticBezierTo(0, size.height * 0.5, -20, size.height * 0.55); + drawCable(c1); + + // Cable 2 + Path c2 = Path()..moveTo(size.width + 20, size.height * 0.4)..quadraticBezierTo(size.width - 100, size.height * 0.5, size.width - 50, size.height * 0.7)..quadraticBezierTo(size.width, size.height * 0.8, size.width + 20, size.height * 0.85); + drawCable(c2); + + // Cable 3 (bottom) + Path c3 = Path()..moveTo(size.width * 0.2, size.height + 20)..quadraticBezierTo(size.width * 0.3, size.height - 80, size.width * 0.5, size.height - 60)..quadraticBezierTo(size.width * 0.7, size.height - 40, size.width * 0.8, size.height + 20); + drawCable(c3); + + // Jack connector + _drawJack(canvas, Offset(80, size.height * 0.38), -0.5); + _drawJack(canvas, Offset(size.width - 60, size.height * 0.68), 0.8); + } + + void _drawJack(Canvas canvas, Offset pos, double angle) { + canvas.save(); + canvas.translate(pos.dx, pos.dy); + canvas.rotate(angle); + + // Cable end + canvas.drawRect(const Rect.fromLTWH(-15, -4, 15, 8), Paint()..color = const Color(0xFF151515)); + + // Base plastic + canvas.drawRRect(RRect.fromRectAndRadius(const Rect.fromLTWH(0, -6, 25, 12), const Radius.circular(2)), Paint()..color = const Color(0xFF222222)); + canvas.drawRRect(RRect.fromRectAndRadius(const Rect.fromLTWH(2, -4, 21, 8), const Radius.circular(2)), Paint()..color = const Color(0xFF444444)); + + // Metal pin + canvas.drawRect(const Rect.fromLTWH(25, -2, 15, 4), Paint()..color = const Color(0xFFCCCCCC)); + canvas.drawRect(const Rect.fromLTWH(40, -1.5, 5, 3), Paint()..color = const Color(0xFFAAAAAA)); // tip + + // Rings + canvas.drawLine(const Offset(30, -2), const Offset(30, 2), Paint()..color = Colors.black..strokeWidth = 1.5); + canvas.drawLine(const Offset(35, -2), const Offset(35, 2), Paint()..color = Colors.black..strokeWidth = 1.5); + + canvas.restore(); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +// --- WIDGETS STANDARD --- + class _FeatureCard extends StatelessWidget { final String title; final String subtitle; diff --git a/lib/ui/settings/settings_screen.dart b/lib/ui/settings/settings_screen.dart index c0a9fc4..d0f007d 100644 --- a/lib/ui/settings/settings_screen.dart +++ b/lib/ui/settings/settings_screen.dart @@ -78,6 +78,15 @@ class _SettingsScreenState extends State { requiredLevel: 15, currentLevel: playerLevel, ), + const SizedBox(height: 15), + _ThemeCard( + title: "Musica", + subtitle: "Vinili, cassette e vibrazioni sonore", + type: AppThemeType.music, + previewColors: AppColors.music, + requiredLevel: 20, // Tema Esclusivo di Livello 20! + currentLevel: playerLevel, + ), ], ), );