diff --git a/.DS_Store b/.DS_Store index 2cd10c5..520e0c4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/assets/.DS_Store b/assets/.DS_Store index e9fe782..172c87f 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/images/egizi_bg.jpg b/assets/images/egizi_bg.jpg new file mode 100644 index 0000000..a6520ef Binary files /dev/null and b/assets/images/egizi_bg.jpg differ diff --git a/lib/.DS_Store b/lib/.DS_Store index 0dbee05..da3115c 100644 Binary files a/lib/.DS_Store and b/lib/.DS_Store differ diff --git a/lib/ui/.DS_Store b/lib/ui/.DS_Store new file mode 100644 index 0000000..865b6cc Binary files /dev/null and b/lib/ui/.DS_Store differ diff --git a/lib/ui/game/board_painter.dart b/lib/ui/game/board_painter.dart index ef8a5e3..e9d19cf 100644 --- a/lib/ui/game/board_painter.dart +++ b/lib/ui/game/board_painter.dart @@ -54,6 +54,29 @@ class BoardPainter extends CustomPainter { double offset = spacing / 2; Offset getScreenPos(int x, int y) => Offset(x * spacing + offset, y * spacing + offset); + // --- NUOVO SFONDO LUMINOSO SAGOMATO (Solo per tema Musica) --- + if (themeType == AppThemeType.music) { + Path arenaShape = Path(); + // Uniamo la forma di ogni box giocabile per creare il tappeto + for (var box in board.boxes) { + if (box.type != BoxType.invisible) { // Ignora i buchi + Offset p1 = getScreenPos(box.x, box.y); + Offset p2 = getScreenPos(box.x + 1, box.y + 1); + arenaShape.addRect(Rect.fromPoints(p1, p2)); + } + } + + // Disegniamo lo sfondo unito, chiaro e con un po' di blur + canvas.drawPath( + arenaShape, + Paint() + ..color = Colors.white.withOpacity(0.08) // Colore chiarissimo + ..style = PaintingStyle.fill + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10.0), // Effetto stacco/glow + ); + } + // ----------------------------------------------------------- + for (var box in board.boxes) { Offset p1 = getScreenPos(box.x, box.y); Offset p2 = getScreenPos(box.x + 1, box.y + 1); @@ -66,6 +89,7 @@ class BoardPainter extends CustomPainter { continue; } + // Sfondo azzurrino se è di ghiaccio (anche prima di chiuderla) if (box.type == BoxType.ice && box.owner == Player.none) { canvas.drawRect(rect.deflate(2.0), Paint()..color = Colors.cyanAccent.withOpacity(0.05)..style=PaintingStyle.fill); } @@ -110,7 +134,7 @@ class BoardPainter extends CustomPainter { if (box.type == BoxType.gold) { _drawIconInBox(canvas, rect, ThemeIcons.gold(themeType), Colors.amber); } else if (box.type == BoxType.bomb) { - _drawIconInBox(canvas, rect, ThemeIcons.bomb(themeType), themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.greenAccent : Colors.deepPurple); + _drawIconInBox(canvas, rect, ThemeIcons.bomb(themeType), themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.greenAccent : Colors.deepPurple); } else if (box.type == BoxType.swap) { _drawIconInBox(canvas, rect, ThemeIcons.swap(themeType), Colors.purpleAccent); } else if (box.type == BoxType.ice) { @@ -126,9 +150,10 @@ class BoardPainter extends CustomPainter { Offset p1 = getScreenPos(line.p1.x, line.p1.y); Offset p2 = getScreenPos(line.p2.x, line.p2.y); + // --- DISEGNO DELLA LINEA "INCRINATA" DAL GHIACCIO --- if (line.isIceCracked) { _drawCrackedIceLine(canvas, p1, p2, blinkValue); - continue; + continue; // Non ha ancora un proprietario, passiamo alla prossima! } bool isLastMove = (line == board.lastMove); @@ -158,6 +183,10 @@ class BoardPainter extends CustomPainter { _drawArcadeLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue); } else if (themeType == AppThemeType.grimorio) { _drawGrimorioLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue); + } else if (themeType == AppThemeType.music) { + // Linee nere per la base nel tema musica + if (line.owner == Player.none) lineColor = Colors.black.withOpacity(0.4); + canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round); } else { if (isLastMove && line.owner != Player.none) lineColor = Color.lerp(lineColor, Colors.white, blinkValue * 0.5) ?? lineColor; canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round); @@ -188,6 +217,9 @@ class BoardPainter extends CustomPainter { canvas.drawCircle(pos, 6.0, Paint()..color = theme.gridLine.withOpacity(0.3)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0)); Path crystal = Path()..moveTo(pos.dx, pos.dy - 5)..lineTo(pos.dx + 3, pos.dy)..lineTo(pos.dx, pos.dy + 5)..lineTo(pos.dx - 3, pos.dy)..close(); canvas.drawPath(crystal, dotPaint..color = theme.gridLine.withOpacity(0.8)); + } else if (themeType == AppThemeType.music) { + // Pallini (dots) neri per staccare dal fondo chiaro + canvas.drawCircle(pos, 4.5, dotPaint..color = Colors.black87); } else { canvas.drawCircle(pos, 5.0, dotPaint..color = theme.text.withOpacity(0.6)); } @@ -218,6 +250,7 @@ class BoardPainter extends CustomPainter { ..strokeCap = StrokeCap.round ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0); + // Effetto linea frammentata canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0); Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy); diff --git a/lib/ui/game/game_screen.dart b/lib/ui/game/game_screen.dart index d790a34..991cc87 100644 --- a/lib/ui/game/game_screen.dart +++ b/lib/ui/game/game_screen.dart @@ -10,6 +10,7 @@ import 'package:provider/provider.dart'; import '../../logic/game_controller.dart'; import '../../core/theme_manager.dart'; import '../../core/app_colors.dart'; +import '../../models/game_board.dart'; import 'board_painter.dart'; import 'score_board.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -338,23 +339,42 @@ class _GameScreenState extends State with TickerProviderStateMixin { return SizedBox( width: actualWidth, height: actualHeight, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType), - child: AnimatedBuilder( - animation: _blinkController, - builder: (context, child) { - return CustomPaint( - size: Size(actualWidth, actualHeight), - painter: BoardPainter( - board: gameController.board, theme: theme, themeType: themeType, - blinkValue: _blinkController.value, isOnline: gameController.isOnline, - isVsCPU: gameController.isVsCPU, isSetupPhase: gameController.isSetupPhase, - myPlayer: gameController.myPlayer, jokerTurn: gameController.jokerTurn, + child: Stack( + children: [ + // --- IL VERO SFONDO SFOCATO SAGOMATO --- + // Ritaglia il filtro di sfocatura esattamente sulla forma dell'arena + if (themeType == AppThemeType.music) + Positioned.fill( + child: ClipPath( + clipper: _ArenaClipper(gameController.board), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), + child: Container( + color: Colors.white.withOpacity(0.08), // La patina chiara + ), ), - ); - } - ), + ), + ), + + GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) => _handleTap(details.localPosition, actualWidth, actualHeight, gameController, themeType), + child: AnimatedBuilder( + animation: _blinkController, + builder: (context, child) { + return CustomPaint( + size: Size(actualWidth, actualHeight), + painter: BoardPainter( + board: gameController.board, theme: theme, themeType: themeType, + blinkValue: _blinkController.value, isOnline: gameController.isOnline, + isVsCPU: gameController.isVsCPU, isSetupPhase: gameController.isSetupPhase, + myPlayer: gameController.myPlayer, jokerTurn: gameController.jokerTurn, + ), + ); + } + ), + ), + ], ), ); } @@ -495,6 +515,38 @@ class _GameScreenState extends State with TickerProviderStateMixin { } } +// =========================================================================== +// CLIPPER MAGICO: Ritaglia l'effetto sfocatura sull'esatta forma dell'arena +// =========================================================================== +class _ArenaClipper extends CustomClipper { + final GameBoard board; + _ArenaClipper(this.board); + + @override + Path getClip(Size size) { + int cols = board.columns + 1; + double spacing = size.width / cols; + double offset = spacing / 2; + Path path = Path(); + + for (var box in board.boxes) { + // Ignora i buchi in modo che non vengano sfocati! + if (box.type != BoxType.invisible) { + path.addRect(Rect.fromLTWH( + box.x * spacing + offset, + box.y * spacing + offset, + spacing, + spacing + )); + } + } + return path; + } + + @override + bool shouldReclip(covariant _ArenaClipper oldClipper) => true; +} + class _Particle { double x, y, vx, vy, size, angle, spin; Color color; int type; diff --git a/lib/ui/home/dialog.dart b/lib/ui/home/dialog.dart new file mode 100644 index 0000000..0f6b044 --- /dev/null +++ b/lib/ui/home/dialog.dart @@ -0,0 +1,375 @@ +// =========================================================================== +// FILE: lib/ui/home/dialogs/dialog.dart +// =========================================================================== + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +import '../../../core/theme_manager.dart'; +import '../../../core/app_colors.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../../widgets/painters.dart'; +import '../../../widgets/cyber_border.dart'; + +// =========================================================================== +// 1. DIALOGO MISSIONI (QUESTS) +// =========================================================================== +class QuestsDialog extends StatelessWidget { + const QuestsDialog({super.key}); + + @override + Widget build(BuildContext context) { + final themeManager = context.watch(); + final theme = themeManager.currentColors; + final themeType = themeManager.currentThemeType; + final loc = AppLocalizations.of(context)!; + + return FutureBuilder( + future: SharedPreferences.getInstance(), + builder: (context, snapshot) { + if (!snapshot.hasData) return const SizedBox(); + final prefs = snapshot.data!; + + return Dialog( + backgroundColor: Colors.transparent, + insetPadding: const EdgeInsets.all(20), + child: Container( + padding: const EdgeInsets.all(25.0), + 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: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2), + boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)] + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.assignment_turned_in, size: 50, color: theme.playerBlue), + const SizedBox(height: 10), + Text(loc.questsTitle, style: getSharedTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), + const SizedBox(height: 25), + + ...List.generate(3, (index) { + int i = index + 1; + int type = prefs.getInt('q${i}_type') ?? 0; + int prog = prefs.getInt('q${i}_prog') ?? 0; + int target = prefs.getInt('q${i}_target') ?? 1; + + String title = ""; + IconData icon = Icons.star; + if (type == 0) { title = "Vinci partite Online"; icon = Icons.public; } + else if (type == 1) { title = "Vinci contro la CPU"; icon = Icons.smart_toy; } + else { title = "Gioca in Arene Speciali"; icon = Icons.extension; } + + bool completed = prog >= target; + double percent = (prog / target).clamp(0.0, 1.0); + + return Container( + margin: const EdgeInsets.only(bottom: 15), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: completed ? Colors.green.withOpacity(0.1) : theme.text.withOpacity(0.05), + borderRadius: BorderRadius.circular(15), + border: Border.all(color: completed ? Colors.green : theme.gridLine.withOpacity(0.3)), + ), + child: Row( + children: [ + Icon(icon, color: completed ? Colors.green : theme.text.withOpacity(0.6), size: 30), + const SizedBox(width: 15), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: completed ? Colors.green : theme.text))), + const SizedBox(height: 6), + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: LinearProgressIndicator(value: percent, backgroundColor: theme.gridLine.withOpacity(0.2), color: completed ? Colors.green : theme.playerBlue, minHeight: 8), + ) + ], + ), + ), + const SizedBox(width: 10), + Text("$prog / $target", style: getSharedTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6)))), + ], + ), + ); + }), + + const SizedBox(height: 15), + SizedBox( + width: double.infinity, height: 50, + child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), + onPressed: () => Navigator.pop(context), + child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)), + ), + ) + ], + ), + ), + ); + } + ); + } +} + +// =========================================================================== +// 2. DIALOGO CLASSIFICA (LEADERBOARD) +// =========================================================================== +class LeaderboardDialog extends StatelessWidget { + const LeaderboardDialog({super.key}); + + @override + Widget build(BuildContext context) { + final themeManager = context.watch(); + final theme = themeManager.currentColors; + final themeType = themeManager.currentThemeType; + final loc = AppLocalizations.of(context)!; + + Widget content = Container( + padding: const EdgeInsets.all(20.0), + 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: Border.all(color: Colors.amber.withOpacity(0.8), width: 2), + boxShadow: [BoxShadow(color: Colors.amber.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)] + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.emoji_events, size: 50, color: Colors.amber), + const SizedBox(height: 10), + Text(loc.leaderboardTitle, style: getSharedTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), + const SizedBox(height: 20), + + SizedBox( + height: 350, + child: StreamBuilder( + stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('xp', descending: true).limit(50).snapshots(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator(color: theme.playerBlue)); + } + if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { + return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5)))); + } + + final docs = snapshot.data!.docs; + return ListView.builder( + physics: const BouncingScrollPhysics(), + itemCount: docs.length, + itemBuilder: (context, index) { + var data = docs[index].data() as Map; + String? myUid = FirebaseAuth.instance.currentUser?.uid; + bool isMe = docs[index].id == myUid; + + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: BoxDecoration( + color: isMe ? theme.playerBlue.withOpacity(0.2) : theme.text.withOpacity(0.05), + borderRadius: BorderRadius.circular(10), + border: isMe ? Border.all(color: theme.playerBlue, width: 1.5) : null + ), + child: Row( + children: [ + Text("#${index + 1}", style: getSharedTextStyle(themeType, TextStyle(fontWeight: FontWeight.w900, color: index == 0 ? Colors.amber : (index == 1 ? Colors.grey.shade400 : (index == 2 ? Colors.brown.shade300 : theme.text.withOpacity(0.5)))))), + const SizedBox(width: 15), + Expanded(child: Text(data['name'] ?? 'Unknown', style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: isMe ? FontWeight.w900 : FontWeight.bold, color: theme.text)))), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text("Lv. ${data['level'] ?? 1}", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 12)), + Text("${data['xp'] ?? 0} XP", style: TextStyle(color: theme.text.withOpacity(0.6), fontSize: 10)), + ], + ) + ], + ), + ); + } + ); + } + ), + ), + + const SizedBox(height: 15), + SizedBox( + width: double.infinity, height: 50, + child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Colors.amber.shade700, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), + onPressed: () => Navigator.pop(context), + child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)), + ), + ) + ], + ), + ); + + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) { + content = AnimatedCyberBorder(child: content); + } + + return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: content); + } +} + +// =========================================================================== +// 3. DIALOGO TUTORIAL +// =========================================================================== +class TutorialDialog extends StatelessWidget { + const TutorialDialog({super.key}); + + @override + Widget build(BuildContext context) { + final themeManager = context.watch(); + final theme = themeManager.currentColors; + final themeType = themeManager.currentThemeType; + Color inkColor = const Color(0xFF111122); + + String goldLabel = themeType == AppThemeType.grimorio ? "CORONA:" : "ORO:"; + String bombLabel = themeType == AppThemeType.grimorio ? "STREGA:" : "BOMBA:"; + String jokerLabel = themeType == AppThemeType.grimorio ? "GIULLARE:" : "JOLLY:"; + + Widget dialogContent = themeType == AppThemeType.doodle + ? Transform.rotate( + angle: -0.01, + child: CustomPaint( + painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade50, strokeColor: inkColor, seed: 400), + child: Padding( + padding: const EdgeInsets.all(25.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center(child: Text("COME GIOCARE", style: getSharedTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2)))), + const SizedBox(height: 20), + TutorialStep(icon: Icons.line_axis, text: "Chiudi i 4 lati di un quadrato per conquistare 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 15), + TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! Ogni quadrato nasconde un'insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 15), + const Divider(color: Colors.black26, thickness: 2), + const SizedBox(height: 10), + Center(child: Text("GLOSSARIO ARENA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor)))), + const SizedBox(height: 10), + + TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber.shade700, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: Colors.green.shade600, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme), + + const SizedBox(height: 25), + Center( + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: CustomPaint( + painter: DoodleBackgroundPainter(fillColor: Colors.red.shade200, strokeColor: inkColor, seed: 401), + child: Container( + height: 50, width: 150, alignment: Alignment.center, + child: Text("HO CAPITO!", style: getSharedTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor))), + ), + ), + ), + ) + ], + ), + ), + ), + ) + : Container( + padding: const EdgeInsets.all(25.0), + 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 || 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(), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center(child: Text("COME GIOCARE", style: getSharedTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2)))), + const SizedBox(height: 20), + TutorialStep(icon: Icons.grid_4x4, text: "Chiudi i 4 lati di un quadrato per conquistare 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 15), + TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! Ogni quadrato nasconde un'insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 15), + const Divider(color: Colors.white24, thickness: 1.5), + const SizedBox(height: 10), + Center(child: Text("GLOSSARIO ARENA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.7), letterSpacing: 1.5)))), + const SizedBox(height: 15), + + TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.greenAccent : Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: theme.playerBlue, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme), + const SizedBox(height: 10), + TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme), + + const SizedBox(height: 30), + SizedBox( + width: double.infinity, height: 50, + child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), + onPressed: () => Navigator.pop(context), + child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)), + ), + ) + ], + ), + ), + ); + + 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); + } +} + +class TutorialStep extends StatelessWidget { + final IconData icon; + final Color? iconColor; + final String text; + final AppThemeType themeType; + final Color inkColor; + final ThemeColors theme; + + const TutorialStep({super.key, required this.icon, this.iconColor, required this.text, required this.themeType, required this.inkColor, required this.theme}); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon, color: iconColor ?? (themeType == AppThemeType.doodle ? inkColor : theme.playerBlue), size: 28), + const SizedBox(width: 15), + Expanded( + child: Text(text, style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, color: themeType == AppThemeType.doodle ? inkColor : theme.text.withOpacity(0.8), height: 1.3))), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index ce6e9fb..8851aa3 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -7,11 +7,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart'; -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 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; // Serve ancora se vuoi fare logica qui, ma per ora teniamo l'import +import 'dart:async'; +import 'package:app_links/app_links.dart'; import '../../logic/game_controller.dart'; import '../../core/theme_manager.dart'; @@ -21,114 +20,26 @@ import '../settings/settings_screen.dart'; import '../../services/storage_service.dart'; import '../multiplayer/lobby_screen.dart'; import 'history_screen.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:tetraq/l10n/app_localizations.dart'; import '../admin/admin_screen.dart'; -import 'dart:async'; -import 'package:app_links/app_links.dart'; +import 'package:tetraq/l10n/app_localizations.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)); - } else if (themeType == AppThemeType.music) { - return GoogleFonts.audiowide(textStyle: baseStyle.copyWith(letterSpacing: 1.5)); - } - return baseStyle; -} +// --- IMPORTIAMO I NOSTRI NUOVI WIDGET E DIALOGHI PULITI --- +import '../../widgets/painters.dart'; +import '../../widgets/cyber_border.dart'; +import '../../widgets/home_buttons.dart'; +import '../../widgets/music_theme_widgets.dart'; +import 'dialog.dart'; // Il file che raggruppa Quests, Leaderboard e Tutorial -class _DoodleBackgroundPainter extends CustomPainter { - final Color fillColor; - final Color strokeColor; - final int seed; - final bool isCircle; - - _DoodleBackgroundPainter({required this.fillColor, required this.strokeColor, required this.seed, this.isCircle = false}); - - @override - void paint(Canvas canvas, Size size) { - final math.Random random = math.Random(seed); - - double wobble() => random.nextDouble() * 6 - 3; - - final Paint fillPaint = Paint() - ..color = fillColor - ..style = PaintingStyle.fill; - - final Paint strokePaint = Paint() - ..color = strokeColor - ..strokeWidth = 2.5 - ..style = PaintingStyle.stroke - ..strokeCap = StrokeCap.round - ..strokeJoin = StrokeJoin.round; - - if (isCircle) { - final Rect rect = Rect.fromLTWH(wobble(), wobble(), size.width + wobble(), size.height + wobble()); - - canvas.save(); - canvas.translate(wobble(), wobble()); - canvas.drawOval(rect, fillPaint); - canvas.restore(); - - canvas.drawOval(rect, strokePaint); - - canvas.save(); - canvas.translate(random.nextDouble() * 4 - 2, random.nextDouble() * 4 - 2); - canvas.drawOval(rect, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6)); - canvas.restore(); - } else { - final Path path = Path(); - path.moveTo(wobble(), wobble()); - path.lineTo(size.width + wobble(), wobble()); - path.lineTo(size.width + wobble(), size.height + wobble()); - path.lineTo(wobble(), size.height + wobble()); - path.close(); - - final Path fillPath = Path(); - fillPath.moveTo(wobble() * 1.5, wobble() * 1.5); - fillPath.lineTo(size.width + wobble() * 1.5, wobble() * 1.5); - fillPath.lineTo(size.width + wobble() * 1.5, size.height + wobble() * 1.5); - fillPath.lineTo(wobble() * 1.5, size.height + wobble() * 1.5); - fillPath.close(); - - canvas.drawPath(fillPath, fillPaint); - - canvas.drawPath(path, strokePaint); - - canvas.save(); - canvas.translate(random.nextDouble() * 3 - 1.5, random.nextDouble() * 3 - 1.5); - canvas.drawPath(path, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6)); - canvas.restore(); - } - } - - @override - bool shouldRepaint(covariant _DoodleBackgroundPainter oldDelegate) { - return oldDelegate.fillColor != fillColor || oldDelegate.strokeColor != strokeColor; - } -} +// =========================================================================== +// WIDGET LOCALI PER IL SETUP DELLA PARTITA (Ancora qui per comodità) +// =========================================================================== class _NeonShapeButton extends StatelessWidget { - final IconData icon; - final String label; - final bool isSelected; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - final bool isLocked; - final bool isSpecial; + final IconData icon; final String label; final bool isSelected; + final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap; + final bool isLocked; final bool isSpecial; - const _NeonShapeButton({ - required this.icon, required this.label, required this.isSelected, - required this.theme, required this.themeType, required this.onTap, - this.isLocked = false, this.isSpecial = false - }); + const _NeonShapeButton({required this.icon, required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap, this.isLocked = false, this.isSpecial = false}); Color _getDoodleColor() { switch (label) { @@ -153,11 +64,7 @@ class _NeonShapeButton extends StatelessWidget { child: GestureDetector( onTap: isLocked ? null : onTap, child: CustomPaint( - painter: _DoodleBackgroundPainter( - fillColor: isSelected ? doodleColor : Colors.white.withOpacity(0.8), - strokeColor: inkColor, - seed: label.length * 3, - ), + painter: DoodleBackgroundPainter(fillColor: isSelected ? doodleColor : Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: label.length * 3), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), child: Column( @@ -165,7 +72,7 @@ class _NeonShapeButton extends StatelessWidget { children: [ Icon(isLocked ? Icons.lock : icon, color: inkColor, size: 24), const SizedBox(height: 2), - Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 0.5))), + Text(isLocked ? "Liv. 10" : label, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 0.5))), ], ), ), @@ -178,38 +85,18 @@ class _NeonShapeButton extends StatelessWidget { return GestureDetector( onTap: isLocked ? null : onTap, child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.easeOutCubic, - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), - transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), + duration: const Duration(milliseconds: 250), curve: Curves.easeOutCubic, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isLocked - ? [Colors.grey.withOpacity(0.1), Colors.black.withOpacity(0.2)] - : isSelected - ? [mainColor.withOpacity(0.3), mainColor.withOpacity(0.1)] - : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], - ), - border: Border.all( - color: isLocked ? Colors.transparent : (isSelected ? mainColor : Colors.white.withOpacity(0.1)), - width: isSelected ? 2 : 1, - ), - boxShadow: isLocked ? [] : isSelected - ? [BoxShadow(color: mainColor.withOpacity(0.5), blurRadius: 15, spreadRadius: 1, offset: const Offset(0, 0))] - : [ - BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), - BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), - ], + borderRadius: BorderRadius.circular(15), border: Border.all(color: isLocked ? Colors.transparent : (isSelected ? mainColor : Colors.white.withOpacity(0.1)), width: isSelected ? 2 : 1), + gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isLocked ? [Colors.grey.withOpacity(0.1), Colors.black.withOpacity(0.2)] : isSelected ? [mainColor.withOpacity(0.3), mainColor.withOpacity(0.1)] : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)]), + boxShadow: isLocked ? [] : isSelected ? [BoxShadow(color: mainColor.withOpacity(0.5), blurRadius: 15, spreadRadius: 1, offset: const Offset(0, 0))] : [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1))], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(isLocked ? Icons.lock : icon, color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), size: 24), const SizedBox(height: 6), - Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(themeType, TextStyle(color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), fontSize: 11, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))), + Text(isLocked ? "Liv. 10" : label, style: getSharedTextStyle(themeType, TextStyle(color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), fontSize: 11, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))), ], ), ), @@ -218,118 +105,57 @@ class _NeonShapeButton extends StatelessWidget { } class _NeonSizeButton extends StatelessWidget { - final String label; - final bool isSelected; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - + final String label; final bool isSelected; final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap; const _NeonSizeButton({required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap}); @override Widget build(BuildContext context) { if (themeType == AppThemeType.doodle) { - Color doodleColor = label == 'MAX' ? Colors.red.shade200 : Colors.cyan.shade100; - Color inkColor = const Color(0xFF111122); - double tilt = (label == 'M' || label == 'MAX') ? 0.05 : -0.04; - + Color doodleColor = label == 'MAX' ? Colors.red.shade200 : Colors.cyan.shade100; Color inkColor = const Color(0xFF111122); double tilt = (label == 'M' || label == 'MAX') ? 0.05 : -0.04; return Transform.rotate( angle: tilt, child: GestureDetector( onTap: onTap, - child: CustomPaint( - painter: _DoodleBackgroundPainter( - fillColor: isSelected ? doodleColor : Colors.white.withOpacity(0.8), - strokeColor: inkColor, - seed: label.codeUnitAt(0), - isCircle: true, - ), - child: SizedBox( - width: 50, height: 50, - child: Center( - child: Text(label, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 18, fontWeight: FontWeight.w900))), - ), - ), - ), + child: CustomPaint(painter: DoodleBackgroundPainter(fillColor: isSelected ? doodleColor : Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: label.codeUnitAt(0), isCircle: true), child: SizedBox(width: 50, height: 50, child: Center(child: Text(label, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 18, fontWeight: FontWeight.w900)))))), ), ); } - return GestureDetector( onTap: onTap, child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.easeOutCubic, - width: 50, height: 50, - transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), + duration: const Duration(milliseconds: 250), curve: Curves.easeOutCubic, width: 50, height: 50, transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0), decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isSelected - ? [theme.playerRed.withOpacity(0.3), theme.playerRed.withOpacity(0.1)] - : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], - ), - border: Border.all( - color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1), - width: isSelected ? 2 : 1, - ), - boxShadow: isSelected - ? [BoxShadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 15, spreadRadius: 1)] - : [ - BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), - BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), - ], - ), - child: Center( - child: Text(label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : theme.text.withOpacity(0.6), fontSize: 14, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))), + shape: BoxShape.circle, border: Border.all(color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1), width: isSelected ? 2 : 1), + gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isSelected ? [theme.playerRed.withOpacity(0.3), theme.playerRed.withOpacity(0.1)] : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)]), + boxShadow: isSelected ? [BoxShadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 15, spreadRadius: 1)] : [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1))], ), + child: Center(child: Text(label, style: getSharedTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : theme.text.withOpacity(0.6), fontSize: 14, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold)))), ), ); } } class _NeonTimeSwitch extends StatelessWidget { - final bool isTimeMode; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - + final bool isTimeMode; final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap; const _NeonTimeSwitch({required this.isTimeMode, required this.theme, required this.themeType, required this.onTap}); @override Widget build(BuildContext context) { if (themeType == AppThemeType.doodle) { - Color doodleColor = Colors.orange.shade200; - Color inkColor = const Color(0xFF111122); - + Color doodleColor = Colors.orange.shade200; Color inkColor = const Color(0xFF111122); return Transform.rotate( angle: -0.015, child: GestureDetector( onTap: onTap, child: CustomPaint( - painter: _DoodleBackgroundPainter( - fillColor: isTimeMode ? doodleColor : Colors.white.withOpacity(0.8), - strokeColor: inkColor, - seed: 42, - ), + painter: DoodleBackgroundPainter(fillColor: isTimeMode ? doodleColor : Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: 42), child: Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: inkColor, size: 28), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: 2.0))), - Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 13, fontWeight: FontWeight.bold))), - ], - ), + Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: inkColor, size: 28), const SizedBox(width: 12), + Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: 2.0))), Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 13, fontWeight: FontWeight.bold)))]), ], ), ), @@ -337,47 +163,20 @@ class _NeonTimeSwitch extends StatelessWidget { ), ); } - return GestureDetector( onTap: onTap, child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isTimeMode - ? [Colors.amber.withOpacity(0.25), Colors.amber.withOpacity(0.05)] - : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)], - ), - border: Border.all( - color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1), - width: isTimeMode ? 2 : 1, - ), - boxShadow: isTimeMode - ? [BoxShadow(color: Colors.amber.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)] - : [ - BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), - BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)), - ], + borderRadius: BorderRadius.circular(20), border: Border.all(color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1), width: isTimeMode ? 2 : 1), + gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isTimeMode ? [Colors.amber.withOpacity(0.25), Colors.amber.withOpacity(0.05)] : [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)]), + boxShadow: isTimeMode ? [BoxShadow(color: Colors.amber.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)] : [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1))], ), child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 28), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 14, letterSpacing: 1.5))), - Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 11, fontWeight: FontWeight.bold))), - ], - ), + Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 28), const SizedBox(width: 12), + Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: getSharedTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 14, letterSpacing: 1.5))), Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: getSharedTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 11, fontWeight: FontWeight.bold)))]), ], ), ), @@ -385,6 +184,9 @@ class _NeonTimeSwitch extends StatelessWidget { } } +// =========================================================================== +// CLASSE PRINCIPALE HOME +// =========================================================================== class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -395,7 +197,6 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State with WidgetsBindingObserver { int _debugTapCount = 0; - late AppLinks _appLinks; StreamSubscription? _linkSubscription; @@ -426,152 +227,24 @@ class _HomeScreenState extends State with WidgetsBindingObserver { } void _checkPlayerName() { - if (StorageService.instance.playerName.isEmpty) { - _showNameDialog(); - } - } - - void _showNameDialog() { - final TextEditingController nameController = TextEditingController(text: StorageService.instance.playerName); - showDialog( - context: context, - barrierDismissible: false, - barrierColor: Colors.black.withOpacity(0.8), - builder: (context) { - final themeManager = context.watch(); - final theme = themeManager.currentColors; - final themeType = themeManager.currentThemeType; - Color inkColor = const Color(0xFF111122); - final loc = AppLocalizations.of(context)!; - - Widget dialogContent = themeType == AppThemeType.doodle - ? CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 100), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 25.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(loc.welcomeTitle, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 28, letterSpacing: 2.0)), textAlign: TextAlign.center), - const SizedBox(height: 20), - Text('Scegli il tuo nome da battaglia', style: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 18)), textAlign: TextAlign.center), - const SizedBox(height: 30), - TextField( - controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5, - style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 36, fontWeight: FontWeight.bold, letterSpacing: 8)), - decoration: InputDecoration( - hintText: loc.nameHint, hintStyle: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.3), letterSpacing: 4)), - filled: false, counterText: "", - enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: inkColor, width: 3)), - focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.red.shade200, width: 5)), - ), - ), - const SizedBox(height: 40), - GestureDetector( - onTap: () { - final name = nameController.text.trim(); - if (name.isNotEmpty) { - StorageService.instance.savePlayerName(name); - Navigator.of(context).pop(); - setState(() {}); - } - }, - child: CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 101), - child: Container( - width: double.infinity, height: 60, - alignment: Alignment.center, - child: Text(loc.saveAndPlay, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 1.5))), - ), - ), - ), - ], - ), - ), - ) - : Container( - decoration: BoxDecoration( - color: theme.background, - borderRadius: BorderRadius.circular(25), - border: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2), - boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 20, spreadRadius: 5)] - ), - child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 30.0, horizontal: 25.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(loc.welcomeTitle, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 24, letterSpacing: 1.5)), textAlign: TextAlign.center), - const SizedBox(height: 20), - Text('Scegli il tuo nome da battaglia per sfidare i tuoi amici online.', style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.8), fontSize: 16)), textAlign: TextAlign.center), - const SizedBox(height: 40), - TextField( - controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5, - style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 8)), - decoration: InputDecoration( - hintText: loc.nameHint, hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 4)), - filled: true, fillColor: theme.text.withOpacity(0.05), counterText: "", - enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2), borderRadius: BorderRadius.circular(15)), - focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3), borderRadius: BorderRadius.circular(15)), - ), - ), - const SizedBox(height: 40), - SizedBox( - width: double.infinity, height: 55, - child: ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), - onPressed: () { - final name = nameController.text.trim(); - if (name.isNotEmpty) { - StorageService.instance.savePlayerName(name); - Navigator.of(context).pop(); - setState(() {}); - } - }, - child: Text(loc.saveAndPlay, style: _getTextStyle(themeType, const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 1.5))), - ), - ), - ], - ), - ), - ), - ); - - if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) dialogContent = _AnimatedCyberBorder(child: dialogContent); - return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent); - }, - ); + if (StorageService.instance.playerName.isEmpty) { _showNameDialog(); } } Future _initDeepLinks() async { _appLinks = AppLinks(); - try { final initialUri = await _appLinks.getInitialLink(); - if (initialUri != null) { - _handleDeepLink(initialUri); - } - } catch (e) { - debugPrint("Errore lettura link iniziale: $e"); - } - - _linkSubscription = _appLinks.uriLinkStream.listen((uri) { - _handleDeepLink(uri); - }, onError: (err) { - debugPrint("Errore stream link: $err"); - }); + if (initialUri != null) _handleDeepLink(initialUri); + } catch (e) { debugPrint("Errore lettura link iniziale: $e"); } + _linkSubscription = _appLinks.uriLinkStream.listen((uri) { _handleDeepLink(uri); }, onError: (err) { debugPrint("Errore stream link: $err"); }); } void _handleDeepLink(Uri uri) { - debugPrint("Link ricevuto: $uri"); if (uri.scheme == 'tetraq' && uri.host == 'join') { String? code = uri.queryParameters['code']; if (code != null && code.length == 5) { Future.delayed(const Duration(milliseconds: 500), () { - if (mounted) { - _promptJoinRoom(code.toUpperCase()); - } + if (mounted) _promptJoinRoom(code.toUpperCase()); }); } } @@ -581,24 +254,22 @@ class _HomeScreenState extends State with WidgetsBindingObserver { try { ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); String? text = data?.text; - if (text != null && text.contains("TetraQ") && text.contains("codice:")) { RegExp regExp = RegExp(r'codice:\s*([A-Z0-9]{5})', caseSensitive: false); Match? match = regExp.firstMatch(text); - if (match != null) { String roomCode = match.group(1)!.toUpperCase(); await Clipboard.setData(const ClipboardData(text: '')); - if (mounted && ModalRoute.of(context)?.isCurrent == true) { - _promptJoinRoom(roomCode); - } + if (mounted && ModalRoute.of(context)?.isCurrent == true) { _promptJoinRoom(roomCode); } } } - } catch (e) { - debugPrint("Errore lettura appunti: $e"); - } + } catch (e) { debugPrint("Errore lettura appunti: $e"); } } + // =========================================================================== + // DIALOGHI IN-FILE (Setup e Nome) + // =========================================================================== + void _promptJoinRoom(String roomCode) { showDialog( context: context, @@ -608,21 +279,17 @@ class _HomeScreenState extends State with WidgetsBindingObserver { return AlertDialog( backgroundColor: themeType == AppThemeType.doodle ? Colors.white : theme.background, shape: themeType == AppThemeType.doodle ? RoundedRectangleBorder(borderRadius: BorderRadius.circular(15), side: BorderSide(color: theme.text, width: 2)) : null, - title: Text("Invito Trovato!", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))), - content: Text("Vuoi unirti alla stanza $roomCode?", style: _getTextStyle(themeType, TextStyle(color: theme.text))), + title: Text("Invito Trovato!", style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))), + content: Text("Vuoi unirti alla stanza $roomCode?", style: getSharedTextStyle(themeType, TextStyle(color: theme.text))), actions: [ - TextButton(onPressed: () => Navigator.pop(context), child: Text("No", style: _getTextStyle(themeType, const TextStyle(color: Colors.red)))), + TextButton(onPressed: () => Navigator.pop(context), child: Text("No", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.red)))), ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: themeType == AppThemeType.doodle ? Colors.transparent : theme.playerBlue, - elevation: 0, - side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 1.5) : BorderSide.none, - ), + style: ElevatedButton.styleFrom(backgroundColor: themeType == AppThemeType.doodle ? Colors.transparent : theme.playerBlue, elevation: 0, side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 1.5) : BorderSide.none), onPressed: () { Navigator.of(context).pop(); Navigator.push(context, MaterialPageRoute(builder: (_) => LobbyScreen(initialRoomCode: roomCode))); }, - child: Text(AppLocalizations.of(context)!.joinMatch, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : Colors.white, fontWeight: FontWeight.bold))), + child: Text(AppLocalizations.of(context)!.joinMatch, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : Colors.white, fontWeight: FontWeight.bold))), ), ], ); @@ -630,20 +297,98 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); } + void _showNameDialog() { + final TextEditingController nameController = TextEditingController(text: StorageService.instance.playerName); + showDialog( + context: context, barrierDismissible: false, barrierColor: Colors.black.withOpacity(0.8), + builder: (context) { + final themeManager = context.watch(); + final theme = themeManager.currentColors; final themeType = themeManager.currentThemeType; + Color inkColor = const Color(0xFF111122); final loc = AppLocalizations.of(context)!; + + Widget dialogContent = themeType == AppThemeType.doodle + ? CustomPaint( + painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 100), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 25.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 28, letterSpacing: 2.0)), textAlign: TextAlign.center), + const SizedBox(height: 20), + Text('Scegli il tuo nome da battaglia', style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 18)), textAlign: TextAlign.center), + const SizedBox(height: 30), + TextField( + controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5, + style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 36, fontWeight: FontWeight.bold, letterSpacing: 8)), + decoration: InputDecoration(hintText: loc.nameHint, hintStyle: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.3), letterSpacing: 4)), filled: false, counterText: "", enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: inkColor, width: 3)), focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.red.shade200, width: 5))), + ), + const SizedBox(height: 40), + GestureDetector( + onTap: () { + final name = nameController.text.trim(); + if (name.isNotEmpty) { StorageService.instance.savePlayerName(name); Navigator.of(context).pop(); setState(() {}); } + }, + child: CustomPaint( + painter: DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 101), + child: Container(width: double.infinity, height: 60, alignment: Alignment.center, child: Text(loc.saveAndPlay, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 22, fontWeight: FontWeight.bold, letterSpacing: 1.5)))), + ), + ), + ], + ), + ), + ) + : Container( + decoration: BoxDecoration(color: theme.background, borderRadius: BorderRadius.circular(25), border: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 20, spreadRadius: 5)]), + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30.0, horizontal: 25.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 24, letterSpacing: 1.5)), textAlign: TextAlign.center), + const SizedBox(height: 20), + Text('Scegli il tuo nome da battaglia per sfidare i tuoi amici online.', style: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.8), fontSize: 16)), textAlign: TextAlign.center), + const SizedBox(height: 40), + TextField( + controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5, + style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 8)), + decoration: InputDecoration(hintText: loc.nameHint, hintStyle: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 4)), filled: true, fillColor: theme.text.withOpacity(0.05), counterText: "", enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2), borderRadius: BorderRadius.circular(15)), focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: theme.playerBlue, width: 3), borderRadius: BorderRadius.circular(15))), + ), + const SizedBox(height: 40), + SizedBox( + width: double.infinity, height: 55, + child: ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), + onPressed: () { + final name = nameController.text.trim(); + if (name.isNotEmpty) { StorageService.instance.savePlayerName(name); Navigator.of(context).pop(); setState(() {}); } + }, + child: Text(loc.saveAndPlay, style: getSharedTextStyle(themeType, const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 1.5))), + ), + ), + ], + ), + ), + ), + ); + + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) dialogContent = AnimatedCyberBorder(child: dialogContent); + return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent); + }, + ); + } + void _showMatchSetupDialog(bool isVsCPU) { - int localRadius = 4; - ArenaShape localShape = ArenaShape.classic; - bool localTimeMode = true; + int localRadius = 4; ArenaShape localShape = ArenaShape.classic; bool localTimeMode = true; bool isChaosUnlocked = StorageService.instance.playerLevel >= 10; final loc = AppLocalizations.of(context)!; showDialog( - context: context, - barrierColor: Colors.black.withOpacity(0.8), + context: context, barrierColor: Colors.black.withOpacity(0.8), builder: (ctx) { final themeManager = ctx.watch(); - final theme = themeManager.currentColors; - final themeType = themeManager.currentThemeType; + final theme = themeManager.currentColors; final themeType = themeManager.currentThemeType; Color inkColor = const Color(0xFF111122); return StatefulBuilder( @@ -652,7 +397,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ? Transform.rotate( angle: 0.015, child: CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.95), strokeColor: inkColor, seed: 200), + painter: DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.95), strokeColor: inkColor, seed: 200), child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( @@ -660,37 +405,16 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Row( - children: [ - SizedBox( - width: 40, - child: IconButton( - padding: EdgeInsets.zero, - alignment: Alignment.centerLeft, - icon: Icon(Icons.arrow_back_ios_new, color: inkColor, size: 26), - onPressed: () => Navigator.pop(ctx), - ), - ), - Expanded( - child: Text(isVsCPU ? loc.cpuTitle : loc.localTitle, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2))), - ), - const SizedBox(width: 40), - ], - ), + Row(children: [ SizedBox(width: 40, child: IconButton(padding: EdgeInsets.zero, alignment: Alignment.centerLeft, icon: Icon(Icons.arrow_back_ios_new, color: inkColor, size: 26), onPressed: () => Navigator.pop(ctx))), Expanded(child: Text(isVsCPU ? loc.cpuTitle : loc.localTitle, textAlign: TextAlign.center, style: getSharedTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2)))), const SizedBox(width: 40) ]), const SizedBox(height: 25), if (isVsCPU) ...[ - Icon(Icons.smart_toy, size: 50, color: inkColor.withOpacity(0.6)), - const SizedBox(height: 10), - Text("MODALITÀ CAMPAGNA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: inkColor))), - const SizedBox(height: 10), - Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 13, color: inkColor.withOpacity(0.8), height: 1.4))), - const SizedBox(height: 25), - Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), - const SizedBox(height: 20), + Icon(Icons.smart_toy, size: 50, color: inkColor.withOpacity(0.6)), const SizedBox(height: 10), + Text("MODALITÀ CAMPAGNA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: inkColor))), const SizedBox(height: 10), + Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: getSharedTextStyle(themeType, TextStyle(fontSize: 13, color: inkColor.withOpacity(0.8), height: 1.4))), const SizedBox(height: 25), + Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), const SizedBox(height: 20), ] else ...[ - Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), - const SizedBox(height: 15), + Text("FORMA ARENA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), const SizedBox(height: 15), Wrap( spacing: 12, runSpacing: 12, alignment: WrapAlignment.center, children: [ @@ -701,13 +425,9 @@ class _HomeScreenState extends State with WidgetsBindingObserver { _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), ], ), + const SizedBox(height: 25), Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), const SizedBox(height: 20), - const SizedBox(height: 25), - Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), - const SizedBox(height: 20), - - Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), - const SizedBox(height: 15), + Text("GRANDEZZA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), const SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -717,34 +437,17 @@ class _HomeScreenState extends State with WidgetsBindingObserver { _NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), ], ), - - const SizedBox(height: 25), - Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), - const SizedBox(height: 20), + const SizedBox(height: 25), Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), const SizedBox(height: 20), ], - Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), - const SizedBox(height: 10), - _NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)), - - const SizedBox(height: 35), + Text("TEMPO", style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), const SizedBox(height: 10), + _NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)), const SizedBox(height: 35), Transform.rotate( angle: -0.02, child: GestureDetector( - onTap: () { - Navigator.pop(ctx); - context.read().startNewGame(localRadius, vsCPU: isVsCPU, shape: localShape, timeMode: localTimeMode); - Navigator.push(context, MaterialPageRoute(builder: (_) => const GameScreen())); - }, - child: CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 300), - child: Container( - height: 65, width: double.infinity, - alignment: Alignment.center, - child: Text(loc.startGame, style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: inkColor))), - ), - ), + onTap: () { Navigator.pop(ctx); context.read().startNewGame(localRadius, vsCPU: isVsCPU, shape: localShape, timeMode: localTimeMode); Navigator.push(context, MaterialPageRoute(builder: (_) => const GameScreen())); }, + child: CustomPaint(painter: DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 300), child: Container(height: 65, width: double.infinity, alignment: Alignment.center, child: Text(loc.startGame, style: getSharedTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: inkColor))))), ), ) ], @@ -756,8 +459,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { : Container( 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 || themeType == AppThemeType.music ? null : Border.all(color: Colors.white.withOpacity(0.15), width: 1.5), + borderRadius: BorderRadius.circular(25), 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( @@ -767,37 +469,16 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Row( - children: [ - SizedBox( - width: 40, - child: IconButton( - padding: EdgeInsets.zero, - alignment: Alignment.centerLeft, - icon: Icon(Icons.arrow_back_ios_new, color: theme.text, size: 26), - onPressed: () => Navigator.pop(ctx), - ), - ), - Expanded( - child: Text(isVsCPU ? loc.cpuTitle : loc.localTitle, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))), - ), - const SizedBox(width: 40), - ], - ), + Row(children: [ SizedBox(width: 40, child: IconButton(padding: EdgeInsets.zero, alignment: Alignment.centerLeft, icon: Icon(Icons.arrow_back_ios_new, color: theme.text, size: 26), onPressed: () => Navigator.pop(ctx))), Expanded(child: Text(isVsCPU ? loc.cpuTitle : loc.localTitle, textAlign: TextAlign.center, style: getSharedTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2)))), const SizedBox(width: 40) ]), const SizedBox(height: 20), if (isVsCPU) ...[ - Icon(Icons.smart_toy, size: 50, color: theme.playerBlue), - const SizedBox(height: 10), - Text("MODALITÀ CAMPAGNA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), - const SizedBox(height: 10), - Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 13, color: theme.text.withOpacity(0.7), height: 1.4))), - const SizedBox(height: 20), - Divider(color: Colors.white.withOpacity(0.05), thickness: 2), - const SizedBox(height: 20), + Icon(Icons.smart_toy, size: 50, color: theme.playerBlue), const SizedBox(height: 10), + Text("MODALITÀ CAMPAGNA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), const SizedBox(height: 10), + Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: getSharedTextStyle(themeType, TextStyle(fontSize: 13, color: theme.text.withOpacity(0.7), height: 1.4))), const SizedBox(height: 20), + Divider(color: Colors.white.withOpacity(0.05), thickness: 2), const SizedBox(height: 20), ] else ...[ - Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), - const SizedBox(height: 10), + Text("FORMA ARENA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 10), Wrap( spacing: 10, runSpacing: 10, alignment: WrapAlignment.center, children: [ @@ -808,13 +489,9 @@ class _HomeScreenState extends State with WidgetsBindingObserver { _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), ], ), + const SizedBox(height: 20), Divider(color: Colors.white.withOpacity(0.05), thickness: 2), const SizedBox(height: 20), - const SizedBox(height: 20), - Divider(color: Colors.white.withOpacity(0.05), thickness: 2), - const SizedBox(height: 20), - - Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), - const SizedBox(height: 10), + Text("GRANDEZZA", style: getSharedTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -824,27 +501,17 @@ class _HomeScreenState extends State with WidgetsBindingObserver { _NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), ], ), - - const SizedBox(height: 20), - Divider(color: Colors.white.withOpacity(0.05), thickness: 2), - const SizedBox(height: 20), + const SizedBox(height: 20), Divider(color: Colors.white.withOpacity(0.05), thickness: 2), const SizedBox(height: 20), ], - Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), - const SizedBox(height: 10), - _NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)), - - const SizedBox(height: 30), + Text("TEMPO", style: getSharedTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 10), + _NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)), const SizedBox(height: 30), SizedBox( width: double.infinity, height: 60, child: ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: isVsCPU ? Colors.purple.shade400 : theme.playerRed, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20))), - onPressed: () { - Navigator.pop(ctx); - context.read().startNewGame(localRadius, vsCPU: isVsCPU, shape: localShape, timeMode: localTimeMode); - Navigator.push(context, MaterialPageRoute(builder: (_) => const GameScreen())); - }, + onPressed: () { Navigator.pop(ctx); context.read().startNewGame(localRadius, vsCPU: isVsCPU, shape: localShape, timeMode: localTimeMode); Navigator.push(context, MaterialPageRoute(builder: (_) => const GameScreen())); }, child: Text(loc.startGame, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900, letterSpacing: 2)), ), ) @@ -855,7 +522,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) { - dialogContent = _AnimatedCyberBorder(child: dialogContent); + dialogContent = AnimatedCyberBorder(child: dialogContent); } return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), child: dialogContent); @@ -865,337 +532,14 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); } - Future _showDailyQuestsDialog() async { - final prefs = await SharedPreferences.getInstance(); - showDialog( - context: context, - barrierColor: Colors.black.withOpacity(0.8), - builder: (ctx) { - final themeManager = ctx.watch(); - final theme = themeManager.currentColors; - final themeType = themeManager.currentThemeType; - Color inkColor = const Color(0xFF111122); - final loc = AppLocalizations.of(context)!; - - return Dialog( - backgroundColor: Colors.transparent, - insetPadding: const EdgeInsets.all(20), - child: Container( - padding: const EdgeInsets.all(25.0), - 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: Border.all(color: theme.playerBlue.withOpacity(0.5), width: 2), - boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)] - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.assignment_turned_in, size: 50, color: theme.playerBlue), - const SizedBox(height: 10), - Text(loc.questsTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), - const SizedBox(height: 25), - - ...List.generate(3, (index) { - int i = index + 1; - int type = prefs.getInt('q${i}_type') ?? 0; - int prog = prefs.getInt('q${i}_prog') ?? 0; - int target = prefs.getInt('q${i}_target') ?? 1; - - String title = ""; - IconData icon = Icons.star; - if (type == 0) { title = "Vinci partite Online"; icon = Icons.public; } - else if (type == 1) { title = "Vinci contro la CPU"; icon = Icons.smart_toy; } - else { title = "Gioca in Arene Speciali"; icon = Icons.extension; } - - bool completed = prog >= target; - double percent = (prog / target).clamp(0.0, 1.0); - - return Container( - margin: const EdgeInsets.only(bottom: 15), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: completed ? Colors.green.withOpacity(0.1) : theme.text.withOpacity(0.05), - borderRadius: BorderRadius.circular(15), - border: Border.all(color: completed ? Colors.green : theme.gridLine.withOpacity(0.3)), - ), - child: Row( - children: [ - Icon(icon, color: completed ? Colors.green : theme.text.withOpacity(0.6), size: 30), - const SizedBox(width: 15), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: completed ? Colors.green : theme.text))), - const SizedBox(height: 6), - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: LinearProgressIndicator( - value: percent, - backgroundColor: theme.gridLine.withOpacity(0.2), - color: completed ? Colors.green : theme.playerBlue, - minHeight: 8, - ), - ) - ], - ), - ), - const SizedBox(width: 10), - Text("$prog / $target", style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6)))), - ], - ), - ); - }), - - const SizedBox(height: 15), - SizedBox( - width: double.infinity, height: 50, - child: ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), - onPressed: () => Navigator.pop(ctx), - child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)), - ), - ) - ], - ), - ), - ); - } - ); - } - - void _showLeaderboardDialog() { - showDialog( - context: context, - barrierColor: Colors.black.withOpacity(0.8), - builder: (ctx) { - final themeManager = ctx.watch(); - final theme = themeManager.currentColors; - final themeType = themeManager.currentThemeType; - final loc = AppLocalizations.of(context)!; - - Widget content = Container( - padding: const EdgeInsets.all(20.0), - 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: Border.all(color: Colors.amber.withOpacity(0.8), width: 2), - boxShadow: [BoxShadow(color: Colors.amber.withOpacity(0.2), blurRadius: 20, spreadRadius: 5)] - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.emoji_events, size: 50, color: Colors.amber), - const SizedBox(height: 10), - Text(loc.leaderboardTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), - const SizedBox(height: 20), - - SizedBox( - height: 350, - child: StreamBuilder( - stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('xp', descending: true).limit(50).snapshots(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Center(child: CircularProgressIndicator(color: theme.playerBlue)); - } - if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { - return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5)))); - } - - final docs = snapshot.data!.docs; - return ListView.builder( - physics: const BouncingScrollPhysics(), - itemCount: docs.length, - itemBuilder: (context, index) { - var data = docs[index].data() as Map; - - String? myUid = FirebaseAuth.instance.currentUser?.uid; - bool isMe = docs[index].id == myUid; - - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - decoration: BoxDecoration( - color: isMe ? theme.playerBlue.withOpacity(0.2) : theme.text.withOpacity(0.05), - borderRadius: BorderRadius.circular(10), - border: isMe ? Border.all(color: theme.playerBlue, width: 1.5) : null - ), - child: Row( - children: [ - Text("#${index + 1}", style: _getTextStyle(themeType, TextStyle(fontWeight: FontWeight.w900, color: index == 0 ? Colors.amber : (index == 1 ? Colors.grey.shade400 : (index == 2 ? Colors.brown.shade300 : theme.text.withOpacity(0.5)))))), - const SizedBox(width: 15), - Expanded(child: Text(data['name'] ?? 'Unknown', style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: isMe ? FontWeight.w900 : FontWeight.bold, color: theme.text)))), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text("Lv. ${data['level'] ?? 1}", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 12)), - Text("${data['xp'] ?? 0} XP", style: TextStyle(color: theme.text.withOpacity(0.6), fontSize: 10)), - ], - ) - ], - ), - ); - } - ); - } - ), - ), - - const SizedBox(height: 15), - SizedBox( - width: double.infinity, height: 50, - child: ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: Colors.amber.shade700, foregroundColor: Colors.black, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), - onPressed: () => Navigator.pop(ctx), - child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)), - ), - ) - ], - ), - ); - - if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) content = _AnimatedCyberBorder(child: content); - return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: content); - } - ); - } - - void _showTutorialDialog() { - showDialog( - context: context, - barrierColor: Colors.black.withOpacity(0.8), - builder: (ctx) { - final themeManager = ctx.watch(); - final theme = themeManager.currentColors; - final themeType = themeManager.currentThemeType; - Color inkColor = const Color(0xFF111122); - final loc = AppLocalizations.of(context)!; - - String goldLabel = themeType == AppThemeType.grimorio ? "CORONA:" : "ORO:"; - String bombLabel = themeType == AppThemeType.grimorio ? "STREGA:" : "BOMBA:"; - String jokerLabel = themeType == AppThemeType.grimorio ? "GIULLARE:" : "JOLLY:"; - - Widget dialogContent = themeType == AppThemeType.doodle - ? Transform.rotate( - angle: -0.01, - child: CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.yellow.shade50, strokeColor: inkColor, seed: 400), - child: Padding( - padding: const EdgeInsets.all(25.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center(child: Text("COME GIOCARE", style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2)))), - const SizedBox(height: 20), - _TutorialStep(icon: Icons.line_axis, text: "Chiudi i 4 lati di un quadrato e conquisti 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 15), - _TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! ogni quadrato nasconde un insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 15), - const Divider(color: Colors.black26, thickness: 2), - const SizedBox(height: 10), - Center(child: Text("GLOSSARIO ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor)))), - const SizedBox(height: 10), - - _TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber.shade700, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: Colors.green.shade600, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme), - - const SizedBox(height: 25), - Center( - child: GestureDetector( - onTap: () => Navigator.pop(ctx), - child: CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.red.shade200, strokeColor: inkColor, seed: 401), - child: Container( - height: 50, width: 150, - alignment: Alignment.center, - child: Text("HO CAPITO!", style: _getTextStyle(themeType, TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: inkColor))), - ), - ), - ), - ) - ], - ), - ), - ), - ) - : Container( - padding: const EdgeInsets.all(25.0), - 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 || 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(), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center(child: Text("COME GIOCARE", style: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2)))), - const SizedBox(height: 20), - _TutorialStep(icon: Icons.grid_4x4, text: "Chiudi i 4 lati di un quadrato e conquisti 1 punto e avere una mossa extra!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 15), - _TutorialStep(icon: Icons.lens_blur, text: "Ma presta attenzione! ogni quadrato nasconde un insidia o un regalo!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 15), - const Divider(color: Colors.white24, thickness: 1.5), - const SizedBox(height: 10), - Center(child: Text("GLOSSARIO ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.7), letterSpacing: 1.5)))), - const SizedBox(height: 15), - - _TutorialStep(icon: ThemeIcons.gold(themeType), iconColor: Colors.amber, text: "$goldLabel Chiudilo per ottenere +2 Punti.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.bomb(themeType), iconColor: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.greenAccent : Colors.deepPurple, text: "$bombLabel Non chiuderlo! Perderai -1 Punto.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "SCAMBIO: Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.joker(themeType), iconColor: theme.playerBlue, text: "$jokerLabel Scegli dove nasconderlo a inizio partita. Se lo chiudi tu +2, se lo chiude l'avversario -1!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "GHIACCIO: Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "x2: Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme), - const SizedBox(height: 10), - _TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "BUCO NERO: Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme), - - const SizedBox(height: 30), - SizedBox( - width: double.infinity, height: 50, - child: ElevatedButton( - style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))), - onPressed: () => Navigator.pop(ctx), - child: const Text("CHIUDI", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2)), - ), - ) - ], - ), - ), - ); - - 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); - }, - ); - } + // =========================================================================== + // INTERFACCIA PRINCIPALE + // =========================================================================== Widget _buildCyberCard(Widget card, AppThemeType themeType) { - if (themeType == AppThemeType.cyberpunk) { - return _AnimatedCyberBorder(child: card); - } - return card; // La card musicale ha già i propri effetti integrati + if (themeType == AppThemeType.cyberpunk) return AnimatedCyberBorder(child: card); + return card; } @override @@ -1229,9 +573,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), + constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0), @@ -1247,7 +589,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { children: [ themeType == AppThemeType.doodle ? CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: 1, isCircle: true), + painter: DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: 1, isCircle: true), child: SizedBox(width: 50, height: 50, child: Icon(Icons.person, color: inkColor, size: 30)), ) : SizedBox( @@ -1271,8 +613,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(playerName, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontSize: 24, fontWeight: FontWeight.w900, letterSpacing: 1.5, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)]))), - Text("LIV. $level", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.8) : theme.playerBlue, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1))), + Text(playerName, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontSize: 24, fontWeight: FontWeight.w900, letterSpacing: 1.5, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)]))), + Text("LIV. $level", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.8) : theme.playerBlue, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1))), ], ), ], @@ -1285,15 +627,15 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ? Transform.rotate( angle: 0.04, child: CustomPaint( - painter: _DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 2), + painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 2), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ Icon(Icons.emoji_events, color: inkColor, size: 20), const SizedBox(width: 6), - Text("$wins", style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), const SizedBox(width: 12), + Text("$wins", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), const SizedBox(width: 12), Icon(Icons.sentiment_very_dissatisfied, color: inkColor, size: 20), const SizedBox(width: 6), - Text("$losses", style: _getTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), + Text("$losses", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), ], ), ), @@ -1310,9 +652,9 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Row( children: [ 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), + Text("$wins", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), const SizedBox(width: 12), Icon(themeType == AppThemeType.music ? FontAwesomeIcons.compactDisc : Icons.sentiment_very_dissatisfied, color: theme.playerRed.withOpacity(0.8), size: 16), const SizedBox(width: 6), - Text("$losses", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), + Text("$losses", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), ], ), ), @@ -1332,7 +674,7 @@ 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: 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; @@ -1343,20 +685,14 @@ class _HomeScreenState extends State with WidgetsBindingObserver { fit: BoxFit.scaleDown, child: Text( loc.appTitle.toUpperCase(), - style: _getTextStyle(themeType, TextStyle( + style: getSharedTextStyle(themeType, TextStyle( fontSize: 65, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? inkColor : theme.text, letterSpacing: 10, shadows: themeType == AppThemeType.doodle - ? [ - const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 0), - Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(2.5, 2.5), blurRadius: 1), - ] - : themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [ - BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(3, 6), blurRadius: 8), - BoxShadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20), - ] + ? [const 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)] )) ), ), @@ -1366,53 +702,39 @@ class _HomeScreenState extends State with WidgetsBindingObserver { const Spacer(), - // --- GESTIONE DEI MENU IN BASE AL TEMA --- + // --- 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())); }, - ), + 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), - ), + 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), - ), + 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, + 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)), + Expanded(child: MusicKnobCard(title: loc.leaderboardTitle, icon: FontAwesomeIcons.compactDisc, iconColor: Colors.amber, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()))), + Expanded(child: MusicKnobCard(title: loc.questsTitle, icon: FontAwesomeIcons.microphoneLines, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()))), + Expanded(child: MusicKnobCard(title: loc.themesTitle, icon: FontAwesomeIcons.palette, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())))), + Expanded(child: MusicKnobCard(title: loc.tutorialTitle, icon: FontAwesomeIcons.bookOpen, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()))), ], ), ] else ...[ - // --- 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), + _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), + _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), + _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)), + Expanded(child: _buildCyberCard(FeatureCard(title: loc.leaderboardTitle, subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()), compact: true), themeType)), const SizedBox(width: 12), - Expanded(child: _buildCyberCard(_FeatureCard(title: loc.questsTitle, subtitle: "Missioni", icon: Icons.assignment_turned_in, color: Colors.green.shade200, theme: theme, themeType: themeType, onTap: _showDailyQuestsDialog, compact: true), themeType)), + Expanded(child: _buildCyberCard(FeatureCard(title: loc.questsTitle, subtitle: "Missioni", icon: Icons.assignment_turned_in, color: Colors.green.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()), compact: true), themeType)), ], ), @@ -1420,9 +742,9 @@ class _HomeScreenState extends State with WidgetsBindingObserver { 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)), + 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)), + Expanded(child: _buildCyberCard(FeatureCard(title: loc.tutorialTitle, subtitle: "Come giocare", icon: Icons.school, color: Colors.indigo.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()), compact: true), themeType)), ], ), ], @@ -1444,492 +766,24 @@ class _HomeScreenState extends State with WidgetsBindingObserver { body: Stack( children: [ 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 || themeType == AppThemeType.music) - ? Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.8)] - ) - ), - ) - : const SizedBox(), - ), + // Sfondo per il tema Doodle + if (themeType == AppThemeType.doodle) + Positioned.fill(child: CustomPaint(painter: DoodleBackgroundPainter(fillColor: Colors.white, strokeColor: Colors.blue.withOpacity(0.15), seed: 0, isCircle: false))), + + if (bgImage != null) + Positioned.fill(child: Image.asset(bgImage, fit: BoxFit.cover, alignment: Alignment.center)), + + 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.4), Colors.black.withOpacity(0.8)])))), + + // Cavi musicali per il tema Musica if (themeType == AppThemeType.music) - Positioned.fill( - child: IgnorePointer( - child: CustomPaint( - painter: _AudioCablesPainter(), - ), - ), - ), + Positioned.fill(child: IgnorePointer(child: CustomPaint(painter: AudioCablesPainter()))), Positioned.fill(child: uiContent), ], ), ); } -} - -class _TutorialStep extends StatelessWidget { - final IconData icon; - final Color? iconColor; - final String text; - final AppThemeType themeType; - final Color inkColor; - final ThemeColors theme; - - const _TutorialStep({required this.icon, this.iconColor, required this.text, required this.themeType, required this.inkColor, required this.theme}); - - @override - Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(icon, color: iconColor ?? (themeType == AppThemeType.doodle ? inkColor : theme.playerBlue), size: 28), - const SizedBox(width: 15), - Expanded( - child: Text(text, style: _getTextStyle(themeType, TextStyle(fontSize: 14, color: themeType == AppThemeType.doodle ? inkColor : theme.text.withOpacity(0.8), height: 1.3))), - ), - ], - ); - } -} - -// --- 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; - final IconData icon; - final Color color; - final ThemeColors theme; - final AppThemeType themeType; - final VoidCallback onTap; - final bool isFeatured; - final bool compact; - - const _FeatureCard({required this.title, required this.subtitle, required this.icon, required this.color, required this.theme, required this.themeType, required this.onTap, this.isFeatured = false, this.compact = false}); - - @override - Widget build(BuildContext context) { - if (themeType == AppThemeType.doodle) { - double tilt = (title.length % 2 == 0) ? -0.015 : 0.02; - Color inkColor = const Color(0xFF111122); - - return Transform.rotate( - angle: tilt, - child: GestureDetector( - onTap: onTap, - child: CustomPaint( - painter: _DoodleBackgroundPainter( - fillColor: color, - strokeColor: inkColor, - seed: title.length * 5, - ), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 22.0, vertical: compact ? 12.0 : 16.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(icon, color: inkColor, size: compact ? 24 : 32), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text(title, style: _getTextStyle(themeType, TextStyle(color: inkColor, fontSize: compact ? 16 : 24, fontWeight: FontWeight.w900))), - ), - if (!compact) ...[ - const SizedBox(height: 2), - Text(subtitle, style: _getTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 14, fontWeight: FontWeight.bold))), - ] - ], - ), - ), - if (!compact) Icon(Icons.chevron_right_rounded, color: inkColor.withOpacity(0.6), size: 32), - ], - ), - ), - ), - ), - ); - } - - return GestureDetector( - onTap: onTap, - child: Container( - padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 20.0, vertical: compact ? 10.0 : 14.0), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: isFeatured - ? [color.withOpacity(0.9), color.withOpacity(0.6)] - : [color.withOpacity(0.25), color.withOpacity(0.05)], - ), - borderRadius: BorderRadius.circular(15), - border: Border.all(color: color.withOpacity(isFeatured ? 0.5 : 0.2), width: 1.5), - boxShadow: [ - BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(0, 8), blurRadius: 15), - BoxShadow(color: color.withOpacity(isFeatured ? 0.3 : 0.05), offset: const Offset(-1, -1), blurRadius: 5), - ] - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.all(compact ? 6 : 10), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.white.withOpacity(0.3), Colors.white.withOpacity(0.05)], - ), - shape: BoxShape.circle, - border: Border.all(color: Colors.white.withOpacity(0.2)), - boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 5, offset: const Offset(2, 4))] - ), - child: Icon(icon, color: isFeatured ? Colors.white : color, size: compact ? 20 : 26), - ), - SizedBox(width: compact ? 10 : 20), - - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: Text(title, style: _getTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white : theme.text, fontSize: compact ? 14 : 18, fontWeight: FontWeight.w900, shadows: [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)]))), - ), - if (!compact) ...[ - const SizedBox(height: 2), - Text(subtitle, style: _getTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white.withOpacity(0.8) : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: FontWeight.bold))), - ] - ], - ), - ), - - if (!compact) Icon(Icons.chevron_right_rounded, color: isFeatured ? Colors.white.withOpacity(0.7) : color.withOpacity(0.5), size: 30), - ], - ), - ), - ); - } -} - -class _AnimatedCyberBorder extends StatefulWidget { - final Widget child; - const _AnimatedCyberBorder({required this.child}); - @override - State<_AnimatedCyberBorder> createState() => _AnimatedCyberBorderState(); -} - -class _AnimatedCyberBorderState extends State<_AnimatedCyberBorder> with SingleTickerProviderStateMixin { - late AnimationController _controller; - @override - void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); } - @override - void dispose() { _controller.dispose(); super.dispose(); } - @override - Widget build(BuildContext context) { - final theme = context.watch().currentColors; - return AnimatedBuilder( - animation: _controller, - builder: (context, child) { - return CustomPaint( - painter: _CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed), - child: Container( - decoration: BoxDecoration(color: theme.background.withOpacity(0.9), borderRadius: BorderRadius.circular(15), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 25, spreadRadius: 2)]), - padding: const EdgeInsets.all(3), - child: widget.child, - ), - ); - }, - child: widget.child, - ); - } -} - -class _CyberBorderPainter extends CustomPainter { - final double animationValue; - final Color color1; - final Color color2; - - _CyberBorderPainter({required this.animationValue, required this.color1, required this.color2}); - - @override - void paint(Canvas canvas, Size size) { - final rect = Offset.zero & size; - final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(15)); - final Paint paint = Paint() - ..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect) - ..style = PaintingStyle.stroke - ..strokeWidth = 4.0 - ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 4); - canvas.drawRRect(rrect, paint); - } - - @override - bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue; } \ No newline at end of file diff --git a/lib/widgets/cyber_border.dart b/lib/widgets/cyber_border.dart new file mode 100644 index 0000000..bd085f5 --- /dev/null +++ b/lib/widgets/cyber_border.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../core/theme_manager.dart'; // Import aggiornato +import 'dart:math' as math; + +class AnimatedCyberBorder extends StatefulWidget { + final Widget child; + const AnimatedCyberBorder({super.key, required this.child}); + @override + State createState() => _AnimatedCyberBorderState(); +} + +class _AnimatedCyberBorderState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + @override + void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); } + @override + void dispose() { _controller.dispose(); super.dispose(); } + @override + Widget build(BuildContext context) { + final theme = context.watch().currentColors; + return AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + painter: CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed), + child: Container( + decoration: BoxDecoration(color: theme.background.withOpacity(0.9), borderRadius: BorderRadius.circular(15), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 25, spreadRadius: 2)]), + padding: const EdgeInsets.all(3), + child: widget.child, + ), + ); + }, + child: widget.child, + ); + } +} + +class CyberBorderPainter extends CustomPainter { + final double animationValue; + final Color color1; + final Color color2; + CyberBorderPainter({required this.animationValue, required this.color1, required this.color2}); + + @override + void paint(Canvas canvas, Size size) { + final rect = Offset.zero & size; + final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(15)); + final Paint paint = Paint() + ..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect) + ..style = PaintingStyle.stroke + ..strokeWidth = 4.0 + ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 4); + canvas.drawRRect(rrect, paint); + } + @override bool shouldRepaint(covariant CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue; +} \ No newline at end of file diff --git a/lib/widgets/home_buttons.dart b/lib/widgets/home_buttons.dart new file mode 100644 index 0000000..360e91b --- /dev/null +++ b/lib/widgets/home_buttons.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import '../core/app_colors.dart'; // Import aggiornato +import 'painters.dart'; + +class FeatureCard extends StatelessWidget { + final String title; + final String subtitle; + final IconData icon; + final Color color; + final ThemeColors theme; + final AppThemeType themeType; + final VoidCallback onTap; + final bool isFeatured; + final bool compact; + + const FeatureCard({super.key, required this.title, required this.subtitle, required this.icon, required this.color, required this.theme, required this.themeType, required this.onTap, this.isFeatured = false, this.compact = false}); + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + double tilt = (title.length % 2 == 0) ? -0.015 : 0.02; + Color inkColor = const Color(0xFF111122); + + return Transform.rotate( + angle: tilt, + child: GestureDetector( + onTap: onTap, + child: CustomPaint( + painter: DoodleBackgroundPainter(fillColor: color, strokeColor: inkColor, seed: title.length * 5), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 22.0, vertical: compact ? 12.0 : 16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(icon, color: inkColor, size: compact ? 24 : 32), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: compact ? 16 : 24, fontWeight: FontWeight.w900)))), + if (!compact) ...[ const SizedBox(height: 2), Text(subtitle, style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 14, fontWeight: FontWeight.bold))) ] + ], + ), + ), + if (!compact) Icon(Icons.chevron_right_rounded, color: inkColor.withOpacity(0.6), size: 32), + ], + ), + ), + ), + ), + ); + } + + return GestureDetector( + onTap: onTap, + child: Container( + padding: EdgeInsets.symmetric(horizontal: compact ? 12.0 : 20.0, vertical: compact ? 10.0 : 14.0), + decoration: BoxDecoration( + gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isFeatured ? [color.withOpacity(0.9), color.withOpacity(0.6)] : [color.withOpacity(0.25), color.withOpacity(0.05)]), + borderRadius: BorderRadius.circular(15), border: Border.all(color: color.withOpacity(isFeatured ? 0.5 : 0.2), width: 1.5), + boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(0, 8), blurRadius: 15), BoxShadow(color: color.withOpacity(isFeatured ? 0.3 : 0.05), offset: const Offset(-1, -1), blurRadius: 5)] + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(compact ? 6 : 10), + decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.white.withOpacity(0.3), Colors.white.withOpacity(0.05)]), shape: BoxShape.circle, border: Border.all(color: Colors.white.withOpacity(0.2)), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 5, offset: const Offset(2, 4))]), + child: Icon(icon, color: isFeatured ? Colors.white : color, size: compact ? 20 : 26), + ), + SizedBox(width: compact ? 10 : 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white : theme.text, fontSize: compact ? 14 : 18, fontWeight: FontWeight.w900, shadows: [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)])))), + if (!compact) ...[ const SizedBox(height: 2), Text(subtitle, style: getSharedTextStyle(themeType, TextStyle(color: isFeatured ? Colors.white.withOpacity(0.8) : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: FontWeight.bold))) ] + ], + ), + ), + if (!compact) Icon(Icons.chevron_right_rounded, color: isFeatured ? Colors.white.withOpacity(0.7) : color.withOpacity(0.5), size: 30), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/music_theme_widgets.dart b/lib/widgets/music_theme_widgets.dart new file mode 100644 index 0000000..a3936bf --- /dev/null +++ b/lib/widgets/music_theme_widgets.dart @@ -0,0 +1,129 @@ +// =========================================================================== +// FILE: lib/widgets/music_theme_widgets.dart +// =========================================================================== + +import 'package:flutter/material.dart'; +import '../core/app_colors.dart'; +import 'painters.dart'; + +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({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) { + 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), + 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) ] + ), + 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)), + child: Row( + children: [ + Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(leftIcon, color: neonColor, size: 28)), + 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))))), + ], + ), + ), + Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(rightIcon, color: neonColor, size: 28)), + ], + ), + ), + ), + const SizedBox(height: 10), + 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: [ + 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({super.key, 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), 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, 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), + 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: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.0)))), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/painters.dart b/lib/widgets/painters.dart new file mode 100644 index 0000000..6c84bbc --- /dev/null +++ b/lib/widgets/painters.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'dart:math' as math; +import '../core/app_colors.dart'; // Import aggiornato + +TextStyle getSharedTextStyle(AppThemeType themeType, TextStyle baseStyle) { + if (themeType == AppThemeType.doodle) { + return GoogleFonts.permanentMarker(textStyle: baseStyle); + } else if (themeType == AppThemeType.arcade) { + return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith(fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null, letterSpacing: 0.5)); + } else if (themeType == AppThemeType.grimorio) { + return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold)); + } else if (themeType == AppThemeType.music) { + return GoogleFonts.audiowide(textStyle: baseStyle.copyWith(letterSpacing: 1.5)); + } + return baseStyle; +} + +class DoodleBackgroundPainter extends CustomPainter { + final Color fillColor; final Color strokeColor; final int seed; final bool isCircle; + DoodleBackgroundPainter({required this.fillColor, required this.strokeColor, required this.seed, this.isCircle = false}); + + @override + void paint(Canvas canvas, Size size) { + final math.Random random = math.Random(seed); + double wobble() => random.nextDouble() * 6 - 3; + final Paint fillPaint = Paint()..color = fillColor..style = PaintingStyle.fill; + final Paint strokePaint = Paint()..color = strokeColor..strokeWidth = 2.5..style = PaintingStyle.stroke..strokeCap = StrokeCap.round..strokeJoin = StrokeJoin.round; + + if (isCircle) { + final Rect rect = Rect.fromLTWH(wobble(), wobble(), size.width + wobble(), size.height + wobble()); + canvas.save(); canvas.translate(wobble(), wobble()); canvas.drawOval(rect, fillPaint); canvas.restore(); + canvas.drawOval(rect, strokePaint); + canvas.save(); canvas.translate(random.nextDouble() * 4 - 2, random.nextDouble() * 4 - 2); canvas.drawOval(rect, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6)); canvas.restore(); + } else { + final Path path = Path()..moveTo(wobble(), wobble())..lineTo(size.width + wobble(), wobble())..lineTo(size.width + wobble(), size.height + wobble())..lineTo(wobble(), size.height + wobble())..close(); + final Path fillPath = Path()..moveTo(wobble() * 1.5, wobble() * 1.5)..lineTo(size.width + wobble() * 1.5, wobble() * 1.5)..lineTo(size.width + wobble() * 1.5, size.height + wobble() * 1.5)..lineTo(wobble() * 1.5, size.height + wobble() * 1.5)..close(); + canvas.drawPath(fillPath, fillPaint); + canvas.drawPath(path, strokePaint); + canvas.save(); canvas.translate(random.nextDouble() * 3 - 1.5, random.nextDouble() * 3 - 1.5); canvas.drawPath(path, strokePaint..strokeWidth = 1.0..color = strokeColor.withOpacity(0.6)); canvas.restore(); + } + } + @override bool shouldRepaint(covariant DoodleBackgroundPainter oldDelegate) => oldDelegate.fillColor != fillColor || oldDelegate.strokeColor != strokeColor; +} + +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); } + + 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); + 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); + 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); + + _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); + canvas.drawRect(const Rect.fromLTWH(-15, -4, 15, 8), Paint()..color = const Color(0xFF151515)); + 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)); + 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)); + 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; +} \ No newline at end of file