Auto-sync: 20260313_230001

This commit is contained in:
Paolo 2026-03-13 23:00:01 +01:00
parent 518cb22ec4
commit 321810a6b4
10 changed files with 444 additions and 95 deletions

BIN
.DS_Store vendored

Binary file not shown.

Binary file not shown.

BIN
assets/images/music_bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
lib/.DS_Store vendored

Binary file not shown.

View file

@ -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;
}
}

View file

@ -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:

View file

@ -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<GameScreen> 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<GameScreen> 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<GameScreen> 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<GameScreen> 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<GameScreen> 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<GameScreen> 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<GameScreen> 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<GameScreen> 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<GameScreen> 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<GameScreen> 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<WinnerVFXOverlay> 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<WinnerVFXOverlay> 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<WinnerVFXOverlay> 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); }

View file

@ -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<ScoreBoard> {
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<ScoreBoard> {
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<ScoreBoard> {
],
),
_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)))),
),
],
);

View file

@ -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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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,7 +1366,38 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
const Spacer(),
// --- NUOVA LISTA BOTTONI ---
// --- 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: [
@ -1395,6 +1427,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
),
],
),
],
const SizedBox(height: 10),
],
),
@ -1407,27 +1440,43 @@ class _HomeScreenState extends State<HomeScreen> 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;

View file

@ -78,6 +78,15 @@ class _SettingsScreenState extends State<SettingsScreen> {
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,
),
],
),
);