Compare commits

...

3 commits

Author SHA1 Message Date
ea54bf3a12 Auto-sync: 20260315_033147 2026-03-15 03:32:09 +01:00
8ef703c082 Auto-sync: 20260315_030000 2026-03-15 03:00:01 +01:00
6e5fb8a984 Auto-sync: 20260315_020000 2026-03-15 02:00:01 +01:00
9 changed files with 731 additions and 625 deletions

BIN
assets/.DS_Store vendored

Binary file not shown.

BIN
assets/images/arcade.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
assets/images/grimorio.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View file

@ -5,7 +5,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio, music } // <-- Aggiunto 'music' enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio, music }
class ThemeColors { class ThemeColors {
final Color background; final Color background;
@ -45,16 +45,15 @@ class AppColors {
); );
static const ThemeColors grimorio = ThemeColors( static const ThemeColors grimorio = ThemeColors(
background: Color(0xFF1E112A), gridLine: Color(0xFF8D6E63), background: Color(0xFF1E112A), gridLine: Colors.black, // <--- Modificato in nero!
playerRed: Color(0xFFE91E63), playerBlue: Color(0xFF4FC3F7), text: Color(0xFFFFF3E0), playerRed: Color(0xFFE91E63), playerBlue: Color(0xFF4FC3F7), text: Color(0xFFFFF3E0),
); );
// --- NUOVO TEMA MUSICA ---
static const ThemeColors music = ThemeColors( static const ThemeColors music = ThemeColors(
background: Color(0xFF120B29), // Viola scuro (stile Synthwave) background: Color(0xFF120B29),
gridLine: Color(0xFF6A1B9A), // Viola elettrico gridLine: Color(0xFF6A1B9A),
playerRed: Color(0xFFFF2A6D), // Rosa acceso playerRed: Color(0xFFFF2A6D),
playerBlue: Color(0xFF05D5FF), // Ciano playerBlue: Color(0xFF05D5FF),
text: Color(0xFFE0E0E0), text: Color(0xFFE0E0E0),
); );
@ -78,7 +77,7 @@ class ThemeIcons {
case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip; case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip;
case AppThemeType.arcade: return FontAwesomeIcons.coins; case AppThemeType.arcade: return FontAwesomeIcons.coins;
case AppThemeType.grimorio: return FontAwesomeIcons.crown; case AppThemeType.grimorio: return FontAwesomeIcons.crown;
case AppThemeType.music: return FontAwesomeIcons.compactDisc; // CD/Vinile per i punti case AppThemeType.music: return FontAwesomeIcons.compactDisc;
} }
} }
@ -89,7 +88,7 @@ class ThemeIcons {
case AppThemeType.cyberpunk: return FontAwesomeIcons.bug; case AppThemeType.cyberpunk: return FontAwesomeIcons.bug;
case AppThemeType.arcade: return FontAwesomeIcons.ghost; case AppThemeType.arcade: return FontAwesomeIcons.ghost;
case AppThemeType.grimorio: return FontAwesomeIcons.hatWizard; case AppThemeType.grimorio: return FontAwesomeIcons.hatWizard;
case AppThemeType.music: return FontAwesomeIcons.volumeXmark; // Muto/Errore per la bomba case AppThemeType.music: return FontAwesomeIcons.volumeXmark;
} }
} }
@ -100,7 +99,7 @@ class ThemeIcons {
case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired; case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired;
case AppThemeType.arcade: return FontAwesomeIcons.shuffle; case AppThemeType.arcade: return FontAwesomeIcons.shuffle;
case AppThemeType.grimorio: return FontAwesomeIcons.hurricane; case AppThemeType.grimorio: return FontAwesomeIcons.hurricane;
case AppThemeType.music: return FontAwesomeIcons.sliders; // Fader da DJ case AppThemeType.music: return FontAwesomeIcons.sliders;
} }
} }
@ -111,7 +110,7 @@ class ThemeIcons {
case AppThemeType.cyberpunk: return FontAwesomeIcons.robot; case AppThemeType.cyberpunk: return FontAwesomeIcons.robot;
case AppThemeType.arcade: return FontAwesomeIcons.gamepad; case AppThemeType.arcade: return FontAwesomeIcons.gamepad;
case AppThemeType.grimorio: return FontAwesomeIcons.masksTheater; case AppThemeType.grimorio: return FontAwesomeIcons.masksTheater;
case AppThemeType.music: return FontAwesomeIcons.headphones; // Cuffie per il Jolly case AppThemeType.music: return FontAwesomeIcons.headphones;
} }
} }
@ -122,17 +121,17 @@ class ThemeIcons {
case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved; case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved;
case AppThemeType.arcade: return FontAwesomeIcons.powerOff; case AppThemeType.arcade: return FontAwesomeIcons.powerOff;
case AppThemeType.grimorio: return FontAwesomeIcons.meteor; case AppThemeType.grimorio: return FontAwesomeIcons.meteor;
case AppThemeType.music: return FontAwesomeIcons.pause; // Pausa per il blocco vuoto case AppThemeType.music: return FontAwesomeIcons.pause;
} }
} }
static IconData ice(AppThemeType type) { static IconData ice(AppThemeType type) {
if (type == AppThemeType.music) return FontAwesomeIcons.music; // Nota musicale ghiacciata if (type == AppThemeType.music) return FontAwesomeIcons.music;
return FontAwesomeIcons.snowflake; return FontAwesomeIcons.snowflake;
} }
static IconData multiplier(AppThemeType type) { static IconData multiplier(AppThemeType type) {
if (type == AppThemeType.music) return FontAwesomeIcons.forwardFast; // Fast Forward per il x2 if (type == AppThemeType.music) return FontAwesomeIcons.forwardFast;
return FontAwesomeIcons.bolt; return FontAwesomeIcons.bolt;
} }
} }

