2026-02-27 23:35:54 +01:00
import ' dart:ui ' ;
import ' package:flutter/material.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:cloud_firestore/cloud_firestore.dart ' ;
import ' dart:math ' as math ;
2026-03-01 20:59:06 +01:00
import ' ../../logic/game_controller.dart ' ;
import ' ../../models/game_board.dart ' ;
2026-02-27 23:35:54 +01:00
import ' ../../core/theme_manager.dart ' ;
import ' ../../core/app_colors.dart ' ;
import ' ../../services/multiplayer_service.dart ' ;
import ' ../../services/storage_service.dart ' ;
import ' ../game/game_screen.dart ' ;
import ' package:google_fonts/google_fonts.dart ' ;
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 _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 . blue . shade700 ;
case ' Croce ' : return Colors . teal . shade700 ;
case ' Buco ' : return Colors . pink . shade600 ;
case ' Clessidra ' : return Colors . deepPurple . shade600 ;
case ' Caos ' : return Colors . blueGrey . shade800 ;
default : return Colors . blue . shade700 ;
}
}
@ override
Widget build ( BuildContext context ) {
if ( themeType = = AppThemeType . doodle ) {
Color doodleColor = isLocked ? Colors . grey : _getDoodleColor ( ) ;
double tilt = ( label . length % 2 = = 0 ) ? - 0.03 : 0.04 ;
return Transform . rotate (
angle: tilt ,
child: GestureDetector (
onTap: isLocked ? null : onTap ,
child: AnimatedContainer (
duration: const Duration ( milliseconds: 200 ) ,
padding: const EdgeInsets . symmetric ( horizontal: 6 , vertical: 6 ) ,
transform: Matrix4 . translationValues ( 0 , isSelected ? 3 : 0 , 0 ) ,
decoration: BoxDecoration (
color: isSelected ? doodleColor : Colors . white ,
borderRadius: const BorderRadius . only (
topLeft: Radius . circular ( 15 ) , topRight: Radius . circular ( 8 ) ,
bottomLeft: Radius . circular ( 6 ) , bottomRight: Radius . circular ( 18 ) ,
) ,
border: Border . all ( color: isSelected ? theme . text : doodleColor . withOpacity ( 0.5 ) , width: isSelected ? 2.5 : 1.5 ) ,
boxShadow: isSelected
? [ BoxShadow ( color: theme . text . withOpacity ( 0.8 ) , offset: const Offset ( 3 , 4 ) , blurRadius: 0 ) ]
: [ BoxShadow ( color: doodleColor . withOpacity ( 0.2 ) , offset: const Offset ( 2 , 2 ) , blurRadius: 0 ) ] ,
) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Icon ( isLocked ? Icons . lock : icon , color: isSelected ? Colors . white : doodleColor , size: 20 ) ,
const SizedBox ( height: 2 ) ,
Text ( isLocked ? " Liv. 10 " : label , style: _getTextStyle ( themeType , TextStyle ( color: isSelected ? Colors . white : doodleColor , fontSize: 9 , fontWeight: FontWeight . w900 , letterSpacing: 0.2 ) ) ) ,
] ,
) ,
) ,
) ,
) ;
}
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: 8 , vertical: 8 ) ,
transform: Matrix4 . translationValues ( 0 , isSelected ? 2 : 0 , 0 ) ,
decoration: BoxDecoration (
borderRadius: BorderRadius . circular ( 12 ) ,
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: 20 ) ,
const SizedBox ( height: 4 ) ,
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: 9 , 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 . shade700 : Colors . blueGrey . shade600 ;
double tilt = ( label = = ' M ' | | label = = ' MAX ' ) ? 0.05 : - 0.04 ;
return Transform . rotate (
angle: tilt ,
child: GestureDetector (
onTap: onTap ,
child: AnimatedContainer (
duration: const Duration ( milliseconds: 200 ) ,
width: 42 , height: 40 ,
transform: Matrix4 . translationValues ( 0 , isSelected ? 3 : 0 , 0 ) ,
decoration: BoxDecoration (
color: isSelected ? doodleColor : Colors . white ,
borderRadius: const BorderRadius . all ( Radius . elliptical ( 25 , 20 ) ) ,
border: Border . all ( color: isSelected ? theme . text : doodleColor . withOpacity ( 0.5 ) , width: 2 ) ,
boxShadow: isSelected
? [ BoxShadow ( color: theme . text . withOpacity ( 0.8 ) , offset: const Offset ( 3 , 4 ) , blurRadius: 0 ) ]
: [ BoxShadow ( color: doodleColor . withOpacity ( 0.2 ) , offset: const Offset ( 2 , 2 ) , blurRadius: 0 ) ] ,
) ,
child: Center (
child: Text ( label , style: _getTextStyle ( themeType , TextStyle ( color: isSelected ? Colors . white : doodleColor , fontSize: 13 , fontWeight: FontWeight . w900 ) ) ) ,
) ,
) ,
) ,
) ;
}
return GestureDetector (
onTap: onTap ,
child: AnimatedContainer (
duration: const Duration ( milliseconds: 250 ) ,
curve: Curves . easeOutCubic ,
width: 42 , height: 42 ,
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: 12 , 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 . shade700 ;
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 , isTimeMode ? 3 : 0 , 0 ) ,
decoration: BoxDecoration (
color: isTimeMode ? doodleColor : Colors . white ,
borderRadius: const BorderRadius . only (
topLeft: Radius . circular ( 8 ) , topRight: Radius . circular ( 15 ) ,
bottomLeft: Radius . circular ( 15 ) , bottomRight: Radius . circular ( 6 ) ,
) ,
border: Border . all ( color: isTimeMode ? theme . text : doodleColor . withOpacity ( 0.5 ) , width: 2.5 ) ,
boxShadow: isTimeMode
? [ BoxShadow ( color: theme . text . withOpacity ( 0.8 ) , offset: const Offset ( 4 , 5 ) , blurRadius: 0 ) ]
: [ BoxShadow ( color: doodleColor . withOpacity ( 0.2 ) , offset: const Offset ( 2 , 2 ) , blurRadius: 0 ) ] ,
) ,
child: Row (
mainAxisSize: MainAxisSize . max ,
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Icon ( isTimeMode ? Icons . timer : Icons . timer_off , color: isTimeMode ? Colors . white : doodleColor , size: 24 ) ,
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 : doodleColor , fontWeight: FontWeight . w900 , fontSize: 14 , letterSpacing: 2.0 ) ) ) ,
Text ( isTimeMode ? ' 15 sec a mossa ' : ' Nessun limite ' , style: _getTextStyle ( themeType , TextStyle ( color: isTimeMode ? Colors . white : doodleColor . withOpacity ( 0.8 ) , fontSize: 11 , 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: 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: 24 ) ,
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: 13 , 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: 10 , 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 " ) ? - 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: Text ( label , style: _getTextStyle ( themeType , const 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: Text ( label , style: _getTextStyle ( 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 ) ) ] ) ) ) ,
) ,
) ,
) ;
}
}
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 (
decoration: BoxDecoration ( color: Colors . transparent , borderRadius: BorderRadius . circular ( 20 ) , boxShadow: [ BoxShadow ( color: theme . playerBlue . withOpacity ( 0.15 ) , blurRadius: 20 , spreadRadius: 2 ) ] ) ,
padding: const EdgeInsets . all ( 3 ) ,
child: widget . child ,
) ,
) ;
} ,
child: widget . child ,
) ;
}
}
class _CyberBorderPainter extends CustomPainter {
final double animationValue ;
final Color color1 ;
final Color color2 ;
_CyberBorderPainter ( { required this . animationValue , required this . color1 , required this . color2 } ) ;
@ override
void paint ( Canvas canvas , Size size ) {
final rect = Offset . zero & size ;
final RRect rrect = RRect . fromRectAndRadius ( rect , const Radius . circular ( 20 ) ) ;
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 = 3.0
. . maskFilter = const MaskFilter . blur ( BlurStyle . solid , 3 ) ;
canvas . drawRRect ( rrect , paint ) ;
}
@ override
bool shouldRepaint ( covariant _CyberBorderPainter oldDelegate ) = > oldDelegate . animationValue ! = animationValue ;
}
class LobbyScreen extends StatefulWidget {
final String ? initialRoomCode ;
const LobbyScreen ( { super . key , this . initialRoomCode } ) ;
@ override
State < LobbyScreen > createState ( ) = > _LobbyScreenState ( ) ;
}
class _LobbyScreenState extends State < LobbyScreen > {
final MultiplayerService _multiplayerService = MultiplayerService ( ) ;
late TextEditingController _codeController ;
bool _isLoading = false ;
String ? _myRoomCode ;
String _playerName = ' ' ;
int _selectedRadius = 4 ;
ArenaShape _selectedShape = ArenaShape . classic ;
bool _isTimeMode = true ;
@ override
void initState ( ) {
super . initState ( ) ;
_codeController = TextEditingController ( ) ;
_playerName = StorageService . instance . playerName ;
if ( widget . initialRoomCode ! = null & & widget . initialRoomCode ! . isNotEmpty ) {
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
setState ( ( ) { _codeController . text = widget . initialRoomCode ! ; } ) ;
} ) ;
}
}
@ override
void dispose ( ) { _codeController . dispose ( ) ; super . dispose ( ) ; }
Future < void > _createRoom ( ) async {
if ( _isLoading ) return ;
setState ( ( ) = > _isLoading = true ) ;
try {
String code = await _multiplayerService . createGameRoom ( _selectedRadius , _playerName , _selectedShape . name , _isTimeMode ) ;
if ( ! mounted ) return ;
setState ( ( ) { _myRoomCode = code ; _isLoading = false ; } ) ;
_multiplayerService . shareInviteLink ( code ) ;
_showWaitingDialog ( code ) ;
} catch ( e ) {
if ( mounted ) { setState ( ( ) = > _isLoading = false ) ; _showError ( " Errore durante la creazione della partita. " ) ; }
}
}
Future < void > _joinRoom ( ) async {
if ( _isLoading ) return ;
FocusScope . of ( context ) . unfocus ( ) ;
String code = _codeController . text . trim ( ) . toUpperCase ( ) ;
if ( code . isEmpty | | code . length ! = 5 ) { _showError ( " Inserisci un codice valido di 5 caratteri. " ) ; return ; }
setState ( ( ) = > _isLoading = true ) ;
try {
Map < String , dynamic > ? roomData = await _multiplayerService . joinGameRoom ( code , _playerName ) ;
if ( ! mounted ) return ;
setState ( ( ) = > _isLoading = false ) ;
if ( roomData ! = null ) {
ScaffoldMessenger . of ( context ) . showSnackBar ( const SnackBar ( content: Text ( " Stanza trovata! Partita in avvio... " ) , backgroundColor: Colors . green ) ) ;
int hostRadius = roomData [ ' radius ' ] ? ? 4 ;
String shapeStr = roomData [ ' shape ' ] ? ? ' classic ' ;
ArenaShape hostShape = ArenaShape . values . firstWhere ( ( e ) = > e . name = = shapeStr , orElse: ( ) = > ArenaShape . classic ) ;
bool hostTimeMode = roomData [ ' timeMode ' ] ? ? true ;
context . read < GameController > ( ) . startNewGame ( hostRadius , isOnline: true , roomCode: code , isHost: false , shape: hostShape , timeMode: hostTimeMode ) ;
Navigator . pushReplacement ( context , MaterialPageRoute ( builder: ( _ ) = > const GameScreen ( ) ) ) ;
} else {
_showError ( " Stanza non trovata, piena o partita già iniziata. " ) ;
}
} catch ( e ) {
if ( mounted ) { setState ( ( ) = > _isLoading = false ) ; _showError ( " Errore di connessione: $ e " ) ; }
}
}
void _showError ( String message ) { ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( message , style: const TextStyle ( color: Colors . white ) ) , backgroundColor: Colors . red ) ) ; }
void _showWaitingDialog ( String code ) {
showDialog (
context: context ,
barrierDismissible: false ,
builder: ( context ) {
final theme = context . watch < ThemeManager > ( ) . currentColors ;
final themeType = context . read < ThemeManager > ( ) . currentThemeType ;
Widget dialogContent = Column (
mainAxisSize: MainAxisSize . min ,
children: [
CircularProgressIndicator ( color: theme . playerRed ) , const SizedBox ( height: 25 ) ,
Text ( " CODICE STANZA " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 16 , fontWeight: FontWeight . bold , color: theme . text . withOpacity ( 0.6 ) , letterSpacing: 2 ) ) ) ,
Text ( code , style: _getTextStyle ( themeType , TextStyle ( fontSize: 40 , fontWeight: FontWeight . w900 , color: theme . playerRed , letterSpacing: 8 , shadows: themeType = = AppThemeType . doodle ? [ ] : [ Shadow ( color: theme . playerRed . withOpacity ( 0.5 ) , blurRadius: 10 ) ] ) ) ) ,
const SizedBox ( height: 25 ) ,
Transform . rotate (
angle: themeType = = AppThemeType . doodle ? 0.02 : 0 ,
child: Container (
padding: const EdgeInsets . all ( 18 ) ,
decoration: BoxDecoration (
color: themeType = = AppThemeType . doodle ? Colors . white : theme . text . withOpacity ( 0.05 ) ,
borderRadius: BorderRadius . circular ( 20 ) ,
border: Border . all ( color: themeType = = AppThemeType . doodle ? theme . text : theme . playerBlue . withOpacity ( 0.3 ) , width: themeType = = AppThemeType . doodle ? 2 : 1.5 ) ,
boxShadow: themeType = = AppThemeType . doodle
? [ BoxShadow ( color: theme . text . withOpacity ( 0.8 ) , offset: const Offset ( 4 , 4 ) ) ]
: [ BoxShadow ( color: theme . playerBlue . withOpacity ( 0.1 ) , blurRadius: 10 ) ]
) ,
child: Column (
children: [
Icon ( Icons . share , color: theme . playerBlue , size: 32 ) , const SizedBox ( height: 12 ) ,
Text ( " Invita un amico " , textAlign: TextAlign . center , style: _getTextStyle ( themeType , TextStyle ( color: theme . text , fontWeight: FontWeight . w900 , fontSize: 18 ) ) ) ,
const SizedBox ( height: 8 ) ,
Text ( " Condividi il codice. La partita inizierà appena si unirà. " , textAlign: TextAlign . center , style: _getTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.8 ) , fontSize: 14 , height: 1.5 ) ) ) ,
] ,
) ,
) ,
) ,
] ,
) ;
if ( themeType = = AppThemeType . cyberpunk ) {
dialogContent = _AnimatedCyberBorder ( child: dialogContent ) ;
} else {
dialogContent = Container (
padding: const EdgeInsets . all ( 20 ) ,
decoration: BoxDecoration (
color: themeType = = AppThemeType . doodle ? Colors . white . withOpacity ( 0.95 ) : theme . background ,
borderRadius: BorderRadius . circular ( 25 ) ,
border: Border . all ( color: themeType = = AppThemeType . doodle ? theme . text : theme . gridLine . withOpacity ( 0.5 ) , width: 2 ) ,
boxShadow: themeType = = AppThemeType . doodle ? [ BoxShadow ( color: theme . text . withOpacity ( 0.6 ) , offset: const Offset ( 8 , 8 ) ) ] : [ ]
) ,
child: dialogContent
) ;
}
return StreamBuilder < DocumentSnapshot > (
stream: _multiplayerService . listenToRoom ( code ) ,
builder: ( context , snapshot ) {
if ( snapshot . hasData & & snapshot . data ! . exists ) {
var data = snapshot . data ! . data ( ) as Map < String , dynamic > ;
if ( data [ ' status ' ] = = ' playing ' ) {
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
Navigator . pop ( context ) ;
context . read < GameController > ( ) . startNewGame ( _selectedRadius , isOnline: true , roomCode: code , isHost: true , shape: _selectedShape , timeMode: _isTimeMode ) ;
Navigator . pushReplacement ( context , MaterialPageRoute ( builder: ( _ ) = > const GameScreen ( ) ) ) ;
} ) ;
}
}
return Dialog (
backgroundColor: Colors . transparent ,
insetPadding: const EdgeInsets . all ( 20 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
dialogContent ,
const SizedBox ( height: 20 ) ,
TextButton (
onPressed: ( ) { FirebaseFirestore . instance . collection ( ' games ' ) . doc ( code ) . delete ( ) ; Navigator . pop ( context ) ; } ,
child: Text ( " ANNULLA " , style: _getTextStyle ( themeType , TextStyle ( color: Colors . red , fontWeight: FontWeight . w900 , fontSize: 20 , letterSpacing: 2.0 , shadows: themeType = = AppThemeType . doodle ? [ ] : [ const Shadow ( color: Colors . black , blurRadius: 2 ) ] ) ) ) ,
) ,
] ,
) ,
) ;
} ,
) ;
}
) ;
}
@ override
Widget build ( BuildContext context ) {
final themeManager = context . watch < ThemeManager > ( ) ;
final themeType = themeManager . currentThemeType ;
final theme = themeManager . currentColors ;
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 ' ;
2026-03-01 20:59:06 +01:00
bool isChaosUnlocked = true ;
2026-02-27 23:35:54 +01:00
Color doodlePenColor = const Color ( 0xFF00008B ) ;
Widget hostPanel = Transform . rotate (
angle: themeType = = AppThemeType . doodle ? 0.01 : 0 ,
child: Container (
padding: const EdgeInsets . symmetric ( horizontal: 12 , vertical: 15 ) ,
decoration: BoxDecoration (
color: themeType = = AppThemeType . cyberpunk ? Colors . black . withOpacity ( 0.85 ) : ( themeType = = AppThemeType . doodle ? Colors . white . withOpacity ( 0.5 ) : Colors . transparent ) ,
borderRadius: BorderRadius . only (
topLeft: Radius . circular ( themeType = = AppThemeType . doodle ? 5 : 20 ) ,
topRight: const Radius . circular ( 20 ) ,
bottomLeft: const Radius . circular ( 20 ) ,
bottomRight: Radius . circular ( themeType = = AppThemeType . doodle ? 5 : 20 ) ,
) ,
border: themeType = = AppThemeType . cyberpunk ? null : Border . all ( color: themeType = = AppThemeType . doodle ? theme . text . withOpacity ( 0.5 ) : Colors . white . withOpacity ( 0.15 ) , width: themeType = = AppThemeType . doodle ? 2 : 1.5 ) ,
) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Center ( child: Text ( " IMPOSTAZIONI GRIGLIA " , textAlign: TextAlign . center , style: _getTextStyle ( themeType , TextStyle ( fontSize: 12 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.6 ) , letterSpacing: 2.0 ) ) ) ) ,
const SizedBox ( height: 10 ) ,
Text ( " FORMA ARENA " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 10 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) ,
const SizedBox ( height: 6 ) ,
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
_NeonShapeButton ( icon: Icons . diamond_outlined , label: ' Rombo ' , isSelected: _selectedShape = = ArenaShape . classic , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . classic ) ) ,
_NeonShapeButton ( icon: Icons . add , label: ' Croce ' , isSelected: _selectedShape = = ArenaShape . cross , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . cross ) ) ,
_NeonShapeButton ( icon: Icons . donut_large , label: ' Buco ' , isSelected: _selectedShape = = ArenaShape . donut , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . donut ) ) ,
_NeonShapeButton ( icon: Icons . hourglass_bottom , label: ' Clessidra ' , isSelected: _selectedShape = = ArenaShape . hourglass , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . hourglass ) ) ,
_NeonShapeButton ( icon: Icons . all_inclusive , label: ' Caos ' , isSelected: _selectedShape = = ArenaShape . chaos , theme: theme , themeType: themeType , isSpecial: true , isLocked: ! isChaosUnlocked , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . chaos ) ) ,
] ,
) ,
const SizedBox ( height: 12 ) ,
Divider ( color: themeType = = AppThemeType . doodle ? theme . text . withOpacity ( 0.5 ) : Colors . white . withOpacity ( 0.05 ) , thickness: themeType = = AppThemeType . doodle ? 2.5 : 1.5 ) ,
const SizedBox ( height: 12 ) ,
Text ( " GRANDEZZA " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 10 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) ,
const SizedBox ( height: 8 ) ,
Row (
mainAxisAlignment: MainAxisAlignment . spaceEvenly ,
children: [
_NeonSizeButton ( label: ' S ' , isSelected: _selectedRadius = = 3 , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedRadius = 3 ) ) ,
_NeonSizeButton ( label: ' M ' , isSelected: _selectedRadius = = 4 , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedRadius = 4 ) ) ,
_NeonSizeButton ( label: ' L ' , isSelected: _selectedRadius = = 5 , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedRadius = 5 ) ) ,
_NeonSizeButton ( label: ' MAX ' , isSelected: _selectedRadius = = 6 , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedRadius = 6 ) ) ,
] ,
) ,
const SizedBox ( height: 12 ) ,
Divider ( color: themeType = = AppThemeType . doodle ? theme . text . withOpacity ( 0.5 ) : Colors . white . withOpacity ( 0.05 ) , thickness: themeType = = AppThemeType . doodle ? 2.5 : 1.5 ) ,
const SizedBox ( height: 12 ) ,
Text ( " TEMPO " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 10 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) ,
const SizedBox ( height: 8 ) ,
_NeonTimeSwitch ( isTimeMode: _isTimeMode , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _isTimeMode = ! _isTimeMode ) ) ,
] ,
) ,
) ,
) ;
if ( themeType = = AppThemeType . cyberpunk ) {
hostPanel = _AnimatedCyberBorder ( child: hostPanel ) ;
}
Widget uiContent = SafeArea (
child: SingleChildScrollView (
padding: const EdgeInsets . symmetric ( horizontal: 20.0 , vertical: 10.0 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Transform . rotate (
angle: themeType = = AppThemeType . doodle ? - 0.02 : 0 ,
child: Text ( " MULTIPLAYER " , style: _getTextStyle ( themeType , TextStyle ( fontSize: 20 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 1 , shadows: themeType = = AppThemeType . doodle ? [ ] : [ Shadow ( color: Colors . black . withOpacity ( 0.5 ) , offset: const Offset ( 2 , 2 ) , blurRadius: 4 ) ] ) ) ) ,
) ,
Transform . rotate (
angle: themeType = = AppThemeType . doodle ? 0.03 : 0 ,
child: Container (
padding: const EdgeInsets . symmetric ( horizontal: 12 , vertical: 4 ) ,
decoration: BoxDecoration (
color: themeType = = AppThemeType . doodle ? Colors . white : theme . playerRed . withOpacity ( 0.2 ) ,
borderRadius: BorderRadius . circular ( 20 ) ,
border: Border . all ( color: themeType = = AppThemeType . doodle ? theme . playerRed : theme . playerRed . withOpacity ( 0.5 ) , width: themeType = = AppThemeType . doodle ? 2.5 : 1.0 ) ,
boxShadow: themeType = = AppThemeType . doodle ? [ BoxShadow ( color: theme . text . withOpacity ( 0.6 ) , offset: const Offset ( 2 , 3 ) , blurRadius: 0 ) ] : [ ]
) ,
child: Text ( " $ _playerName " , textAlign: TextAlign . center , style: _getTextStyle ( themeType , TextStyle ( fontSize: 14 , fontWeight: FontWeight . bold , color: theme . playerRed , letterSpacing: 1 ) ) ) ,
) ,
) ,
] ,
) ,
const SizedBox ( height: 20 ) ,
hostPanel ,
const SizedBox ( height: 15 ) ,
_NeonActionButton ( label: " CREA PARTITA " , color: theme . playerRed , onTap: _createRoom , theme: theme , themeType: themeType ) ,
const SizedBox ( height: 20 ) ,
Row (
children: [
Expanded ( child: Divider ( color: theme . text . withOpacity ( 0.4 ) , thickness: themeType = = AppThemeType . doodle ? 2 : 1.0 ) ) ,
Padding ( padding: const EdgeInsets . symmetric ( horizontal: 10 ) , child: Text ( " OPPURE " , style: _getTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , fontWeight: FontWeight . bold , letterSpacing: 2.0 , fontSize: 13 ) ) ) ) ,
Expanded ( child: Divider ( color: theme . text . withOpacity ( 0.4 ) , thickness: themeType = = AppThemeType . doodle ? 2 : 1.0 ) ) ,
] ,
) ,
const SizedBox ( height: 20 ) ,
Transform . rotate (
angle: themeType = = AppThemeType . doodle ? 0.02 : 0 ,
child: Container (
decoration: themeType = = AppThemeType . doodle ? BoxDecoration (
color: Colors . white ,
borderRadius: const BorderRadius . only ( topLeft: Radius . circular ( 20 ) , bottomRight: Radius . circular ( 20 ) , topRight: Radius . circular ( 5 ) , bottomLeft: Radius . circular ( 5 ) ) ,
border: Border . all ( color: theme . text , width: 2.5 ) ,
boxShadow: [ BoxShadow ( color: theme . text . withOpacity ( 0.8 ) , offset: const Offset ( 5 , 5 ) , blurRadius: 0 ) ] ,
) : BoxDecoration (
boxShadow: [ BoxShadow ( color: theme . playerBlue . withOpacity ( 0.15 ) , blurRadius: 15 , spreadRadius: 1 ) ]
) ,
child: TextField (
controller: _codeController , textCapitalization: TextCapitalization . characters , textAlign: TextAlign . center , maxLength: 5 ,
style: _getTextStyle ( themeType , TextStyle ( fontSize: 28 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 12 , shadows: themeType = = AppThemeType . doodle ? [ ] : [ Shadow ( color: theme . playerBlue . withOpacity ( 0.5 ) , blurRadius: 8 ) ] ) ) ,
decoration: InputDecoration (
contentPadding: const EdgeInsets . symmetric ( vertical: 12 ) ,
hintText: " CODICE " , hintStyle: _getTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.3 ) , letterSpacing: 10 , fontSize: 20 ) ) , counterText: " " ,
filled: themeType ! = AppThemeType . doodle ,
fillColor: themeType = = AppThemeType . cyberpunk ? Colors . black . withOpacity ( 0.85 ) : theme . text . withOpacity ( 0.05 ) ,
enabledBorder: themeType = = AppThemeType . doodle ? InputBorder . none : OutlineInputBorder ( borderSide: BorderSide ( color: theme . gridLine . withOpacity ( 0.5 ) , width: 2.0 ) , borderRadius: BorderRadius . circular ( 15 ) ) ,
focusedBorder: themeType = = AppThemeType . doodle ? InputBorder . none : OutlineInputBorder ( borderSide: BorderSide ( color: theme . playerBlue , width: 3.0 ) , borderRadius: BorderRadius . circular ( 15 ) ) ,
) ,
) ,
) ,
) ,
const SizedBox ( height: 15 ) ,
_NeonActionButton ( label: " UNISCITI " , color: theme . playerBlue , onTap: _joinRoom , theme: theme , themeType: themeType ) ,
const SizedBox ( height: 10 ) ,
] ,
) ,
) ,
) ;
return Scaffold (
backgroundColor: bgImage ! = null ? Colors . transparent : theme . background ,
extendBodyBehindAppBar: true ,
appBar: AppBar ( backgroundColor: Colors . transparent , elevation: 0 , iconTheme: IconThemeData ( color: theme . text ) ) ,
body: Stack (
children: [
Container (
decoration: bgImage ! = null ? BoxDecoration ( image: DecorationImage ( image: AssetImage ( bgImage ) , fit: BoxFit . cover ) ) : null ,
child: bgImage ! = null & & themeType = = AppThemeType . cyberpunk
? BackdropFilter ( filter: ImageFilter . blur ( sigmaX: 3.5 , sigmaY: 3.5 ) , child: Container ( color: Colors . black . withOpacity ( 0.2 ) ) )
: bgImage ! = null & & themeType ! = AppThemeType . cyberpunk
? BackdropFilter ( filter: ImageFilter . blur ( sigmaX: 3.5 , sigmaY: 3.5 ) , child: Container ( color: themeType = = AppThemeType . doodle ? Colors . white . withOpacity ( 0.1 ) : Colors . transparent ) )
: null ,
) ,
if ( themeType = = AppThemeType . doodle )
Positioned (
top: 150 , left: - 20 , right: - 20 ,
child: Stack (
alignment: Alignment . center ,
children: [
Transform . rotate ( angle: - 0.06 , child: Icon ( Icons . wifi_tethering , size: 450 , color: doodlePenColor . withOpacity ( 0.08 ) ) ) ,
Transform . rotate ( angle: 0.04 , child: Icon ( Icons . wifi_tethering , size: 430 , color: doodlePenColor . withOpacity ( 0.06 ) ) ) ,
Transform . rotate ( angle: 0.01 , child: Icon ( Icons . wifi_tethering , size: 460 , color: doodlePenColor . withOpacity ( 0.05 ) ) ) ,
] ,
) ,
)
else
Positioned (
top: 70 , left: - 50 , right: - 50 ,
child: Center (
child: Icon ( Icons . wifi_tethering , size: 450 , color: theme . playerBlue . withOpacity ( 0.12 ) ) ,
) ,
) ,
_isLoading ? Center ( child: CircularProgressIndicator ( color: theme . playerRed ) ) : uiContent ,
] ,
) ,
) ;
}
}