2026-02-27 23:35:54 +01:00
// ===========================================================================
// FILE: lib/ui/home/home_screen.dart
// ===========================================================================
import ' dart:ui ' ;
import ' package:flutter/material.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:flutter/services.dart ' ;
2026-03-01 20:59:06 +01:00
import ' package:flutter/foundation.dart ' ;
2026-02-27 23:35:54 +01:00
import ' dart:math ' as math ;
import ' package:google_fonts/google_fonts.dart ' ;
2026-03-01 20:59:06 +01:00
import ' package:cloud_firestore/cloud_firestore.dart ' ;
import ' package:shared_preferences/shared_preferences.dart ' ;
import ' ../../logic/game_controller.dart ' ;
2026-02-27 23:35:54 +01:00
import ' ../../core/theme_manager.dart ' ;
import ' ../../core/app_colors.dart ' ;
import ' ../game/game_screen.dart ' ;
import ' ../settings/settings_screen.dart ' ;
import ' ../../services/storage_service.dart ' ;
import ' ../multiplayer/lobby_screen.dart ' ;
import ' history_screen.dart ' ;
2026-03-04 18:00:01 +01:00
import ' package:firebase_auth/firebase_auth.dart ' ;
2026-02-27 23:35:54 +01:00
TextStyle _getTextStyle ( AppThemeType themeType , TextStyle baseStyle ) {
if ( themeType = = AppThemeType . doodle ) {
return GoogleFonts . permanentMarker ( textStyle: baseStyle ) ;
2026-03-01 20:59:06 +01:00
} 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 ) ) ;
2026-02-27 23:35:54 +01:00
}
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 ( ) ;
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 ;
}
}
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: _getTextStyle ( 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 ) ,
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 ) ) ,
] ,
) ,
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 ) ) ) ,
] ,
) ,
) ,
) ;
}
}
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: _getTextStyle ( 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 ,
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 ) ) ) ,
) ,
) ,
) ;
}
}
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: _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 ) ) ) ,
] ,
) ,
] ,
) ,
) ,
) ,
) ,
) ;
}
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 ) ,
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 ) ) ,
] ,
) ,
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: _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 ) ) ) ,
] ,
) ,
] ,
) ,
) ,
) ;
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen ( { super . key } ) ;
@ override
State < HomeScreen > createState ( ) = > _HomeScreenState ( ) ;
}
class _HomeScreenState extends State < HomeScreen > with WidgetsBindingObserver {
2026-03-01 20:59:06 +01:00
int _debugTapCount = 0 ;
2026-02-27 23:35:54 +01:00
@ override
void initState ( ) {
super . initState ( ) ;
WidgetsBinding . instance . addObserver ( this ) ;
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
_checkPlayerName ( ) ;
} ) ;
_checkClipboardForInvite ( ) ;
}
@ override
void dispose ( ) {
WidgetsBinding . instance . removeObserver ( this ) ;
super . dispose ( ) ;
}
@ override
void didChangeAppLifecycleState ( AppLifecycleState state ) {
if ( state = = AppLifecycleState . resumed ) {
_checkClipboardForInvite ( ) ;
}
}
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 < ThemeManager > ( ) ;
final theme = themeManager . currentColors ;
final themeType = themeManager . currentThemeType ;
Color inkColor = const Color ( 0xFF111122 ) ;
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 ( ' BENVENUTO! ' , 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: ' NOME ' , 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 ( ' SALVA E GIOCA ' , 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 ( ' BENVENUTO IN TETRAQ! ' , 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: ' NOME ' , 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 ( ' SALVA E GIOCA ' , style: _getTextStyle ( themeType , const TextStyle ( fontSize: 18 , fontWeight: FontWeight . bold , letterSpacing: 1.5 ) ) ) ,
) ,
) ,
] ,
) ,
) ,
) ,
) ;
if ( themeType = = AppThemeType . cyberpunk ) dialogContent = _AnimatedCyberBorder ( child: dialogContent ) ;
return Dialog ( backgroundColor: Colors . transparent , insetPadding: const EdgeInsets . all ( 20 ) , child: dialogContent ) ;
} ,
) ;
}
Future < void > _checkClipboardForInvite ( ) async {
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 ) ;
}
}
}
} catch ( e ) {
debugPrint ( " Errore lettura appunti: $ e " ) ;
}
}
void _promptJoinRoom ( String roomCode ) {
showDialog (
context: context ,
builder: ( context ) {
final theme = context . watch < ThemeManager > ( ) . currentColors ;
final themeType = context . read < ThemeManager > ( ) . currentThemeType ;
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 ) ) ) ,
actions: [
TextButton ( onPressed: ( ) = > Navigator . pop ( context ) , child: Text ( " No " , style: _getTextStyle ( 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 ,
) ,
onPressed: ( ) {
Navigator . of ( context ) . pop ( ) ;
Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > LobbyScreen ( initialRoomCode: roomCode ) ) ) ;
} ,
child: Text ( " Unisciti " , style: _getTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : Colors . white , fontWeight: FontWeight . bold ) ) ) ,
) ,
] ,
) ;
}
) ;
}
void _showMatchSetupDialog ( bool isVsCPU ) {
int localRadius = 4 ;
ArenaShape localShape = ArenaShape . classic ;
bool localTimeMode = true ;
2026-03-01 20:59:06 +01:00
bool isChaosUnlocked = StorageService . instance . playerLevel > = 10 ;
2026-02-27 23:35:54 +01:00
showDialog (
context: context ,
barrierColor: Colors . black . withOpacity ( 0.8 ) ,
builder: ( ctx ) {
final themeManager = ctx . watch < ThemeManager > ( ) ;
final theme = themeManager . currentColors ;
final themeType = themeManager . currentThemeType ;
Color inkColor = const Color ( 0xFF111122 ) ;
return StatefulBuilder (
builder: ( context , setStateDialog ) {
Widget dialogContent = themeType = = AppThemeType . doodle
? Transform . rotate (
angle: 0.015 ,
child: CustomPaint (
painter: _DoodleBackgroundPainter ( fillColor: Colors . white . withOpacity ( 0.95 ) , strokeColor: inkColor , seed: 200 ) ,
child: SingleChildScrollView (
physics: const BouncingScrollPhysics ( ) ,
child: Padding (
padding: const EdgeInsets . all ( 25.0 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( isVsCPU ? " SFIDA IA " : " MODALITÀ LOCALE " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 26 , fontWeight: FontWeight . w900 , color: inkColor , letterSpacing: 2 ) ) ) ,
const SizedBox ( height: 25 ) ,
Text ( " FORMA ARENA " , style: _getTextStyle ( 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: [
_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 . 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 . 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 ) ,
Text ( " GRANDEZZA " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 14 , fontWeight: FontWeight . w900 , color: inkColor . withOpacity ( 0.6 ) , letterSpacing: 1.5 ) ) ) ,
const SizedBox ( height: 15 ) ,
Row (
mainAxisAlignment: MainAxisAlignment . spaceEvenly ,
children: [
_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: ' 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 ) ) ,
] ,
) ,
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 ) ,
Transform . rotate (
angle: - 0.02 ,
child: GestureDetector (
onTap: ( ) {
Navigator . pop ( ctx ) ;
context . read < GameController > ( ) . 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 ( " AVVIA PARTITA " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 22 , fontWeight: FontWeight . w900 , letterSpacing: 3.0 , color: inkColor ) ) ) ,
) ,
) ,
) ,
)
] ,
) ,
) ,
) ,
) ,
)
: 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 ) ,
2026-03-01 20:59:06 +01:00
border: themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . arcade ? null : Border . all ( color: Colors . white . withOpacity ( 0.15 ) , width: 1.5 ) ,
boxShadow: themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . arcade ? [ ] : [ BoxShadow ( color: Colors . black . withOpacity ( 0.5 ) , blurRadius: 20 , offset: const Offset ( 4 , 10 ) ) ] ,
2026-02-27 23:35:54 +01:00
) ,
child: SingleChildScrollView (
physics: const BouncingScrollPhysics ( ) ,
child: Padding (
padding: const EdgeInsets . all ( 20.0 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( isVsCPU ? " SFIDA IA " : " MODALITÀ LOCALE " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 24 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 2 ) ) ) ,
const SizedBox ( height: 20 ) ,
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 ) ,
Wrap (
spacing: 10 , runSpacing: 10 , alignment: WrapAlignment . center ,
children: [
_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 . 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 . 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 ) ,
Text ( " GRANDEZZA " , style: _getTextStyle ( 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: [
_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: ' 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 ) ) ,
] ,
) ,
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 ) ,
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 < GameController > ( ) . startNewGame ( localRadius , vsCPU: isVsCPU , shape: localShape , timeMode: localTimeMode ) ;
Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const GameScreen ( ) ) ) ;
} ,
child: const Text ( " AVVIA PARTITA " , style: TextStyle ( fontSize: 18 , fontWeight: FontWeight . w900 , letterSpacing: 2 ) ) ,
) ,
)
] ,
) ,
) ,
) ,
) ;
if ( themeType = = AppThemeType . cyberpunk ) {
dialogContent = _AnimatedCyberBorder ( child: dialogContent ) ;
}
return Dialog ( backgroundColor: Colors . transparent , insetPadding: const EdgeInsets . symmetric ( horizontal: 15 , vertical: 20 ) , child: dialogContent ) ;
} ,
) ;
}
) ;
}
2026-03-01 20:59:06 +01:00
// --- NUOVA FUNZIONE: MOSTRA LE SFIDE GIORNALIERE ---
Future < void > _showDailyQuestsDialog ( ) async {
final prefs = await SharedPreferences . getInstance ( ) ;
showDialog (
context: context ,
barrierColor: Colors . black . withOpacity ( 0.8 ) ,
builder: ( ctx ) {
final themeManager = ctx . watch < ThemeManager > ( ) ;
final theme = themeManager . currentColors ;
final themeType = themeManager . currentThemeType ;
Color inkColor = const Color ( 0xFF111122 ) ;
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 ( " SFIDE GIORNALIERE " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 22 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 1.5 ) ) ) ,
const SizedBox ( height: 25 ) ,
// Generiamo dinamicamente le 3 missioni salvate in memoria
. . . 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 ) ) ,
) ,
)
] ,
) ,
) ,
) ;
}
) ;
}
// --- NUOVA FUNZIONE: MOSTRA LA CLASSIFICA GLOBALE ---
void _showLeaderboardDialog ( ) {
showDialog (
context: context ,
barrierColor: Colors . black . withOpacity ( 0.8 ) ,
builder: ( ctx ) {
final themeManager = ctx . watch < ThemeManager > ( ) ;
final theme = themeManager . currentColors ;
final themeType = themeManager . currentThemeType ;
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 ( " CLASSIFICA MONDIALE " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 20 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 1.5 ) ) ) ,
const SizedBox ( height: 20 ) ,
// Lista giocatori pescata da Firebase!
SizedBox (
height: 350 ,
child: StreamBuilder < QuerySnapshot > (
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 ( ) ,
itemBuilder: ( context , index ) {
var data = docs [ index ] . data ( ) as Map < String , dynamic > ;
2026-03-04 18:00:01 +01:00
// Ora controlliamo se l'ID del documento su Firebase è uguale al nostro ID segreto!
String ? myUid = FirebaseAuth . instance . currentUser ? . uid ;
bool isMe = docs [ index ] . id = = myUid ;
2026-03-01 20:59:06 +01:00
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 ) content = _AnimatedCyberBorder ( child: content ) ;
return Dialog ( backgroundColor: Colors . transparent , insetPadding: const EdgeInsets . all ( 20 ) , child: content ) ;
}
) ;
}
2026-02-27 23:35:54 +01:00
void _showTutorialDialog ( ) {
showDialog (
context: context ,
barrierColor: Colors . black . withOpacity ( 0.8 ) ,
builder: ( ctx ) {
final themeManager = ctx . watch < ThemeManager > ( ) ;
final theme = themeManager . currentColors ;
final themeType = themeManager . currentThemeType ;
Color inkColor = const Color ( 0xFF111122 ) ;
2026-03-01 20:59:06 +01:00
String goldLabel = themeType = = AppThemeType . grimorio ? " CORONA: " : " ORO: " ;
String bombLabel = themeType = = AppThemeType . grimorio ? " STREGA: " : " BOMBA: " ;
String jokerLabel = themeType = = AppThemeType . grimorio ? " GIULLARE: " : " JOLLY: " ;
2026-02-27 23:35:54 +01:00
Widget dialogContent = themeType = = AppThemeType . doodle
? Transform . rotate (
angle: - 0.01 ,
child: CustomPaint (
2026-03-01 20:59:06 +01:00
painter: _DoodleBackgroundPainter ( fillColor: Colors . yellow . shade50 , strokeColor: inkColor , seed: 400 ) ,
2026-02-27 23:35:54 +01:00
child: Padding (
padding: const EdgeInsets . all ( 25.0 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
2026-03-01 20:59:06 +01:00
crossAxisAlignment: CrossAxisAlignment . start ,
2026-02-27 23:35:54 +01:00
children: [
2026-03-01 20:59:06 +01:00
Center ( child: Text ( " COME GIOCARE " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 28 , fontWeight: FontWeight . w900 , color: inkColor , letterSpacing: 2 ) ) ) ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 20 ) ,
2026-03-01 20:59:06 +01:00
_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 ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 15 ) ,
2026-03-01 20:59:06 +01:00
_TutorialStep ( icon: Icons . lens_blur , text: " Ma presta attenzione! ogni quadrato nasconde un insidia o un regalo! " , themeType: themeType , inkColor: inkColor , theme: theme ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 15 ) ,
2026-03-01 20:59:06 +01:00
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 ) ) ) ,
) ,
2026-02-27 23:35:54 +01:00
) ,
) ,
)
] ,
) ,
) ,
) ,
)
: 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 ) ,
2026-03-01 20:59:06 +01:00
border: themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . arcade ? null : Border . all ( color: Colors . white . withOpacity ( 0.15 ) , width: 1.5 ) ,
boxShadow: themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . arcade ? [ ] : [ BoxShadow ( color: Colors . black . withOpacity ( 0.5 ) , blurRadius: 20 , offset: const Offset ( 4 , 10 ) ) ] ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-01 20:59:06 +01:00
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 ) ) ,
) ,
)
] ,
) ,
2026-02-27 23:35:54 +01:00
) ,
) ;
if ( themeType = = AppThemeType . cyberpunk ) dialogContent = _AnimatedCyberBorder ( child: dialogContent ) ;
return Dialog ( backgroundColor: Colors . transparent , insetPadding: const EdgeInsets . symmetric ( horizontal: 20 , vertical: 20 ) , child: dialogContent ) ;
} ,
) ;
}
Widget _buildCyberCard ( Widget card , AppThemeType themeType ) {
if ( themeType = = AppThemeType . cyberpunk ) {
return _AnimatedCyberBorder ( child: card ) ;
}
return card ;
}
@ override
Widget build ( BuildContext context ) {
SystemChrome . setEnabledSystemUIMode ( SystemUiMode . immersiveSticky ) ;
SystemChrome . setPreferredOrientations ( [ DeviceOrientation . portraitUp , DeviceOrientation . portraitDown ] ) ;
final themeManager = context . watch < ThemeManager > ( ) ;
final themeType = themeManager . currentThemeType ;
final theme = themeManager . currentColors ;
Color inkColor = const Color ( 0xFF111122 ) ;
String ? bgImage ;
if ( themeType = = AppThemeType . wood ) bgImage = ' assets/images/wood_bg.jpg ' ;
if ( themeType = = AppThemeType . doodle ) bgImage = ' assets/images/doodle_bg.jpg ' ;
if ( themeType = = AppThemeType . cyberpunk ) bgImage = ' assets/images/cyber_bg.jpg ' ;
int wins = StorageService . instance . wins ;
int losses = StorageService . instance . losses ;
String playerName = StorageService . instance . playerName ;
if ( playerName . isEmpty ) playerName = " GUEST " ;
2026-03-01 20:59:06 +01:00
int level = StorageService . instance . playerLevel ;
int currentXP = StorageService . instance . totalXP ;
double xpProgress = ( currentXP % 100 ) / 100.0 ;
2026-02-27 23:35:54 +01:00
Widget uiContent = SafeArea (
2026-03-01 20:59:06 +01:00
child: LayoutBuilder (
builder: ( context , constraints ) {
return SingleChildScrollView (
physics: const BouncingScrollPhysics ( ) ,
child: ConstrainedBox (
constraints: BoxConstraints (
minHeight: constraints . maxHeight ,
) ,
child: IntrinsicHeight (
child: Padding (
padding: const EdgeInsets . symmetric ( horizontal: 20.0 , vertical: 20.0 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
2026-02-27 23:35:54 +01:00
children: [
2026-03-01 20:59:06 +01:00
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
GestureDetector (
onTap: _showNameDialog ,
child: Row (
children: [
themeType = = AppThemeType . doodle
? CustomPaint (
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 (
width: 50 , height: 50 ,
child: Stack (
fit: StackFit . expand ,
children: [
CircularProgressIndicator ( value: xpProgress , color: theme . playerBlue , strokeWidth: 3 , backgroundColor: theme . gridLine . withOpacity ( 0.2 ) ) ,
Padding (
padding: const EdgeInsets . all ( 4.0 ) ,
child: Container (
decoration: BoxDecoration ( shape: BoxShape . circle , boxShadow: [ BoxShadow ( color: theme . playerBlue . withOpacity ( 0.3 ) , blurRadius: 10 , offset: const Offset ( 0 , 4 ) ) ] ) ,
child: CircleAvatar ( backgroundColor: theme . playerBlue . withOpacity ( 0.2 ) , child: Icon ( Icons . person , color: theme . playerBlue , size: 26 ) ) ,
) ,
) ,
] ,
) ,
) ,
const SizedBox ( width: 12 ) ,
Column (
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 ) ) ) ,
] ,
) ,
] ,
) ,
) ,
GestureDetector (
onTap: ( ) = > Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const HistoryScreen ( ) ) ) ,
child: themeType = = AppThemeType . doodle
? Transform . rotate (
angle: 0.04 ,
child: CustomPaint (
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 ) ,
Icon ( Icons . sentiment_very_dissatisfied , color: inkColor , size: 20 ) , const SizedBox ( width: 6 ) ,
Text ( " $ losses " , style: _getTextStyle ( themeType , TextStyle ( color: inkColor , fontWeight: FontWeight . w900 ) ) ) ,
] ,
) ,
) ,
) ,
)
: Container (
padding: const EdgeInsets . symmetric ( horizontal: 14 , vertical: 10 ) ,
decoration: BoxDecoration (
gradient: LinearGradient ( begin: Alignment . topLeft , end: Alignment . bottomRight , colors: [ theme . text . withOpacity ( 0.15 ) , theme . text . withOpacity ( 0.02 ) ] ) ,
borderRadius: BorderRadius . circular ( 20 ) ,
border: Border . all ( color: Colors . white . withOpacity ( 0.1 ) , width: 1.5 ) ,
boxShadow: [ BoxShadow ( color: Colors . black . withOpacity ( 0.3 ) , offset: const Offset ( 2 , 4 ) , blurRadius: 8 ) , BoxShadow ( color: Colors . white . withOpacity ( 0.05 ) , offset: const Offset ( - 1 , - 1 ) , blurRadius: 2 ) ] ,
) ,
child: Row (
children: [
Icon ( Icons . emoji_events , color: Colors . amber . shade600 , size: 20 ) , const SizedBox ( width: 6 ) ,
Text ( " $ wins " , style: _getTextStyle ( themeType , const TextStyle ( color: Colors . white , fontWeight: FontWeight . w900 ) ) ) , const SizedBox ( width: 12 ) ,
Icon ( Icons . sentiment_very_dissatisfied , color: theme . playerRed . withOpacity ( 0.8 ) , size: 20 ) , const SizedBox ( width: 6 ) ,
Text ( " $ losses " , style: _getTextStyle ( themeType , const TextStyle ( color: Colors . white , fontWeight: FontWeight . w900 ) ) ) ,
] ,
) ,
) ,
)
] ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-01 20:59:06 +01:00
const Spacer ( ) ,
Center (
child: Transform . rotate (
angle: themeType = = AppThemeType . doodle ? - 0.04 : 0 ,
// --- IL TRUCCO DELLO SVILUPPATORE PROTETTO ---
child: GestureDetector (
onTap: ( ) {
if ( kReleaseMode ) return ;
_debugTapCount + + ;
if ( _debugTapCount > = 5 ) {
_debugTapCount = 0 ;
StorageService . instance . addXP ( 2000 ) ;
setState ( ( ) { } ) ;
ScaffoldMessenger . of ( context ) . showSnackBar (
SnackBar (
content: Text ( " 🛠 DEBUG MODE: +20 Livelli! Tutto sbloccato. " , style: _getTextStyle ( themeType , const TextStyle ( color: Colors . white , fontWeight: FontWeight . bold ) ) ) ,
backgroundColor: Colors . purpleAccent ,
behavior: SnackBarBehavior . floating ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 15 ) ) ,
)
) ;
}
} ,
child: FittedBox (
fit: BoxFit . scaleDown ,
child: Text (
" TETRAQ " ,
style: _getTextStyle ( themeType , TextStyle (
fontSize: 65 ,
fontWeight: FontWeight . w900 ,
color: themeType = = AppThemeType . doodle ? inkColor : theme . text ,
letterSpacing: 10 ,
shadows: themeType = = AppThemeType . doodle | | themeType = = AppThemeType . arcade ? [ ] : [
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 ) ,
]
) )
) ,
) ,
) ,
2026-02-27 23:35:54 +01:00
) ,
) ,
2026-03-01 20:59:06 +01:00
const Spacer ( ) ,
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
// --- NUOVA LISTA BOTTONI CON CLASSIFICHE E SFIDE ---
Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
_buildCyberCard ( _FeatureCard ( title: " ONLINE " , subtitle: " Sfida il mondo " , 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: " VS CPU " , subtitle: " Allenati con l'IA " , icon: Icons . smart_toy , color: Colors . purple . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > _showMatchSetupDialog ( true ) ) , themeType ) ,
const SizedBox ( height: 12 ) ,
_buildCyberCard ( _FeatureCard ( title: " LOCALE " , subtitle: " Stesso schermo " , icon: Icons . people_alt , color: Colors . red . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > _showMatchSetupDialog ( false ) ) , themeType ) ,
const SizedBox ( height: 12 ) ,
// NUOVI BOTTONI PER LA VERSIONE 2.0
Row (
children: [
Expanded ( child: _buildCyberCard ( _FeatureCard ( title: " CLASSIFICA " , subtitle: " Top 50 Globale " , icon: Icons . leaderboard , color: Colors . amber . shade200 , theme: theme , themeType: themeType , onTap: _showLeaderboardDialog , compact: true ) , themeType ) ) ,
const SizedBox ( width: 12 ) ,
Expanded ( child: _buildCyberCard ( _FeatureCard ( title: " SFIDE " , subtitle: " Missioni " , icon: Icons . assignment_turned_in , color: Colors . green . shade200 , theme: theme , themeType: themeType , onTap: _showDailyQuestsDialog , compact: true ) , themeType ) ) ,
] ,
) ,
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
const SizedBox ( height: 12 ) ,
Row (
children: [
Expanded ( child: _buildCyberCard ( _FeatureCard ( title: " TEMI " , 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: " TUTORIAL " , subtitle: " Come giocare " , icon: Icons . school , color: Colors . indigo . shade200 , theme: theme , themeType: themeType , onTap: _showTutorialDialog , compact: true ) , themeType ) ) ,
] ,
) ,
] ,
) ,
const SizedBox ( height: 10 ) ,
] ,
) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-01 20:59:06 +01:00
) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-01 20:59:06 +01:00
) ;
} ,
2026-02-27 23:35:54 +01:00
) ,
) ;
return Scaffold (
backgroundColor: bgImage ! = null ? Colors . transparent : theme . background ,
body: Stack (
children: [
Container ( color: theme . background ) ,
if ( bgImage ! = null )
Positioned . fill (
child: Image . asset ( bgImage , fit: BoxFit . cover , alignment: Alignment . center ) ,
) ,
if ( bgImage ! = null )
Positioned . fill (
child: themeType = = AppThemeType . cyberpunk
? Container (
decoration: BoxDecoration (
gradient: LinearGradient (
begin: Alignment . topCenter , end: Alignment . bottomCenter ,
colors: [ Colors . black . withOpacity ( 0.2 ) , Colors . black . withOpacity ( 0.7 ) ]
)
) ,
)
: const SizedBox ( ) ,
) ,
Positioned . fill ( child: uiContent ) ,
] ,
) ,
) ;
}
}
class _TutorialStep extends StatelessWidget {
final IconData icon ;
2026-03-01 20:59:06 +01:00
final Color ? iconColor ;
2026-02-27 23:35:54 +01:00
final String text ;
final AppThemeType themeType ;
final Color inkColor ;
final ThemeColors theme ;
2026-03-01 20:59:06 +01:00
const _TutorialStep ( { required this . icon , this . iconColor , required this . text , required this . themeType , required this . inkColor , required this . theme } ) ;
2026-02-27 23:35:54 +01:00
@ override
Widget build ( BuildContext context ) {
return Row (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
2026-03-01 20:59:06 +01:00
Icon ( icon , color: iconColor ? ? ( themeType = = AppThemeType . doodle ? inkColor : theme . playerBlue ) , size: 28 ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( width: 15 ) ,
Expanded (
2026-03-01 20:59:06 +01:00
child: Text ( text , style: _getTextStyle ( themeType , TextStyle ( fontSize: 14 , color: themeType = = AppThemeType . doodle ? inkColor : theme . text . withOpacity ( 0.8 ) , height: 1.3 ) ) ) ,
2026-02-27 23:35:54 +01:00
) ,
] ,
) ;
}
}
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 ;
2026-03-01 20:59:06 +01:00
final bool compact ;
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
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 } ) ;
2026-02-27 23:35:54 +01:00
@ 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 (
2026-03-01 20:59:06 +01:00
padding: EdgeInsets . symmetric ( horizontal: compact ? 12.0 : 22.0 , vertical: compact ? 12.0 : 16.0 ) ,
2026-02-27 23:35:54 +01:00
child: Row (
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
2026-03-01 20:59:06 +01:00
Icon ( icon , color: inkColor , size: compact ? 24 : 32 ) ,
const SizedBox ( width: 12 ) ,
2026-02-27 23:35:54 +01:00
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisAlignment: MainAxisAlignment . center ,
children: [
2026-03-01 20:59:06 +01:00
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 ) ) ) ,
]
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
2026-03-01 20:59:06 +01:00
if ( ! compact ) Icon ( Icons . chevron_right_rounded , color: inkColor . withOpacity ( 0.6 ) , size: 32 ) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
) ,
) ,
) ;
}
return GestureDetector (
onTap: onTap ,
child: Container (
2026-03-01 20:59:06 +01:00
padding: EdgeInsets . symmetric ( horizontal: compact ? 12.0 : 20.0 , vertical: compact ? 10.0 : 14.0 ) ,
2026-02-27 23:35:54 +01:00
decoration: BoxDecoration (
gradient: LinearGradient (
begin: Alignment . topLeft ,
end: Alignment . bottomRight ,
colors: isFeatured
? [ color . withOpacity ( 0.9 ) , color . withOpacity ( 0.6 ) ]
2026-03-01 20:59:06 +01:00
: [ color . withOpacity ( 0.25 ) , color . withOpacity ( 0.05 ) ] ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-01 20:59:06 +01:00
borderRadius: BorderRadius . circular ( 15 ) ,
border: Border . all ( color: color . withOpacity ( isFeatured ? 0.5 : 0.2 ) , width: 1.5 ) ,
2026-02-27 23:35:54 +01:00
boxShadow: [
BoxShadow ( color: Colors . black . withOpacity ( 0.6 ) , offset: const Offset ( 0 , 8 ) , blurRadius: 15 ) ,
2026-03-01 20:59:06 +01:00
BoxShadow ( color: color . withOpacity ( isFeatured ? 0.3 : 0.05 ) , offset: const Offset ( - 1 , - 1 ) , blurRadius: 5 ) ,
2026-02-27 23:35:54 +01:00
]
) ,
child: Row (
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Container (
2026-03-01 20:59:06 +01:00
padding: EdgeInsets . all ( compact ? 6 : 10 ) ,
2026-02-27 23:35:54 +01:00
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 ) ) ]
) ,
2026-03-01 20:59:06 +01:00
child: Icon ( icon , color: isFeatured ? Colors . white : color , size: compact ? 20 : 26 ) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-01 20:59:06 +01:00
SizedBox ( width: compact ? 10 : 20 ) ,
2026-02-27 23:35:54 +01:00
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisAlignment: MainAxisAlignment . center ,
children: [
2026-03-01 20:59:06 +01:00
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 ) ) ) ,
]
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
2026-03-01 20:59:06 +01:00
if ( ! compact ) Icon ( Icons . chevron_right_rounded , color: isFeatured ? Colors . white . withOpacity ( 0.7 ) : color . withOpacity ( 0.5 ) , size: 30 ) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
) ;
}
}
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 < ThemeManager > ( ) . currentColors ;
return AnimatedBuilder (
animation: _controller ,
builder: ( context , child ) {
return CustomPaint (
painter: _CyberBorderPainter ( animationValue: _controller . value , color1: theme . playerBlue , color2: theme . playerRed ) ,
child: Container (
2026-03-01 20:59:06 +01:00
decoration: BoxDecoration ( color: theme . background . withOpacity ( 0.9 ) , borderRadius: BorderRadius . circular ( 15 ) , boxShadow: [ BoxShadow ( color: theme . playerBlue . withOpacity ( 0.3 ) , blurRadius: 25 , spreadRadius: 2 ) ] ) ,
2026-02-27 23:35:54 +01:00
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 ;
2026-03-01 20:59:06 +01:00
final RRect rrect = RRect . fromRectAndRadius ( rect , const Radius . circular ( 15 ) ) ;
2026-02-27 23:35:54 +01:00
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 ;
}