View file

@ -54,28 +54,72 @@ class BoardPainter extends CustomPainter {
double offset = spacing / 2; double offset = spacing / 2;
Offset getScreenPos(int x, int y) => Offset(x * spacing + offset, y * spacing + offset); 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) { // 1. CREAZIONE DELLA SAGOMA DELL'ARENA (SFONDO E BORDO)
Path arenaShape = Path(); // =======================================================================
// Uniamo la forma di ogni box giocabile per creare il tappeto Path arenaShape = Path();
for (var box in board.boxes) { bool isFirst = true;
if (box.type != BoxType.invisible) { // Ignora i buchi
Offset p1 = getScreenPos(box.x, box.y); // Uniamo la forma di ogni box giocabile per creare un'unica sagoma
Offset p2 = getScreenPos(box.x + 1, box.y + 1); for (var box in board.boxes) {
arenaShape.addRect(Rect.fromPoints(p1, p2)); if (box.type != BoxType.invisible) { // Ignora i buchi
Offset p1 = getScreenPos(box.x, box.y);
Offset p2 = getScreenPos(box.x + 1, box.y + 1);
Path boxPath = Path()..addRect(Rect.fromPoints(p1, p2));
if (isFirst) {
arenaShape = boxPath;
isFirst = false;
} else {
arenaShape = Path.combine(PathOperation.union, arenaShape, boxPath);
} }
} }
// 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
);
} }
// -----------------------------------------------------------
// --- DISEGNO DELLO SFONDO LUMINOSO ---
final fillPaint = Paint()
..style = PaintingStyle.fill
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10.0);
if (themeType == AppThemeType.music) {
fillPaint.color = Colors.white.withOpacity(0.08);
canvas.drawPath(arenaShape, fillPaint);
} else if (themeType == AppThemeType.wood) {
fillPaint.color = Colors.black.withOpacity(0.3);
fillPaint.maskFilter = const MaskFilter.blur(BlurStyle.normal, 15.0);
canvas.drawPath(arenaShape, fillPaint);
} else if (themeType == AppThemeType.cyberpunk) {
fillPaint.color = theme.playerBlue.withOpacity(0.1);
canvas.drawPath(arenaShape, fillPaint);
}
// --- DISEGNO DEL BORDO ESTERNO SOTTILE ---
double baseStroke = themeType == AppThemeType.grimorio ? 6.0 : 4.0;
if (themeType == AppThemeType.doodle) baseStroke = 2.5;
final outlinePaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = baseStroke * 0.5 // Moltiplicato per 0.5 = grande la metà delle linee interne!
..strokeJoin = StrokeJoin.round;
if (themeType == AppThemeType.cyberpunk) {
outlinePaint.color = theme.gridLine;
outlinePaint.maskFilter = MaskFilter.blur(BlurStyle.solid, 4.0 * blinkValue.clamp(0.1, 1.0));
}
else if (themeType == AppThemeType.arcade) { outlinePaint.color = Colors.white; }
else if (themeType == AppThemeType.grimorio) { outlinePaint.color = theme.gridLine.withOpacity(0.6); }
else if (themeType == AppThemeType.music) { outlinePaint.color = Colors.black; } // Rimosso lo spessore forzato a 8.0!
else if (themeType == AppThemeType.doodle) { outlinePaint.color = const Color(0xFF111122); } // Rimosso lo spessore forzato a 6.0!
else if (themeType == AppThemeType.wood) {
outlinePaint.color = const Color(0xFF3E2723);
outlinePaint.maskFilter = const MaskFilter.blur(BlurStyle.normal, 2.0);
}
else { outlinePaint.color = theme.gridLine.withOpacity(0.8); }
// Disegniamo il contorno
canvas.drawPath(arenaShape, outlinePaint);
// =======================================================================
for (var box in board.boxes) { for (var box in board.boxes) {
Offset p1 = getScreenPos(box.x, box.y); Offset p1 = getScreenPos(box.x, box.y);

View file

@ -291,9 +291,11 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
String? bgImage; String? bgImage;
if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg'; if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg';
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; // Questo è lo sfondo carta if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg'; if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
if (themeType == AppThemeType.arcade) bgImage = 'assets/images/arcade.jpg';
if (themeType == AppThemeType.grimorio) bgImage = 'assets/images/grimorio.jpg';
Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.white : Colors.black; Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.music ? Colors.white : Colors.black;
@ -326,7 +328,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
Expanded( Expanded(
child: Center( child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(10.0), // PADDING RIDOTTO AL MINIMO: permette alla griglia di guadagnare pixel preziosi per allargarsi/alzarsi!
padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
int cols = gameController.board.columns + 1; int cols = gameController.board.columns + 1;
@ -341,19 +344,20 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
width: actualWidth, height: actualHeight, width: actualWidth, height: actualHeight,
child: Stack( child: Stack(
children: [ children: [
// --- IL VERO SFONDO SFOCATO SAGOMATO --- // --- IL VERO SFONDO SFOCATO SAGOMATO (ORA PER TUTTI I TEMI) ---
if (themeType == AppThemeType.music) Positioned.fill(
Positioned.fill( child: ClipPath(
child: ClipPath( clipper: _ArenaClipper(gameController.board),
clipper: _ArenaClipper(gameController.board), child: BackdropFilter(
child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), child: Container(
child: Container( color: themeType == AppThemeType.doodle
color: Colors.white.withOpacity(0.08), ? Colors.black.withOpacity(0.05)
), : Colors.white.withOpacity(0.12),
), ),
), ),
), ),
),
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@ -383,7 +387,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
), ),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 20.0, left: 20.0, right: 20.0), // PADDING RIDOTTO IN BASSO: 10 al posto di 20, per far esplodere la griglia verticalmente
padding: const EdgeInsets.only(bottom: 10.0, left: 20.0, right: 20.0, top: 5.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -429,23 +434,13 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
canPop: true, canPop: true,
onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); }, onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); },
child: Scaffold( child: Scaffold(
backgroundColor: Colors.transparent, // Assicuriamo che la scaffold base sia trasparente backgroundColor: themeType == AppThemeType.doodle ? Colors.white : (bgImage != null ? Colors.transparent : theme.background),
body: Stack( body: Stack(
children: [ children: [
// 1. Sfondo base a tinta unita (In caso non ci sia l'immagine) // 1. Sfondo base a tinta unita
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background), Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
// 2. Immagine di Sfondo per tutti i temi che la supportano // 2. Griglia a quadretti (Doodle Theme)
if (bgImage != null)
Positioned.fill(
child: Image.asset(
bgImage,
fit: BoxFit.cover,
alignment: Alignment.center,
),
),
// 3. Griglia a righe incrociate per il doodle (Sopra l'immagine di carta)
if (themeType == AppThemeType.doodle) if (themeType == AppThemeType.doodle)
Positioned.fill( Positioned.fill(
child: CustomPaint( child: CustomPaint(
@ -453,8 +448,20 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
), ),
), ),
// 4. Patina scura (Cyberpunk e Music) per far risaltare il neon // 3. Immagine di Sfondo
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music)) if (bgImage != null)
Positioned.fill(
child: Image.asset(
bgImage,
fit: BoxFit.cover,
alignment: Alignment.center,
color: themeType == AppThemeType.doodle ? Colors.white.withOpacity(0.5) : null,
colorBlendMode: themeType == AppThemeType.doodle ? BlendMode.lighten : null,
),
),
// 4. Patina scura (Cyberpunk, Music, Arcade e Grimorio)
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio))
Positioned.fill( Positioned.fill(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(

View file

@ -1,5 +1,5 @@
// =========================================================================== // ===========================================================================
// FILE: lib/ui/home/dialogs/dialog.dart // FILE: lib/ui/home/dialog.dart
// =========================================================================== // ===========================================================================
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -8,11 +8,11 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_auth/firebase_auth.dart';
import '../../../core/theme_manager.dart'; import '../../core/theme_manager.dart';
import '../../../core/app_colors.dart'; import '../../core/app_colors.dart';
import '../../../l10n/app_localizations.dart'; import '../../l10n/app_localizations.dart';
import '../../../widgets/painters.dart'; import '../../widgets/painters.dart';
import '../../../widgets/cyber_border.dart'; import '../../widgets/cyber_border.dart';
// =========================================================================== // ===========================================================================
// 1. DIALOGO MISSIONI (QUESTS) // 1. DIALOGO MISSIONI (QUESTS)
@ -158,14 +158,29 @@ class LeaderboardDialog extends StatelessWidget {
return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5)))); return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5))));
} }
final docs = snapshot.data!.docs; // 1. ESTRAIAMO TUTTI I DOCUMENTI
final rawDocs = snapshot.data!.docs;
// 2. APPLICHIAMO IL FILTRO PER NASCONDERE "PAOLO"
final filteredDocs = rawDocs.where((doc) {
var data = doc.data() as Map<String, dynamic>;
String name = (data['name'] ?? '').toString().toUpperCase();
return name != 'PAOLO';
}).toList();
// 3. SE DOPO IL FILTRO NON C'E' NESSUNO
if (filteredDocs.isEmpty) {
return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5))));
}
return ListView.builder( return ListView.builder(
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
itemCount: docs.length, itemCount: filteredDocs.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var data = docs[index].data() as Map<String, dynamic>; var doc = filteredDocs[index];
var data = doc.data() as Map<String, dynamic>;
String? myUid = FirebaseAuth.instance.currentUser?.uid; String? myUid = FirebaseAuth.instance.currentUser?.uid;
bool isMe = docs[index].id == myUid; bool isMe = doc.id == myUid;
return Container( return Container(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
@ -230,9 +245,50 @@ class TutorialDialog extends StatelessWidget {
final themeType = themeManager.currentThemeType; final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122); Color inkColor = const Color(0xFF111122);
String goldLabel = themeType == AppThemeType.grimorio ? "CORONA:" : "ORO:"; // ETICHETTE DINAMICHE PER I POTENZIAMENTI
String bombLabel = themeType == AppThemeType.grimorio ? "STREGA:" : "BOMBA:"; String goldLabel = "ORO:";
String jokerLabel = themeType == AppThemeType.grimorio ? "GIULLARE:" : "JOLLY:"; String bombLabel = "BOMBA:";
String swapLabel = "SCAMBIO:";
String jokerLabel = "JOLLY:";
String iceLabel = "GHIACCIO:";
String multiplierLabel = "x2:";
String blockLabel = "BUCO NERO:";
if (themeType == AppThemeType.grimorio) {
goldLabel = "CORONA:";
bombLabel = "STREGA:";
jokerLabel = "GIULLARE:";
swapLabel = "TORNADO:";
multiplierLabel = "FULMINE:";
blockLabel = "METEORITE:";
} else if (themeType == AppThemeType.music) {
goldLabel = "DISCO D'ORO:";
bombLabel = "MUTO:";
jokerLabel = "DJ:";
swapLabel = "MIXER:";
iceLabel = "NOTA:";
multiplierLabel = "AVANTI VELOCE:";
blockLabel = "PAUSA:";
} else if (themeType == AppThemeType.arcade) {
goldLabel = "GETTONE:";
bombLabel = "FANTASMA:";
jokerLabel = "GAMEPAD:";
swapLabel = "SHUFFLE:";
blockLabel = "POWER OFF:";
} else if (themeType == AppThemeType.cyberpunk) {
goldLabel = "CHIP:";
bombLabel = "VIRUS:";
jokerLabel = "BOT:";
swapLabel = "NETWORK:";
blockLabel = "FIREWALL:";
} else if (themeType == AppThemeType.wood) {
goldLabel = "GEMMA:";
bombLabel = "FUOCO:";
jokerLabel = "CHIAVE:";
blockLabel = "DIVIETO:";
} else if (themeType == AppThemeType.doodle) {
bombLabel = "VIRUS:";
}
Widget dialogContent = themeType == AppThemeType.doodle Widget dialogContent = themeType == AppThemeType.doodle
? Transform.rotate( ? Transform.rotate(
@ -260,15 +316,15 @@ class TutorialDialog extends StatelessWidget {
const SizedBox(height: 10), 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), 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), 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), TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "$swapLabel Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10), 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), 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), 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), TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "$iceLabel Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10), 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), TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "$multiplierLabel Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10), 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), TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "$blockLabel Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 25), const SizedBox(height: 25),
Center( Center(
@ -317,15 +373,15 @@ class TutorialDialog extends StatelessWidget {
const SizedBox(height: 10), 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), 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), 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), TutorialStep(icon: ThemeIcons.swap(themeType), iconColor: Colors.purpleAccent, text: "$swapLabel Inverte istantaneamente i punteggi dei giocatori.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10), 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), 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), 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), TutorialStep(icon: ThemeIcons.ice(themeType), iconColor: Colors.cyanAccent, text: "$iceLabel Devi cliccarlo due volte per poterlo rompere e chiudere.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10), 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), TutorialStep(icon: ThemeIcons.multiplier(themeType), iconColor: Colors.yellowAccent, text: "$multiplierLabel Non dà punti, ma raddoppia il punteggio della prossima casella che chiudi!", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 10), 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), TutorialStep(icon: ThemeIcons.block(themeType), iconColor: Colors.grey, text: "$blockLabel Questa casella non esiste. Se la chiudi perdi il turno.", themeType: themeType, inkColor: inkColor, theme: theme),
const SizedBox(height: 30), const SizedBox(height: 30),
SizedBox( SizedBox(

View file

@ -6,12 +6,14 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async'; import 'dart:async';
import 'package:app_links/app_links.dart'; import 'package:app_links/app_links.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../logic/game_controller.dart'; import '../../logic/game_controller.dart';
import '../../core/theme_manager.dart'; import '../../core/theme_manager.dart';
import '../../core/app_colors.dart'; import '../../core/app_colors.dart';
@ -29,321 +31,9 @@ import '../../widgets/painters.dart';
import '../../widgets/cyber_border.dart'; import '../../widgets/cyber_border.dart';
import '../../widgets/music_theme_widgets.dart'; import '../../widgets/music_theme_widgets.dart';
import '../../widgets/home_buttons.dart'; import '../../widgets/home_buttons.dart';
import '../../widgets/custom_settings_button.dart';
import 'dialog.dart'; import 'dialog.dart';
// ===========================================================================
// WIDGET LOCALI PER IL SETUP DELLA PARTITA
// ===========================================================================
class _NeonShapeButton extends StatelessWidget {
final IconData icon; final String label; final bool isSelected;
final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap;
final bool isLocked; final bool isSpecial;
const _NeonShapeButton({required this.icon, required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap, this.isLocked = false, this.isSpecial = false});
Color _getDoodleColor() {
switch (label) {
case 'Rombo': return Colors.lightBlue.shade200;
case 'Croce': return Colors.green.shade200;
case 'Buco': return Colors.pink.shade200;
case 'Clessidra': return Colors.purple.shade200;
case 'Caos': return Colors.grey.shade300;
default: return Colors.lightBlue.shade200;
}
}
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = isLocked ? Colors.grey : _getDoodleColor();
Color inkColor = const Color(0xFF111122);
double tilt = (label.length % 2 == 0) ? -0.05 : 0.04;
return Transform.rotate(
angle: tilt,
child: GestureDetector(
onTap: isLocked ? null : onTap,
child: CustomPaint(
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(
mainAxisSize: MainAxisSize.min,
children: [
Icon(isLocked ? Icons.lock : icon, color: inkColor, size: 24),
const SizedBox(height: 2),
Text(isLocked ? "Liv. 10" : label, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 0.5))),
],
),
),
),
),
);
}
Color mainColor = isSpecial && !isLocked ? Colors.purpleAccent : theme.playerBlue;
return GestureDetector(
onTap: isLocked ? null : onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250), curve: Curves.easeOutCubic, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
decoration: BoxDecoration(
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: 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))),
],
),
),
);
}
}
class _NeonSizeButton extends StatelessWidget {
final String label; final bool isSelected; final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap;
const _NeonSizeButton({required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = label == 'MAX' ? Colors.red.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: 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),
decoration: BoxDecoration(
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;
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);
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),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Row(
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: 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)))]),
],
),
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
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,
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: 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)))]),
],
),
),
);
}
}
class _NeonPrivacySwitch extends StatelessWidget {
final bool isPublic;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
const _NeonPrivacySwitch({required this.isPublic, required this.theme, required this.themeType, required this.onTap});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600;
return Transform.rotate(
angle: 0.015,
child: GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0),
decoration: BoxDecoration(
color: isPublic ? doodleColor : Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15), topRight: Radius.circular(8),
bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15),
),
border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5),
boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
Text(isPublic ? 'In Bacheca' : 'Solo Codice', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))),
],
),
],
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isPublic
? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)]
: [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)],
),
border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1),
boxShadow: isPublic
? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
: [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4))],
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))),
Text(isPublic ? 'Tutti ti vedono' : 'Solo con Codice', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold))),
],
),
],
),
),
);
}
}
class _NeonActionButton extends StatelessWidget {
final String label;
final Color color;
final VoidCallback onTap;
final ThemeColors theme;
final AppThemeType themeType;
const _NeonActionButton({required this.label, required this.color, required this.onTap, required this.theme, required this.themeType});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02;
return Transform.rotate(
angle: tilt,
child: GestureDetector(
onTap: onTap,
child: Container(
height: 50,
decoration: BoxDecoration(
color: color,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10), topRight: Radius.circular(20),
bottomLeft: Radius.circular(25), bottomRight: Radius.circular(10),
),
border: Border.all(color: theme.text, width: 3.0),
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)],
),
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: getSharedTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))),
),
),
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [color.withOpacity(0.9), color.withOpacity(0.6)]),
borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.white.withOpacity(0.3), width: 1.5),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.5), offset: const Offset(4, 8), blurRadius: 12),
BoxShadow(color: color.withOpacity(0.3), offset: const Offset(0, 0), blurRadius: 15, spreadRadius: 1),
],
),
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: const [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))),
),
),
),
),
);
}
}
// =========================================================================== // ===========================================================================
// CLASSE PRINCIPALE HOME // CLASSE PRINCIPALE HOME
// =========================================================================== // ===========================================================================
@ -511,10 +201,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
void _showError(String message) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message, style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red)); } void _showError(String message) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message, style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red)); }
// ===========================================================================
// DIALOGHI IN-FILE
// ===========================================================================
void _showWaitingDialog(String code) { void _showWaitingDialog(String code) {
showDialog( showDialog(
context: context, context: context,
@ -644,8 +330,14 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
); );
} }
// --- FINESTRA DI REGISTRAZIONE/LOGIN ---
void _showNameDialog() { void _showNameDialog() {
final TextEditingController nameController = TextEditingController(text: StorageService.instance.playerName); final TextEditingController nameController = TextEditingController(text: StorageService.instance.playerName);
final TextEditingController passController = TextEditingController();
bool isLoadingAuth = false;
bool _obscurePassword = true;
String _errorMessage = "";
showDialog( showDialog(
context: context, barrierDismissible: false, barrierColor: Colors.black.withOpacity(0.8), context: context, barrierDismissible: false, barrierColor: Colors.black.withOpacity(0.8),
builder: (context) { builder: (context) {
@ -653,75 +345,245 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
final theme = themeManager.currentColors; final themeType = themeManager.currentThemeType; final theme = themeManager.currentColors; final themeType = themeManager.currentThemeType;
Color inkColor = const Color(0xFF111122); final loc = AppLocalizations.of(context)!; Color inkColor = const Color(0xFF111122); final loc = AppLocalizations.of(context)!;
Widget dialogContent = themeType == AppThemeType.doodle return StatefulBuilder(
? CustomPaint( builder: (context, setStateDialog) {
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); Future<void> handleAuth(bool isLogin) async {
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent); final name = nameController.text.trim();
final password = passController.text.trim();
setStateDialog(() {
_errorMessage = "";
isLoadingAuth = true;
});
if (name.isEmpty || password.isEmpty) {
setStateDialog(() {
_errorMessage = "Inserisci Nome e Password!";
isLoadingAuth = false;
});
return;
}
if (password.length < 6) {
setStateDialog(() {
_errorMessage = "La password deve avere almeno 6 caratteri!";
isLoadingAuth = false;
});
return;
}
final fakeEmail = "${name.toLowerCase().replaceAll(' ', '')}@tetraq.game";
try {
if (isLogin) {
await FirebaseAuth.instance.signInWithEmailAndPassword(email: fakeEmail, password: password);
final doc = await FirebaseFirestore.instance.collection('leaderboard').doc(FirebaseAuth.instance.currentUser!.uid).get();
if (doc.exists) {
final data = doc.data() as Map<String, dynamic>;
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('totalXP', data['xp'] ?? 0);
await prefs.setInt('wins', data['wins'] ?? 0);
await prefs.setInt('losses', data['losses'] ?? 0);
}
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Bentornato $name! Dati sincronizzati."), backgroundColor: Colors.green));
} else {
await FirebaseAuth.instance.createUserWithEmailAndPassword(email: fakeEmail, password: password);
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Account creato con successo!"), backgroundColor: Colors.green));
}
await StorageService.instance.savePlayerName(name);
if (mounted) Navigator.of(context).pop();
setState(() {});
} on FirebaseAuthException catch (e) {
String msg = "Errore di autenticazione.";
if (e.code == 'email-already-in-use') {
msg = "Nome occupato!\nSe è il tuo account, clicca su ACCEDI.";
} else if (e.code == 'user-not-found' || e.code == 'wrong-password' || e.code == 'invalid-credential') {
msg = "Nome o Password errati!";
}
setStateDialog(() {
_errorMessage = msg;
isLoadingAuth = false;
});
} catch (e) {
setStateDialog(() {
_errorMessage = "Errore imprevisto: $e";
isLoadingAuth = false;
});
}
}
Widget dialogContent = themeType == AppThemeType.doodle
? CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 100),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900, fontSize: 24, letterSpacing: 2.0)), textAlign: TextAlign.center),
const SizedBox(height: 10),
Text('Scegli Nome e Password.\nTi serviranno per recuperare gli XP!', style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.8), fontSize: 13)), textAlign: TextAlign.center),
const SizedBox(height: 15),
TextField(
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 8,
style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 4)),
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: 8),
TextField(
controller: passController, obscureText: _obscurePassword, textAlign: TextAlign.center, maxLength: 20,
style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 20, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(
hintText: "PASSWORD",
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)),
suffixIcon: IconButton(
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off, color: inkColor.withOpacity(0.6)),
onPressed: () { setStateDialog(() { _obscurePassword = !_obscurePassword; }); },
),
),
),
const SizedBox(height: 15),
// MESSAGGIO DI ERRORE
if (_errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(_errorMessage, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.red, fontSize: 14, fontWeight: FontWeight.bold)), textAlign: TextAlign.center),
),
Text("💡 Nota: Non serve una vera email. Usa una password facile da ricordare!", style: getSharedTextStyle(themeType, TextStyle(color: inkColor.withOpacity(0.6), fontSize: 11, height: 1.3)), textAlign: TextAlign.center),
const SizedBox(height: 15),
isLoadingAuth
? CircularProgressIndicator(color: inkColor)
: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => handleAuth(true),
child: CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.blue.shade200, strokeColor: inkColor, seed: 101),
child: Container(height: 45, alignment: Alignment.center, child: Text("ACCEDI", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1.5)))),
),
),
),
const SizedBox(width: 10),
Expanded(
child: GestureDetector(
onTap: () => handleAuth(false),
child: CustomPaint(
painter: DoodleBackgroundPainter(fillColor: Colors.green.shade200, strokeColor: inkColor, seed: 102),
child: Container(height: 45, alignment: Alignment.center, child: Text("REGISTRATI", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 14, 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(
physics: const BouncingScrollPhysics(),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(loc.welcomeTitle, style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 1.5)), textAlign: TextAlign.center),
const SizedBox(height: 10),
Text('Scegli Nome e Password.\nTi serviranno per recuperare gli XP!', style: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.8), fontSize: 13)), textAlign: TextAlign.center),
const SizedBox(height: 15),
TextField(
controller: nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 8,
style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 4)),
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: 10),
TextField(
controller: passController, obscureText: _obscurePassword, textAlign: TextAlign.center, maxLength: 20,
style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 20, fontWeight: FontWeight.bold, letterSpacing: 8)),
decoration: InputDecoration(
hintText: "PASSWORD",
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)),
suffixIcon: IconButton(
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off, color: theme.text.withOpacity(0.6)),
onPressed: () { setStateDialog(() { _obscurePassword = !_obscurePassword; }); },
),
),
),
const SizedBox(height: 15),
// MESSAGGIO DI ERRORE
if (_errorMessage.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(_errorMessage, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.redAccent, fontSize: 14, fontWeight: FontWeight.bold)), textAlign: TextAlign.center),
),
Text("💡 Nota: Non serve una vera email. Usa una password facile da ricordare!", style: getSharedTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 11, height: 1.3)), textAlign: TextAlign.center),
const SizedBox(height: 20),
isLoadingAuth
? CircularProgressIndicator(color: theme.playerBlue)
: Row(
children: [
Expanded(
child: SizedBox(
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.text.withOpacity(0.1), foregroundColor: theme.text, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), side: BorderSide(color: theme.playerBlue, width: 1.5)),
onPressed: () => handleAuth(true),
child: Text("ACCEDI", style: getSharedTextStyle(themeType, const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.0))),
),
),
),
const SizedBox(width: 10),
Expanded(
child: SizedBox(
height: 45,
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
onPressed: () => handleAuth(false),
child: Text("REGISTRATI", style: getSharedTextStyle(themeType, const TextStyle(fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.0))),
),
),
),
],
),
],
),
),
),
);
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) dialogContent = AnimatedCyberBorder(child: dialogContent);
return Dialog(backgroundColor: Colors.transparent, insetPadding: const EdgeInsets.all(20), child: dialogContent);
},
);
}, },
); );
} }
@ -765,11 +627,11 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
Wrap( Wrap(
spacing: 12, runSpacing: 12, alignment: WrapAlignment.center, spacing: 12, runSpacing: 12, alignment: WrapAlignment.center,
children: [ children: [
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)), NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)), NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)), NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)), NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), 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),
@ -778,17 +640,17 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)), NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)), NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)), NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), 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: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), const SizedBox(height: 10), 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), NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)), const SizedBox(height: 35),
Transform.rotate( Transform.rotate(
angle: -0.02, angle: -0.02,
@ -829,11 +691,11 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
Wrap( Wrap(
spacing: 10, runSpacing: 10, alignment: WrapAlignment.center, spacing: 10, runSpacing: 10, alignment: WrapAlignment.center,
children: [ children: [
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)), NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)), NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)), NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)), NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), 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),
@ -842,17 +704,17 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)), NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)), NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)), NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), 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: getSharedTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 10), 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), NeonTimeSwitch(isTimeMode: localTimeMode, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localTimeMode = !localTimeMode)), const SizedBox(height: 30),
SizedBox( SizedBox(
width: double.infinity, height: 60, width: double.infinity, height: 60,
@ -905,6 +767,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg'; if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg'; if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
if (themeType == AppThemeType.arcade) bgImage = 'assets/images/arcade.jpg';
if (themeType == AppThemeType.grimorio) bgImage = 'assets/images/grimorio.jpg'; // Aggiunto Grimorio
int wins = StorageService.instance.wins; int wins = StorageService.instance.wins;
int losses = StorageService.instance.losses; int losses = StorageService.instance.losses;
@ -933,8 +797,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// BLOCCO SINISTRO: AVATAR, NOME E AUDIO // BLOCCO SINISTRO: AVATAR E NOME
Expanded( // Permette di occupare lo spazio necessario senza spingere la destra Expanded(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -979,7 +843,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
), ),
), ),
// BLOCCO DESTRO: STATISTICHE (SOPRA) E AUDIO (SOTTO) // BLOCCO DESTRO: STATISTICHE E AUDIO
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
@ -1175,7 +1039,9 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
); );
return Scaffold( return Scaffold(
backgroundColor: Colors.transparent, backgroundColor: bgImage != null ? Colors.transparent : theme.background,
extendBodyBehindAppBar: true,
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0, iconTheme: IconThemeData(color: theme.text)),
body: Stack( body: Stack(
children: [ children: [
// 1. Sfondo base a tinta unita // 1. Sfondo base a tinta unita
@ -1199,8 +1065,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
), ),
), ),
// 4. Patina scura (Cyberpunk e Music) // 4. Patina scura (Cyberpunk, Music, Arcade e Grimorio)
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music)) if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio))
Positioned.fill( Positioned.fill(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(

View file

@ -1,64 +1,74 @@
// ===========================================================================
// FILE: lib/widgets/custom_settings_button.dart
// ===========================================================================
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../theme/app_colors.dart'; import '../core/app_colors.dart';
import 'painters.dart'; // Importiamo i painter per i doodle e il font
// Widget per i pulsanti di selezione della forma dell'arena
class NeonShapeButton extends StatelessWidget { class NeonShapeButton extends StatelessWidget {
final IconData icon; final IconData icon; final String label; final bool isSelected;
final String label; final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap;
final bool isSelected; final bool isLocked; final bool isSpecial;
final VoidCallback onTap;
final ShapeBorder shape; // La forma geometrica del pulsante
const NeonShapeButton({ const NeonShapeButton({super.key, required this.icon, required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap, this.isLocked = false, this.isSpecial = false});
super.key,
required this.icon, Color _getDoodleColor() {
required this.label, switch (label) {
required this.isSelected, case 'Rombo': return Colors.lightBlue.shade200;
required this.onTap, case 'Croce': return Colors.green.shade200;
this.shape = const RoundedRectangleBorder( // Forma di default case 'Buco': return Colors.pink.shade200;
borderRadius: BorderRadius.all(Radius.circular(12.0))), case 'Clessidra': return Colors.purple.shade200;
}); case 'Caos': return Colors.grey.shade300;
default: return Colors.lightBlue.shade200;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( if (themeType == AppThemeType.doodle) {
onTap: onTap, Color doodleColor = isLocked ? Colors.grey : _getDoodleColor();
child: AnimatedContainer( Color inkColor = const Color(0xFF111122);
duration: const Duration(milliseconds: 300), double tilt = (label.length % 2 == 0) ? -0.05 : 0.04;
curve: Curves.easeInOut,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), return Transform.rotate(
decoration: ShapeDecoration( angle: tilt,
shape: shape, child: GestureDetector(
color: isSelected onTap: isLocked ? null : onTap,
? AppColors.neonGreen.withOpacity(0.2) // Sfondo luminoso se selezionato child: CustomPaint(
: AppColors.surface.withOpacity(0.5), // Sfondo più scuro se non selezionato painter: DoodleBackgroundPainter(fillColor: isSelected ? doodleColor : Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: label.length * 3),
shadows: isSelected child: Container(
? [ // Bagliore intenso se selezionato padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
BoxShadow( child: Column(
color: AppColors.neonGreen.withOpacity(0.6), mainAxisSize: MainAxisSize.min,
blurRadius: 12.0, children: [
spreadRadius: 2.0, Icon(isLocked ? Icons.lock : icon, color: inkColor, size: 24),
const SizedBox(height: 2),
Text(isLocked ? "Liv. 10" : label, style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 0.5))),
],
),
), ),
] ),
: [], ),
);
}
Color mainColor = isSpecial && !isLocked ? Colors.purpleAccent : theme.playerBlue;
return GestureDetector(
onTap: isLocked ? null : onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 250), curve: Curves.easeOutCubic, padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
decoration: BoxDecoration(
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( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( Icon(isLocked ? Icons.lock : icon, color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), size: 24),
icon, const SizedBox(height: 6),
color: isSelected ? AppColors.neonGreen : AppColors.textSecondary, 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))),
size: 28,
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
color: isSelected ? AppColors.textPrimary : AppColors.textSecondary,
fontSize: 12,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
], ],
), ),
), ),
@ -66,114 +76,169 @@ class NeonShapeButton extends StatelessWidget {
} }
} }
// Widget per i pulsanti di selezione della taglia dell'arena
class NeonSizeButton extends StatelessWidget { class NeonSizeButton extends StatelessWidget {
final String label; final String label; final bool isSelected; final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap;
final bool isSelected; const NeonSizeButton({super.key, required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap});
final VoidCallback onTap;
const NeonSizeButton({
super.key,
required this.label,
required this.isSelected,
required this.onTap,
});
@override @override
Widget build(BuildContext context) { 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;
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: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 18, fontWeight: FontWeight.w900)))))),
),
);
}
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 250), curve: Curves.easeOutCubic, width: 50, height: 50, transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
curve: Curves.easeInOut,
width: 50,
height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, // Forma circolare shape: BoxShape.circle, border: Border.all(color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1), width: isSelected ? 2 : 1),
color: isSelected 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)]),
? AppColors.neonBlue.withOpacity(0.2) 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))],
: AppColors.surface.withOpacity(0.5),
border: Border.all(
color: isSelected ? AppColors.neonBlue : AppColors.surfaceLight,
width: 2.0,
),
shadows: isSelected
? [
BoxShadow(
color: AppColors.neonBlue.withOpacity(0.6),
blurRadius: 10.0,
spreadRadius: 1.5,
),
]
: [],
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isSelected ? AppColors.textPrimary : AppColors.textSecondary,
fontSize: 16,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
), ),
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)))),
), ),
); );
} }
} }
// Widget per l'interruttore della modalità tempo (Clessidra)
class NeonTimeSwitch extends StatelessWidget { class NeonTimeSwitch extends StatelessWidget {
final bool isTimeMode; final bool isTimeMode; final ThemeColors theme; final AppThemeType themeType; final VoidCallback onTap;
final VoidCallback onTap; const NeonTimeSwitch({super.key, required this.isTimeMode, required this.theme, required this.themeType, required this.onTap});
const NeonTimeSwitch({
super.key,
required this.isTimeMode,
required this.onTap,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
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),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Row(
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: 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)))]),
],
),
),
),
),
);
}
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
curve: Curves.easeInOut,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0), // Forma arrotondata per lo switch borderRadius: BorderRadius.circular(20), border: Border.all(color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1), width: isTimeMode ? 2 : 1),
color: isTimeMode 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)]),
? AppColors.neonGreen.withOpacity(0.2) 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))],
: AppColors.surface.withOpacity(0.5),
border: Border.all(
color: isTimeMode ? AppColors.neonGreen : AppColors.surfaceLight,
width: 2.0,
),
shadows: isTimeMode
? [
BoxShadow(
color: AppColors.neonGreen.withOpacity(0.6),
blurRadius: 12.0,
spreadRadius: 2.0,
),
]
: [],
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 28), const SizedBox(width: 12),
Icons.hourglass_empty, // Icona clessidra 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)))]),
color: isTimeMode ? AppColors.neonGreen : AppColors.textSecondary, ],
), ),
const SizedBox(width: 8), ),
Text( );
isTimeMode ? 'A TEMPO' : 'SENZA TEMPO', }
style: TextStyle( }
color: isTimeMode ? AppColors.textPrimary : AppColors.textSecondary,
fontWeight: isTimeMode ? FontWeight.bold : FontWeight.normal, class NeonPrivacySwitch extends StatelessWidget {
final bool isPublic;
final ThemeColors theme;
final AppThemeType themeType;
final VoidCallback onTap;
const NeonPrivacySwitch({super.key, required this.isPublic, required this.theme, required this.themeType, required this.onTap});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600;
return Transform.rotate(
angle: 0.015,
child: GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0),
decoration: BoxDecoration(
color: isPublic ? doodleColor : Colors.white,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15), topRight: Radius.circular(8),
bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15),
), ),
border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5),
boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
Text(isPublic ? 'In Bacheca' : 'Solo Codice', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))),
],
),
],
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isPublic
? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)]
: [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)],
),
border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1),
boxShadow: isPublic
? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
: [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4))],
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))),
Text(isPublic ? 'Tutti ti vedono' : 'Solo con Codice', style: getSharedTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold))),
],
), ),
], ],
), ),
@ -181,3 +246,72 @@ class NeonTimeSwitch extends StatelessWidget {
); );
} }
} }
class NeonActionButton extends StatelessWidget {
final String label;
final Color color;
final VoidCallback onTap;
final ThemeColors theme;
final AppThemeType themeType;
const NeonActionButton({super.key, required this.label, required this.color, required this.onTap, required this.theme, required this.themeType});
@override
Widget build(BuildContext context) {
if (themeType == AppThemeType.doodle) {
double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02;
return Transform.rotate(
angle: tilt,
child: GestureDetector(
onTap: onTap,
child: Container(
height: 50,
decoration: BoxDecoration(
color: color,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10), topRight: Radius.circular(20),
bottomLeft: Radius.circular(25), bottomRight: Radius.circular(10),
),
border: Border.all(color: theme.text, width: 3.0),
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)],
),
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: getSharedTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))),
),
),
),
),
),
);
}
return GestureDetector(
onTap: onTap,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [color.withOpacity(0.9), color.withOpacity(0.6)]),
borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.white.withOpacity(0.3), width: 1.5),
boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.5), offset: const Offset(4, 8), blurRadius: 12),
BoxShadow(color: color.withOpacity(0.3), offset: const Offset(0, 0), blurRadius: 15, spreadRadius: 1),
],
),
child: Center(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Text(label, style: getSharedTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))),
),
),
),
),
);
}
}