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-14 00:00:01 +01:00
import ' package:font_awesome_flutter/font_awesome_flutter.dart ' ;
2026-03-14 19:00:00 +01:00
import ' package:cloud_firestore/cloud_firestore.dart ' ;
2026-03-14 00:00:01 +01:00
import ' dart:async ' ;
import ' package:app_links/app_links.dart ' ;
2026-03-01 20:59:06 +01:00
2026-03-15 03:32:09 +01:00
import ' package:firebase_auth/firebase_auth.dart ' ;
import ' package:shared_preferences/shared_preferences.dart ' ;
2026-03-01 20:59:06 +01:00
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 ' ;
2026-03-14 19:00:00 +01:00
import ' ../../services/audio_service.dart ' ;
import ' ../../services/multiplayer_service.dart ' ;
2026-02-27 23:35:54 +01:00
import ' ../multiplayer/lobby_screen.dart ' ;
import ' history_screen.dart ' ;
2026-03-12 15:00:00 +01:00
import ' ../admin/admin_screen.dart ' ;
2026-03-14 00:00:01 +01:00
import ' package:tetraq/l10n/app_localizations.dart ' ;
2026-02-27 23:35:54 +01:00
2026-03-14 00:00:01 +01:00
import ' ../../widgets/painters.dart ' ;
import ' ../../widgets/cyber_border.dart ' ;
import ' ../../widgets/music_theme_widgets.dart ' ;
2026-03-14 19:00:00 +01:00
import ' ../../widgets/home_buttons.dart ' ;
2026-03-15 03:00:01 +01:00
import ' ../../widgets/custom_settings_button.dart ' ;
2026-03-14 19:00:00 +01:00
import ' dialog.dart ' ;
2026-02-27 23:35:54 +01:00
2026-03-14 00:00:01 +01:00
// ===========================================================================
// CLASSE PRINCIPALE HOME
// ===========================================================================
2026-02-27 23:35:54 +01:00
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-03-12 21:00:08 +01:00
late AppLinks _appLinks ;
StreamSubscription < Uri > ? _linkSubscription ;
2026-03-14 19:00:00 +01:00
bool _isCreatingRoom = false ;
int _selectedRadius = 4 ;
ArenaShape _selectedShape = ArenaShape . classic ;
bool _isTimeMode = true ;
bool _isPublicRoom = true ;
bool _isLoading = false ;
String ? _myRoomCode ;
bool _roomStarted = false ;
final MultiplayerService _multiplayerService = MultiplayerService ( ) ;
final TextEditingController _codeController = TextEditingController ( ) ;
2026-03-12 21:00:08 +01:00
2026-02-27 23:35:54 +01:00
@ override
void initState ( ) {
super . initState ( ) ;
WidgetsBinding . instance . addObserver ( this ) ;
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
_checkPlayerName ( ) ;
2026-03-12 21:00:08 +01:00
StorageService . instance . syncLeaderboard ( ) ;
2026-02-27 23:35:54 +01:00
} ) ;
_checkClipboardForInvite ( ) ;
2026-03-13 22:00:00 +01:00
_initDeepLinks ( ) ;
2026-02-27 23:35:54 +01:00
}
@ override
void dispose ( ) {
WidgetsBinding . instance . removeObserver ( this ) ;
2026-03-14 19:00:00 +01:00
_cleanupGhostRoom ( ) ;
2026-03-13 22:00:00 +01:00
_linkSubscription ? . cancel ( ) ;
2026-03-14 19:00:00 +01:00
_codeController . dispose ( ) ;
2026-02-27 23:35:54 +01:00
super . dispose ( ) ;
}
@ override
void didChangeAppLifecycleState ( AppLifecycleState state ) {
if ( state = = AppLifecycleState . resumed ) {
_checkClipboardForInvite ( ) ;
2026-03-14 19:00:00 +01:00
} else if ( state = = AppLifecycleState . paused | | state = = AppLifecycleState . detached ) {
_cleanupGhostRoom ( ) ;
}
}
void _cleanupGhostRoom ( ) {
if ( _myRoomCode ! = null & & ! _roomStarted ) {
FirebaseFirestore . instance . collection ( ' games ' ) . doc ( _myRoomCode ) . delete ( ) ;
_myRoomCode = null ;
2026-02-27 23:35:54 +01:00
}
}
void _checkPlayerName ( ) {
2026-03-14 00:00:01 +01:00
if ( StorageService . instance . playerName . isEmpty ) { _showNameDialog ( ) ; }
}
Future < void > _initDeepLinks ( ) async {
_appLinks = AppLinks ( ) ;
try {
final initialUri = await _appLinks . getInitialLink ( ) ;
if ( initialUri ! = null ) _handleDeepLink ( initialUri ) ;
} catch ( e ) { debugPrint ( " Errore lettura link iniziale: $ e " ) ; }
_linkSubscription = _appLinks . uriLinkStream . listen ( ( uri ) { _handleDeepLink ( uri ) ; } , onError: ( err ) { debugPrint ( " Errore stream link: $ err " ) ; } ) ;
}
void _handleDeepLink ( Uri uri ) {
if ( uri . scheme = = ' tetraq ' & & uri . host = = ' join ' ) {
String ? code = uri . queryParameters [ ' code ' ] ;
if ( code ! = null & & code . length = = 5 ) {
Future . delayed ( const Duration ( milliseconds: 500 ) , ( ) {
if ( mounted ) _promptJoinRoom ( code . toUpperCase ( ) ) ;
} ) ;
}
2026-02-27 23:35:54 +01:00
}
}
2026-03-14 00:00:01 +01:00
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 " ) ; }
}
2026-03-14 19:00:00 +01:00
Future < void > _createRoom ( ) async {
if ( _isLoading ) return ;
setState ( ( ) = > _isLoading = true ) ;
try {
String playerName = StorageService . instance . playerName ;
if ( playerName . isEmpty ) playerName = " HOST " ;
String code = await _multiplayerService . createGameRoom (
_selectedRadius , playerName , _selectedShape . name , _isTimeMode , isPublic: _isPublicRoom
) ;
if ( ! mounted ) return ;
setState ( ( ) { _myRoomCode = code ; _isLoading = false ; _roomStarted = false ; } ) ;
if ( ! _isPublicRoom ) {
_multiplayerService . shareInviteLink ( code ) ;
}
_showWaitingDialog ( code ) ;
} catch ( e ) {
if ( mounted ) { setState ( ( ) = > _isLoading = false ) ; _showError ( " Errore durante la creazione della partita. " ) ; }
}
}
Future < void > _joinRoomByCode ( String code ) async {
if ( _isLoading ) return ;
FocusScope . of ( context ) . unfocus ( ) ;
code = code . trim ( ) . toUpperCase ( ) ;
if ( code . isEmpty | | code . length ! = 5 ) { _showError ( " Inserisci un codice valido di 5 caratteri. " ) ; return ; }
setState ( ( ) = > _isLoading = true ) ;
try {
String playerName = StorageService . instance . playerName ;
if ( playerName . isEmpty ) playerName = " GUEST " ;
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: getSharedTextStyle ( themeType , TextStyle ( fontSize: 16 , fontWeight: FontWeight . bold , color: theme . text . withOpacity ( 0.6 ) , letterSpacing: 2 ) ) ) ,
Text ( code , style: getSharedTextStyle ( 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 ( _isPublicRoom ? Icons . podcasts : Icons . share , color: theme . playerBlue , size: 32 ) , const SizedBox ( height: 12 ) ,
Text ( _isPublicRoom ? " Sei in Bacheca! " : " Invita un amico " , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text , fontWeight: FontWeight . w900 , fontSize: 18 ) ) ) ,
const SizedBox ( height: 8 ) ,
Text ( _isPublicRoom ? " Aspettiamo che uno sfidante si unisca dalla lobby pubblica. " : " Condividi il codice. La partita inizierà appena si unirà. " , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.8 ) , fontSize: 14 , height: 1.5 ) ) ) ,
] ,
) ,
) ,
) ,
] ,
) ;
if ( themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . music ) {
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 ' ) {
_roomStarted = true ;
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 PopScope (
canPop: false ,
onPopInvoked: ( didPop ) {
if ( didPop ) return ;
_cleanupGhostRoom ( ) ;
Navigator . pop ( context ) ;
} ,
child: Dialog (
backgroundColor: Colors . transparent ,
insetPadding: const EdgeInsets . all ( 20 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
dialogContent ,
const SizedBox ( height: 20 ) ,
TextButton (
onPressed: ( ) {
_cleanupGhostRoom ( ) ;
Navigator . pop ( context ) ;
} ,
child: Text ( " ANNULLA " , style: getSharedTextStyle ( themeType , TextStyle ( color: Colors . red , fontWeight: FontWeight . w900 , fontSize: 20 , letterSpacing: 2.0 , shadows: themeType = = AppThemeType . doodle ? [ ] : [ const Shadow ( color: Colors . black , blurRadius: 2 ) ] ) ) ) ,
) ,
] ,
) ,
) ,
) ;
} ,
) ;
}
) ;
}
2026-03-14 00:00:01 +01:00
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: getSharedTextStyle ( themeType , TextStyle ( color: theme . text , fontWeight: FontWeight . bold ) ) ) ,
content: Text ( " Vuoi unirti alla stanza $ roomCode ? " , style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text ) ) ) ,
actions: [
TextButton ( onPressed: ( ) = > Navigator . pop ( context ) , child: Text ( " No " , style: getSharedTextStyle ( themeType , const TextStyle ( color: Colors . red ) ) ) ) ,
ElevatedButton (
style: ElevatedButton . styleFrom ( backgroundColor: themeType = = AppThemeType . doodle ? Colors . transparent : theme . playerBlue , elevation: 0 , side: themeType = = AppThemeType . doodle ? BorderSide ( color: theme . text , width: 1.5 ) : BorderSide . none ) ,
onPressed: ( ) {
Navigator . of ( context ) . pop ( ) ;
2026-03-14 19:00:00 +01:00
_joinRoomByCode ( roomCode ) ;
2026-03-14 00:00:01 +01:00
} ,
child: Text ( AppLocalizations . of ( context ) ! . joinMatch , style: getSharedTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : Colors . white , fontWeight: FontWeight . bold ) ) ) ,
) ,
] ,
) ;
}
) ;
}
2026-03-15 03:32:09 +01:00
// --- FINESTRA DI REGISTRAZIONE/LOGIN ---
2026-02-27 23:35:54 +01:00
void _showNameDialog ( ) {
final TextEditingController nameController = TextEditingController ( text: StorageService . instance . playerName ) ;
2026-03-15 03:32:09 +01:00
final TextEditingController passController = TextEditingController ( ) ;
bool isLoadingAuth = false ;
bool _obscurePassword = true ;
String _errorMessage = " " ;
2026-02-27 23:35:54 +01:00
showDialog (
2026-03-14 00:00:01 +01:00
context: context , barrierDismissible: false , barrierColor: Colors . black . withOpacity ( 0.8 ) ,
2026-02-27 23:35:54 +01:00
builder: ( context ) {
final themeManager = context . watch < ThemeManager > ( ) ;
2026-03-14 00:00:01 +01:00
final theme = themeManager . currentColors ; final themeType = themeManager . currentThemeType ;
Color inkColor = const Color ( 0xFF111122 ) ; final loc = AppLocalizations . of ( context ) ! ;
2026-02-27 23:35:54 +01:00
2026-03-15 03:32:09 +01:00
return StatefulBuilder (
builder: ( context , setStateDialog ) {
Future < void > handleAuth ( bool isLogin ) async {
final name = nameController . text . trim ( ) ;
final password = passController . text . trim ( ) ;
setStateDialog ( ( ) {
_errorMessage = " " ;
isLoadingAuth = true ;
} ) ;
if ( name . isEmpty | | password . isEmpty ) {
setStateDialog ( ( ) {
_errorMessage = " Inserisci Nome e Password! " ;
isLoadingAuth = false ;
} ) ;
return ;
}
if ( password . length < 6 ) {
setStateDialog ( ( ) {
_errorMessage = " La password deve avere almeno 6 caratteri! " ;
isLoadingAuth = false ;
} ) ;
return ;
}
final fakeEmail = " ${ name . toLowerCase ( ) . replaceAll ( ' ' , ' ' ) } @tetraq.game " ;
try {
if ( isLogin ) {
await FirebaseAuth . instance . signInWithEmailAndPassword ( email: fakeEmail , password: password ) ;
final doc = await FirebaseFirestore . instance . collection ( ' leaderboard ' ) . doc ( FirebaseAuth . instance . currentUser ! . uid ) . get ( ) ;
if ( doc . exists ) {
final data = doc . data ( ) as Map < String , dynamic > ;
final prefs = await SharedPreferences . getInstance ( ) ;
await prefs . setInt ( ' totalXP ' , data [ ' xp ' ] ? ? 0 ) ;
await prefs . setInt ( ' wins ' , data [ ' wins ' ] ? ? 0 ) ;
await prefs . setInt ( ' losses ' , data [ ' losses ' ] ? ? 0 ) ;
}
if ( mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( " Bentornato $ name ! Dati sincronizzati. " ) , backgroundColor: Colors . green ) ) ;
} else {
await FirebaseAuth . instance . createUserWithEmailAndPassword ( email: fakeEmail , password: password ) ;
if ( mounted ) ScaffoldMessenger . of ( context ) . showSnackBar ( const SnackBar ( content: Text ( " Account creato con successo! " ) , backgroundColor: Colors . green ) ) ;
}
await StorageService . instance . savePlayerName ( name ) ;
if ( mounted ) Navigator . of ( context ) . pop ( ) ;
setState ( ( ) { } ) ;
} on FirebaseAuthException catch ( e ) {
String msg = " Errore di autenticazione. " ;
if ( e . code = = ' email-already-in-use ' ) {
msg = " Nome occupato! \n Se è il tuo account, clicca su ACCEDI. " ;
} else if ( e . code = = ' user-not-found ' | | e . code = = ' wrong-password ' | | e . code = = ' invalid-credential ' ) {
msg = " Nome o Password errati! " ;
}
setStateDialog ( ( ) {
_errorMessage = msg ;
isLoadingAuth = false ;
} ) ;
} catch ( e ) {
setStateDialog ( ( ) {
_errorMessage = " Errore imprevisto: $ e " ;
isLoadingAuth = false ;
} ) ;
}
}
Widget dialogContent = themeType = = AppThemeType . doodle
? CustomPaint (
painter: DoodleBackgroundPainter ( fillColor: Colors . yellow . shade100 , strokeColor: inkColor , seed: 100 ) ,
child: SingleChildScrollView (
physics: const BouncingScrollPhysics ( ) ,
child: Padding (
padding: const EdgeInsets . symmetric ( vertical: 20.0 , horizontal: 20.0 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( loc . welcomeTitle , style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor , fontWeight: FontWeight . w900 , fontSize: 24 , letterSpacing: 2.0 ) ) , textAlign: TextAlign . center ) ,
const SizedBox ( height: 10 ) ,
Text ( ' Scegli Nome e Password. \n Ti serviranno per recuperare gli XP! ' , style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor . withOpacity ( 0.8 ) , fontSize: 13 ) ) , textAlign: TextAlign . center ) ,
const SizedBox ( height: 15 ) ,
TextField (
controller: nameController , textCapitalization: TextCapitalization . characters , textAlign: TextAlign . center , maxLength: 8 ,
style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor , fontSize: 24 , fontWeight: FontWeight . bold , letterSpacing: 4 ) ) ,
decoration: InputDecoration (
hintText: loc . nameHint ,
hintStyle: getSharedTextStyle ( themeType , TextStyle ( color: inkColor . withOpacity ( 0.3 ) , letterSpacing: 4 ) ) ,
filled: false , counterText: " " ,
enabledBorder: UnderlineInputBorder ( borderSide: BorderSide ( color: inkColor , width: 3 ) ) ,
focusedBorder: UnderlineInputBorder ( borderSide: BorderSide ( color: Colors . red . shade200 , width: 5 ) )
) ,
) ,
const SizedBox ( height: 8 ) ,
TextField (
controller: passController , obscureText: _obscurePassword , textAlign: TextAlign . center , maxLength: 20 ,
style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor , fontSize: 20 , fontWeight: FontWeight . bold , letterSpacing: 8 ) ) ,
decoration: InputDecoration (
hintText: " PASSWORD " ,
hintStyle: getSharedTextStyle ( themeType , TextStyle ( color: inkColor . withOpacity ( 0.3 ) , letterSpacing: 4 ) ) ,
filled: false , counterText: " " ,
enabledBorder: UnderlineInputBorder ( borderSide: BorderSide ( color: inkColor , width: 3 ) ) ,
focusedBorder: UnderlineInputBorder ( borderSide: BorderSide ( color: Colors . red . shade200 , width: 5 ) ) ,
suffixIcon: IconButton (
icon: Icon ( _obscurePassword ? Icons . visibility : Icons . visibility_off , color: inkColor . withOpacity ( 0.6 ) ) ,
onPressed: ( ) { setStateDialog ( ( ) { _obscurePassword = ! _obscurePassword ; } ) ; } ,
) ,
) ,
) ,
const SizedBox ( height: 15 ) ,
// MESSAGGIO DI ERRORE
if ( _errorMessage . isNotEmpty )
Padding (
padding: const EdgeInsets . only ( bottom: 10 ) ,
child: Text ( _errorMessage , style: getSharedTextStyle ( themeType , const TextStyle ( color: Colors . red , fontSize: 14 , fontWeight: FontWeight . bold ) ) , textAlign: TextAlign . center ) ,
) ,
Text ( " 💡 Nota: Non serve una vera email. Usa una password facile da ricordare! " , style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor . withOpacity ( 0.6 ) , fontSize: 11 , height: 1.3 ) ) , textAlign: TextAlign . center ) ,
const SizedBox ( height: 15 ) ,
isLoadingAuth
? CircularProgressIndicator ( color: inkColor )
: Row (
children: [
Expanded (
child: GestureDetector (
onTap: ( ) = > handleAuth ( true ) ,
child: CustomPaint (
painter: DoodleBackgroundPainter ( fillColor: Colors . blue . shade200 , strokeColor: inkColor , seed: 101 ) ,
child: Container ( height: 45 , alignment: Alignment . center , child: Text ( " ACCEDI " , style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor , fontSize: 14 , fontWeight: FontWeight . bold , letterSpacing: 1.5 ) ) ) ) ,
) ,
) ,
) ,
const SizedBox ( width: 10 ) ,
Expanded (
child: GestureDetector (
onTap: ( ) = > handleAuth ( false ) ,
child: CustomPaint (
painter: DoodleBackgroundPainter ( fillColor: Colors . green . shade200 , strokeColor: inkColor , seed: 102 ) ,
child: Container ( height: 45 , alignment: Alignment . center , child: Text ( " REGISTRATI " , style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor , fontSize: 14 , fontWeight: FontWeight . bold , letterSpacing: 1.5 ) ) ) ) ,
) ,
) ,
) ,
] ,
) ,
] ,
2026-02-27 23:35:54 +01:00
) ,
) ,
2026-03-15 03:32:09 +01:00
) ,
)
: Container (
decoration: BoxDecoration ( color: theme . background , borderRadius: BorderRadius . circular ( 25 ) , border: Border . all ( color: theme . playerBlue . withOpacity ( 0.5 ) , width: 2 ) , boxShadow: [ BoxShadow ( color: theme . playerBlue . withOpacity ( 0.3 ) , blurRadius: 20 , spreadRadius: 5 ) ] ) ,
child: SingleChildScrollView (
physics: const BouncingScrollPhysics ( ) ,
child: Padding (
padding: const EdgeInsets . symmetric ( vertical: 20.0 , horizontal: 20.0 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
Text ( loc . welcomeTitle , style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text , fontWeight: FontWeight . w900 , fontSize: 20 , letterSpacing: 1.5 ) ) , textAlign: TextAlign . center ) ,
const SizedBox ( height: 10 ) ,
Text ( ' Scegli Nome e Password. \n Ti serviranno per recuperare gli XP! ' , style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.8 ) , fontSize: 13 ) ) , textAlign: TextAlign . center ) ,
const SizedBox ( height: 15 ) ,
TextField (
controller: nameController , textCapitalization: TextCapitalization . characters , textAlign: TextAlign . center , maxLength: 8 ,
style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text , fontSize: 24 , fontWeight: FontWeight . bold , letterSpacing: 4 ) ) ,
decoration: InputDecoration (
hintText: loc . nameHint ,
hintStyle: getSharedTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.3 ) , letterSpacing: 4 ) ) ,
filled: true , fillColor: theme . text . withOpacity ( 0.05 ) , counterText: " " ,
enabledBorder: OutlineInputBorder ( borderSide: BorderSide ( color: theme . gridLine . withOpacity ( 0.5 ) , width: 2 ) , borderRadius: BorderRadius . circular ( 15 ) ) ,
focusedBorder: OutlineInputBorder ( borderSide: BorderSide ( color: theme . playerBlue , width: 3 ) , borderRadius: BorderRadius . circular ( 15 ) )
) ,
) ,
const SizedBox ( height: 10 ) ,
TextField (
controller: passController , obscureText: _obscurePassword , textAlign: TextAlign . center , maxLength: 20 ,
style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text , fontSize: 20 , fontWeight: FontWeight . bold , letterSpacing: 8 ) ) ,
decoration: InputDecoration (
hintText: " PASSWORD " ,
hintStyle: getSharedTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.3 ) , letterSpacing: 4 ) ) ,
filled: true , fillColor: theme . text . withOpacity ( 0.05 ) , counterText: " " ,
enabledBorder: OutlineInputBorder ( borderSide: BorderSide ( color: theme . gridLine . withOpacity ( 0.5 ) , width: 2 ) , borderRadius: BorderRadius . circular ( 15 ) ) ,
focusedBorder: OutlineInputBorder ( borderSide: BorderSide ( color: theme . playerBlue , width: 3 ) , borderRadius: BorderRadius . circular ( 15 ) ) ,
suffixIcon: IconButton (
icon: Icon ( _obscurePassword ? Icons . visibility : Icons . visibility_off , color: theme . text . withOpacity ( 0.6 ) ) ,
onPressed: ( ) { setStateDialog ( ( ) { _obscurePassword = ! _obscurePassword ; } ) ; } ,
) ,
) ,
) ,
const SizedBox ( height: 15 ) ,
// MESSAGGIO DI ERRORE
if ( _errorMessage . isNotEmpty )
Padding (
padding: const EdgeInsets . only ( bottom: 10 ) ,
child: Text ( _errorMessage , style: getSharedTextStyle ( themeType , const TextStyle ( color: Colors . redAccent , fontSize: 14 , fontWeight: FontWeight . bold ) ) , textAlign: TextAlign . center ) ,
) ,
Text ( " 💡 Nota: Non serve una vera email. Usa una password facile da ricordare! " , style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.6 ) , fontSize: 11 , height: 1.3 ) ) , textAlign: TextAlign . center ) ,
const SizedBox ( height: 20 ) ,
isLoadingAuth
? CircularProgressIndicator ( color: theme . playerBlue )
: Row (
children: [
Expanded (
child: SizedBox (
height: 45 ,
child: ElevatedButton (
style: ElevatedButton . styleFrom ( backgroundColor: theme . text . withOpacity ( 0.1 ) , foregroundColor: theme . text , shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 15 ) ) , side: BorderSide ( color: theme . playerBlue , width: 1.5 ) ) ,
onPressed: ( ) = > handleAuth ( true ) ,
child: Text ( " ACCEDI " , style: getSharedTextStyle ( themeType , const TextStyle ( fontSize: 13 , fontWeight: FontWeight . bold , letterSpacing: 1.0 ) ) ) ,
) ,
) ,
) ,
const SizedBox ( width: 10 ) ,
Expanded (
child: SizedBox (
height: 45 ,
child: ElevatedButton (
style: ElevatedButton . styleFrom ( backgroundColor: theme . playerBlue , foregroundColor: Colors . white , shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 15 ) ) ) ,
onPressed: ( ) = > handleAuth ( false ) ,
child: Text ( " REGISTRATI " , style: getSharedTextStyle ( themeType , const TextStyle ( fontSize: 13 , fontWeight: FontWeight . bold , letterSpacing: 1.0 ) ) ) ,
) ,
) ,
) ,
] ,
) ,
] ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-15 03:32:09 +01:00
) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-15 03:32:09 +01:00
) ;
2026-02-27 23:35:54 +01:00
2026-03-15 03:32:09 +01:00
if ( themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . music ) dialogContent = AnimatedCyberBorder ( child: dialogContent ) ;
return Dialog ( backgroundColor: Colors . transparent , insetPadding: const EdgeInsets . all ( 20 ) , child: dialogContent ) ;
} ,
) ;
2026-02-27 23:35:54 +01:00
} ,
) ;
}
void _showMatchSetupDialog ( bool isVsCPU ) {
2026-03-14 00:00:01 +01:00
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-03-05 15:00:00 +01:00
final loc = AppLocalizations . of ( context ) ! ;
2026-02-27 23:35:54 +01:00
showDialog (
2026-03-14 00:00:01 +01:00
context: context , barrierColor: Colors . black . withOpacity ( 0.8 ) ,
2026-02-27 23:35:54 +01:00
builder: ( ctx ) {
final themeManager = ctx . watch < ThemeManager > ( ) ;
2026-03-14 00:00:01 +01:00
final theme = themeManager . currentColors ; final themeType = themeManager . currentThemeType ;
2026-02-27 23:35:54 +01:00
Color inkColor = const Color ( 0xFF111122 ) ;
return StatefulBuilder (
builder: ( context , setStateDialog ) {
Widget dialogContent = themeType = = AppThemeType . doodle
? Transform . rotate (
angle: 0.015 ,
child: CustomPaint (
2026-03-14 00:00:01 +01:00
painter: DoodleBackgroundPainter ( fillColor: Colors . white . withOpacity ( 0.95 ) , strokeColor: inkColor , seed: 200 ) ,
2026-02-27 23:35:54 +01:00
child: SingleChildScrollView (
physics: const BouncingScrollPhysics ( ) ,
child: Padding (
padding: const EdgeInsets . all ( 25.0 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
2026-03-14 00:00:01 +01:00
Row ( children: [ SizedBox ( width: 40 , child: IconButton ( padding: EdgeInsets . zero , alignment: Alignment . centerLeft , icon: Icon ( Icons . arrow_back_ios_new , color: inkColor , size: 26 ) , onPressed: ( ) = > Navigator . pop ( ctx ) ) ) , Expanded ( child: Text ( isVsCPU ? loc . cpuTitle : loc . localTitle , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 26 , fontWeight: FontWeight . w900 , color: inkColor , letterSpacing: 2 ) ) ) ) , const SizedBox ( width: 40 ) ] ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 25 ) ,
2026-03-13 22:00:00 +01:00
if ( isVsCPU ) . . . [
2026-03-14 00:00:01 +01:00
Icon ( Icons . smart_toy , size: 50 , color: inkColor . withOpacity ( 0.6 ) ) , const SizedBox ( height: 10 ) ,
Text ( " MODALITÀ CAMPAGNA " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 16 , fontWeight: FontWeight . w900 , color: inkColor ) ) ) , const SizedBox ( height: 10 ) ,
Text ( " Livello CPU: ${ StorageService . instance . cpuLevel } \n Forma e dimensioni si adatteranno alla tua bravura! " , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 13 , color: inkColor . withOpacity ( 0.8 ) , height: 1.4 ) ) ) , const SizedBox ( height: 25 ) ,
Divider ( color: inkColor . withOpacity ( 0.3 ) , thickness: 2.5 ) , const SizedBox ( height: 20 ) ,
2026-03-13 22:00:00 +01:00
] else . . . [
2026-03-14 00:00:01 +01:00
Text ( " FORMA ARENA " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 14 , fontWeight: FontWeight . w900 , color: inkColor . withOpacity ( 0.6 ) , letterSpacing: 1.5 ) ) ) , const SizedBox ( height: 15 ) ,
2026-03-13 22:00:00 +01:00
Wrap (
spacing: 12 , runSpacing: 12 , alignment: WrapAlignment . center ,
children: [
2026-03-15 02:00:01 +01:00
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 ) ) ,
2026-03-13 22:00:00 +01:00
] ,
) ,
2026-03-14 00:00:01 +01:00
const SizedBox ( height: 25 ) , Divider ( color: inkColor . withOpacity ( 0.3 ) , thickness: 2.5 ) , const SizedBox ( height: 20 ) ,
2026-02-27 23:35:54 +01:00
2026-03-14 00:00:01 +01:00
Text ( " GRANDEZZA " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 14 , fontWeight: FontWeight . w900 , color: inkColor . withOpacity ( 0.6 ) , letterSpacing: 1.5 ) ) ) , const SizedBox ( height: 15 ) ,
2026-03-13 22:00:00 +01:00
Row (
mainAxisAlignment: MainAxisAlignment . spaceEvenly ,
children: [
2026-03-15 02:00:01 +01:00
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 ) ) ,
2026-03-13 22:00:00 +01:00
] ,
) ,
2026-03-14 00:00:01 +01:00
const SizedBox ( height: 25 ) , Divider ( color: inkColor . withOpacity ( 0.3 ) , thickness: 2.5 ) , const SizedBox ( height: 20 ) ,
2026-03-13 22:00:00 +01:00
] ,
2026-02-27 23:35:54 +01:00
2026-03-14 00:00:01 +01:00
Text ( " TEMPO " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 14 , fontWeight: FontWeight . w900 , color: inkColor . withOpacity ( 0.6 ) , letterSpacing: 1.5 ) ) ) , const SizedBox ( height: 10 ) ,
2026-03-15 02:00:01 +01:00
NeonTimeSwitch ( isTimeMode: localTimeMode , theme: theme , themeType: themeType , onTap: ( ) = > setStateDialog ( ( ) = > localTimeMode = ! localTimeMode ) ) , const SizedBox ( height: 35 ) ,
2026-02-27 23:35:54 +01:00
Transform . rotate (
angle: - 0.02 ,
child: GestureDetector (
2026-03-14 00:00:01 +01:00
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 ( loc . startGame , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 22 , fontWeight: FontWeight . w900 , letterSpacing: 3.0 , color: inkColor ) ) ) ) ) ,
2026-02-27 23:35:54 +01:00
) ,
)
] ,
) ,
) ,
) ,
) ,
)
: Container (
decoration: BoxDecoration (
gradient: LinearGradient ( begin: Alignment . topLeft , end: Alignment . bottomRight , colors: [ theme . background . withOpacity ( 0.95 ) , theme . background . withOpacity ( 0.8 ) ] ) ,
2026-03-14 00:00:01 +01:00
borderRadius: BorderRadius . circular ( 25 ) , border: themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . arcade | | themeType = = AppThemeType . music ? null : Border . all ( color: Colors . white . withOpacity ( 0.15 ) , width: 1.5 ) ,
2026-03-13 23:00:01 +01:00
boxShadow: themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . arcade | | themeType = = AppThemeType . music ? [ ] : [ 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: [
2026-03-14 00:00:01 +01:00
Row ( children: [ SizedBox ( width: 40 , child: IconButton ( padding: EdgeInsets . zero , alignment: Alignment . centerLeft , icon: Icon ( Icons . arrow_back_ios_new , color: theme . text , size: 26 ) , onPressed: ( ) = > Navigator . pop ( ctx ) ) ) , Expanded ( child: Text ( isVsCPU ? loc . cpuTitle : loc . localTitle , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 24 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 2 ) ) ) ) , const SizedBox ( width: 40 ) ] ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 20 ) ,
2026-03-13 22:00:00 +01:00
if ( isVsCPU ) . . . [
2026-03-14 00:00:01 +01:00
Icon ( Icons . smart_toy , size: 50 , color: theme . playerBlue ) , const SizedBox ( height: 10 ) ,
Text ( " MODALITÀ CAMPAGNA " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 16 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 1.5 ) ) ) , const SizedBox ( height: 10 ) ,
Text ( " Livello CPU: ${ StorageService . instance . cpuLevel } \n Forma e dimensioni si adatteranno alla tua bravura! " , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 13 , color: theme . text . withOpacity ( 0.7 ) , height: 1.4 ) ) ) , const SizedBox ( height: 20 ) ,
Divider ( color: Colors . white . withOpacity ( 0.05 ) , thickness: 2 ) , const SizedBox ( height: 20 ) ,
2026-03-13 22:00:00 +01:00
] else . . . [
2026-03-14 00:00:01 +01:00
Text ( " FORMA ARENA " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 12 , fontWeight: FontWeight . w900 , color: theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) , const SizedBox ( height: 10 ) ,
2026-03-13 22:00:00 +01:00
Wrap (
spacing: 10 , runSpacing: 10 , alignment: WrapAlignment . center ,
children: [
2026-03-15 02:00:01 +01:00
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 ) ) ,
2026-03-13 22:00:00 +01:00
] ,
) ,
2026-03-14 00:00:01 +01:00
const SizedBox ( height: 20 ) , Divider ( color: Colors . white . withOpacity ( 0.05 ) , thickness: 2 ) , const SizedBox ( height: 20 ) ,
2026-02-27 23:35:54 +01:00
2026-03-14 00:00:01 +01:00
Text ( " GRANDEZZA " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 12 , fontWeight: FontWeight . w900 , color: theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) , const SizedBox ( height: 10 ) ,
2026-03-13 22:00:00 +01:00
Row (
mainAxisAlignment: MainAxisAlignment . spaceEvenly ,
children: [
2026-03-15 02:00:01 +01:00
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 ) ) ,
2026-03-13 22:00:00 +01:00
] ,
) ,
2026-03-14 00:00:01 +01:00
const SizedBox ( height: 20 ) , Divider ( color: Colors . white . withOpacity ( 0.05 ) , thickness: 2 ) , const SizedBox ( height: 20 ) ,
2026-03-13 22:00:00 +01:00
] ,
2026-02-27 23:35:54 +01:00
2026-03-14 00:00:01 +01:00
Text ( " TEMPO " , style: getSharedTextStyle ( themeType , TextStyle ( fontSize: 12 , fontWeight: FontWeight . w900 , color: theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) , const SizedBox ( height: 10 ) ,
2026-03-15 02:00:01 +01:00
NeonTimeSwitch ( isTimeMode: localTimeMode , theme: theme , themeType: themeType , onTap: ( ) = > setStateDialog ( ( ) = > localTimeMode = ! localTimeMode ) ) , const SizedBox ( height: 30 ) ,
2026-02-27 23:35:54 +01:00
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 ) ) ) ,
2026-03-14 00:00:01 +01:00
onPressed: ( ) { Navigator . pop ( ctx ) ; context . read < GameController > ( ) . startNewGame ( localRadius , vsCPU: isVsCPU , shape: localShape , timeMode: localTimeMode ) ; Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const GameScreen ( ) ) ) ; } ,
2026-03-05 15:00:00 +01:00
child: Text ( loc . startGame , style: const TextStyle ( fontSize: 18 , fontWeight: FontWeight . w900 , letterSpacing: 2 ) ) ,
2026-02-27 23:35:54 +01:00
) ,
)
] ,
) ,
) ,
) ,
) ;
2026-03-13 23:00:01 +01:00
if ( themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . music ) {
2026-03-14 00:00:01 +01:00
dialogContent = AnimatedCyberBorder ( child: dialogContent ) ;
2026-02-27 23:35:54 +01:00
}
return Dialog ( backgroundColor: Colors . transparent , insetPadding: const EdgeInsets . symmetric ( horizontal: 15 , vertical: 20 ) , child: dialogContent ) ;
} ,
) ;
}
) ;
}
2026-03-01 20:59:06 +01:00
2026-03-14 00:00:01 +01:00
// ===========================================================================
// INTERFACCIA PRINCIPALE
// ===========================================================================
2026-02-27 23:35:54 +01:00
Widget _buildCyberCard ( Widget card , AppThemeType themeType ) {
2026-03-14 00:00:01 +01:00
if ( themeType = = AppThemeType . cyberpunk ) return AnimatedCyberBorder ( child: card ) ;
return card ;
2026-02-27 23:35:54 +01:00
}
@ 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 ) ;
2026-03-05 15:00:00 +01:00
final loc = AppLocalizations . of ( context ) ! ;
2026-02-27 23:35:54 +01:00
String ? bgImage ;
if ( themeType = = AppThemeType . wood ) bgImage = ' assets/images/wood_bg.jpg ' ;
2026-03-14 19:00:00 +01:00
if ( themeType = = AppThemeType . doodle ) bgImage = ' assets/images/doodle_bg.jpg ' ;
2026-02-27 23:35:54 +01:00
if ( themeType = = AppThemeType . cyberpunk ) bgImage = ' assets/images/cyber_bg.jpg ' ;
2026-03-13 23:00:01 +01:00
if ( themeType = = AppThemeType . music ) bgImage = ' assets/images/music_bg.jpg ' ;
2026-03-15 02:00:01 +01:00
if ( themeType = = AppThemeType . arcade ) bgImage = ' assets/images/arcade.jpg ' ;
2026-03-15 03:00:01 +01:00
if ( themeType = = AppThemeType . grimorio ) bgImage = ' assets/images/grimorio.jpg ' ; // Aggiunto Grimorio
2026-02-27 23:35:54 +01:00
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 (
2026-03-14 00:00:01 +01:00
constraints: BoxConstraints ( minHeight: constraints . maxHeight ) ,
2026-03-01 20:59:06 +01:00
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-14 19:00:00 +01:00
// --- NUOVO HEADER FISSATO ---
2026-03-01 20:59:06 +01:00
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2026-03-14 19:00:00 +01:00
crossAxisAlignment: CrossAxisAlignment . start ,
2026-03-01 20:59:06 +01:00
children: [
2026-03-15 02:00:01 +01:00
// BLOCCO SINISTRO: AVATAR E NOME
2026-03-15 03:00:01 +01:00
Expanded (
2026-03-01 20:59:06 +01:00
child: Row (
2026-03-14 19:00:00 +01:00
crossAxisAlignment: CrossAxisAlignment . start ,
2026-03-01 20:59:06 +01:00
children: [
2026-03-14 19:00:00 +01:00
GestureDetector (
behavior: HitTestBehavior . opaque ,
onTap: _showNameDialog ,
child: 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 ) ) ,
) ,
2026-03-01 20:59:06 +01:00
) ,
2026-03-14 19:00:00 +01:00
] ,
) ,
2026-03-01 20:59:06 +01:00
) ,
) ,
const SizedBox ( width: 12 ) ,
2026-03-14 19:00:00 +01:00
GestureDetector (
behavior: HitTestBehavior . opaque ,
onTap: _showNameDialog ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text ( playerName , style: getSharedTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? inkColor : theme . text , fontSize: 24 , fontWeight: FontWeight . w900 , letterSpacing: 1.5 , shadows: themeType = = AppThemeType . doodle ? [ ] : [ Shadow ( color: Colors . black . withOpacity ( 0.5 ) , offset: const Offset ( 1 , 2 ) , blurRadius: 2 ) ] ) ) ) ,
Text ( " LIV. $ level " , style: getSharedTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? inkColor . withOpacity ( 0.8 ) : theme . playerBlue , fontSize: 14 , fontWeight: FontWeight . bold , letterSpacing: 1 ) ) ) ,
] ,
) ,
2026-03-01 20:59:06 +01:00
) ,
] ,
) ,
) ,
2026-03-15 02:00:01 +01:00
// BLOCCO DESTRO: STATISTICHE E AUDIO
2026-03-14 19:00:00 +01:00
Column (
crossAxisAlignment: CrossAxisAlignment . end ,
children: [
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 (
mainAxisSize: MainAxisSize . min ,
children: [
Icon ( Icons . emoji_events , color: inkColor , size: 20 ) , const SizedBox ( width: 6 ) ,
Text ( " $ wins " , style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor , fontWeight: FontWeight . w900 ) ) ) , const SizedBox ( width: 12 ) ,
Icon ( Icons . sentiment_very_dissatisfied , color: inkColor , size: 20 ) , const SizedBox ( width: 6 ) ,
Text ( " $ losses " , style: getSharedTextStyle ( 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 ) ] ,
) ,
2026-03-01 20:59:06 +01:00
child: Row (
2026-03-14 19:00:00 +01:00
mainAxisSize: MainAxisSize . min ,
2026-03-01 20:59:06 +01:00
children: [
2026-03-14 19:00:00 +01:00
Icon ( themeType = = AppThemeType . music ? FontAwesomeIcons . microphone : Icons . emoji_events , color: Colors . amber . shade600 , size: 16 ) , const SizedBox ( width: 6 ) ,
Text ( " $ wins " , style: getSharedTextStyle ( themeType , const TextStyle ( color: Colors . white , fontWeight: FontWeight . w900 ) ) ) , const SizedBox ( width: 12 ) ,
Icon ( themeType = = AppThemeType . music ? FontAwesomeIcons . compactDisc : Icons . sentiment_very_dissatisfied , color: theme . playerRed . withOpacity ( 0.8 ) , size: 16 ) , const SizedBox ( width: 6 ) ,
Text ( " $ losses " , style: getSharedTextStyle ( themeType , const TextStyle ( color: Colors . white , fontWeight: FontWeight . w900 ) ) ) ,
2026-03-01 20:59:06 +01:00
] ,
) ,
) ,
) ,
2026-03-14 19:00:00 +01:00
const SizedBox ( height: 12 ) ,
// PULSANTE AUDIO FISSATO A DESTRA
AnimatedBuilder (
animation: AudioService . instance ,
builder: ( context , child ) {
bool isMuted = AudioService . instance . isMuted ;
return GestureDetector (
behavior: HitTestBehavior . opaque ,
onTap: ( ) {
AudioService . instance . toggleMute ( ) ;
} ,
child: themeType = = AppThemeType . doodle
? CustomPaint (
painter: DoodleBackgroundPainter ( fillColor: Colors . white , strokeColor: inkColor , seed: 99 , isCircle: true ) ,
child: SizedBox (
width: 45 , height: 45 ,
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Icon ( isMuted ? Icons . volume_off : Icons . volume_up , color: inkColor , size: 18 ) ,
Text ( isMuted ? " OFF " : " ON " , style: getSharedTextStyle ( themeType , TextStyle ( color: inkColor , fontSize: 10 , fontWeight: FontWeight . w900 ) ) ) ,
] ,
)
) ,
)
: Container (
width: 45 , height: 45 ,
decoration: BoxDecoration (
color: theme . background . withOpacity ( 0.8 ) ,
shape: BoxShape . circle ,
border: Border . all ( color: theme . gridLine . withOpacity ( 0.5 ) , width: 1.5 ) ,
boxShadow: [ BoxShadow ( color: Colors . black . withOpacity ( 0.3 ) , blurRadius: 5 , offset: const Offset ( 0 , 4 ) ) ] ,
) ,
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Icon ( isMuted ? Icons . volume_off : Icons . volume_up , color: theme . playerBlue , size: 16 ) ,
Text ( isMuted ? " OFF " : " ON " , style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text , fontSize: 9 , fontWeight: FontWeight . bold ) ) ) ,
] ,
) ,
) ,
) ;
}
2026-03-01 20:59:06 +01:00
) ,
2026-03-14 19:00:00 +01:00
] ,
2026-03-01 20:59:06 +01:00
)
] ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-14 19:00:00 +01:00
// --- FINE HEADER FISSATO ---
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 ,
child: GestureDetector (
onTap: ( ) {
_debugTapCount + + ;
2026-03-12 15:00:00 +01:00
if ( _debugTapCount = = 5 ) {
2026-03-01 20:59:06 +01:00
StorageService . instance . addXP ( 2000 ) ;
setState ( ( ) { } ) ;
ScaffoldMessenger . of ( context ) . showSnackBar (
2026-03-14 00:00:01 +01:00
SnackBar ( content: Text ( " 🛠 DEBUG MODE: +20 Livelli! " , style: getSharedTextStyle ( themeType , const TextStyle ( color: Colors . white , fontWeight: FontWeight . bold ) ) ) , backgroundColor: Colors . purpleAccent , behavior: SnackBarBehavior . floating , shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 15 ) ) )
2026-03-01 20:59:06 +01:00
) ;
2026-03-12 15:00:00 +01:00
} else if ( _debugTapCount > = 7 ) {
2026-03-13 23:00:01 +01:00
_debugTapCount = 0 ;
2026-03-12 15:00:00 +01:00
Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const AdminScreen ( ) ) ) ;
2026-03-01 20:59:06 +01:00
}
} ,
child: FittedBox (
fit: BoxFit . scaleDown ,
child: Text (
2026-03-05 15:00:00 +01:00
loc . appTitle . toUpperCase ( ) ,
2026-03-14 00:00:01 +01:00
style: getSharedTextStyle ( themeType , TextStyle (
2026-03-01 20:59:06 +01:00
fontSize: 65 ,
fontWeight: FontWeight . w900 ,
color: themeType = = AppThemeType . doodle ? inkColor : theme . text ,
letterSpacing: 10 ,
2026-03-13 23:00:01 +01:00
shadows: themeType = = AppThemeType . doodle
2026-03-14 00:00:01 +01:00
? [ const Shadow ( color: Colors . white , offset: Offset ( - 2.5 , - 2.5 ) , blurRadius: 0 ) , Shadow ( color: Colors . black . withOpacity ( 0.25 ) , offset: const Offset ( 2.5 , 2.5 ) , blurRadius: 1 ) ]
: themeType = = AppThemeType . arcade | | themeType = = AppThemeType . music ? [ ] : [ BoxShadow ( color: Colors . black . withOpacity ( 0.6 ) , offset: const Offset ( 3 , 6 ) , blurRadius: 8 ) , BoxShadow ( color: theme . playerBlue . withOpacity ( 0.4 ) , offset: const Offset ( 0 , 0 ) , blurRadius: 20 ) ]
2026-03-01 20:59:06 +01:00
) )
) ,
) ,
) ,
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-14 00:00:01 +01:00
// --- MENU IN BASE AL TEMA ---
2026-03-13 23:00:01 +01:00
if ( themeType = = AppThemeType . music ) . . . [
2026-03-14 00:00:01 +01:00
MusicCassetteCard ( title: loc . onlineTitle , subtitle: loc . onlineSub , neonColor: Colors . blueAccent , angle: - 0.04 , leftIcon: FontAwesomeIcons . sliders , rightIcon: FontAwesomeIcons . globe , themeType: themeType , onTap: ( ) { Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const LobbyScreen ( ) ) ) ; } ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( height: 12 ) ,
2026-03-14 00:00:01 +01:00
MusicCassetteCard ( title: loc . cpuTitle , subtitle: loc . cpuSub , neonColor: Colors . purpleAccent , angle: 0.03 , leftIcon: FontAwesomeIcons . desktop , rightIcon: FontAwesomeIcons . music , themeType: themeType , onTap: ( ) = > _showMatchSetupDialog ( true ) ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( height: 12 ) ,
2026-03-14 00:00:01 +01:00
MusicCassetteCard ( title: loc . localTitle , subtitle: loc . localSub , neonColor: Colors . deepPurpleAccent , angle: - 0.02 , leftIcon: FontAwesomeIcons . headphones , rightIcon: FontAwesomeIcons . headphones , themeType: themeType , onTap: ( ) = > _showMatchSetupDialog ( false ) ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( height: 30 ) ,
Row (
2026-03-14 00:00:01 +01:00
mainAxisAlignment: MainAxisAlignment . spaceEvenly , crossAxisAlignment: CrossAxisAlignment . start ,
2026-03-13 23:00:01 +01:00
children: [
2026-03-14 00:00:01 +01:00
Expanded ( child: MusicKnobCard ( title: loc . leaderboardTitle , icon: FontAwesomeIcons . compactDisc , iconColor: Colors . amber , themeType: themeType , onTap: ( ) = > showDialog ( context: context , builder: ( ctx ) = > const LeaderboardDialog ( ) ) ) ) ,
Expanded ( child: MusicKnobCard ( title: loc . questsTitle , icon: FontAwesomeIcons . microphoneLines , themeType: themeType , onTap: ( ) = > showDialog ( context: context , builder: ( ctx ) = > const QuestsDialog ( ) ) ) ) ,
Expanded ( child: MusicKnobCard ( title: loc . themesTitle , icon: FontAwesomeIcons . palette , themeType: themeType , onTap: ( ) = > Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const SettingsScreen ( ) ) ) ) ) ,
Expanded ( child: MusicKnobCard ( title: loc . tutorialTitle , icon: FontAwesomeIcons . bookOpen , themeType: themeType , onTap: ( ) = > showDialog ( context: context , builder: ( ctx ) = > const TutorialDialog ( ) ) ) ) ,
2026-03-13 23:00:01 +01:00
] ,
) ,
] else . . . [
Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
2026-03-14 00:00:01 +01:00
_buildCyberCard ( FeatureCard ( title: loc . onlineTitle , subtitle: loc . onlineSub , icon: Icons . public , color: Colors . lightBlue . shade200 , theme: theme , themeType: themeType , isFeatured: true , onTap: ( ) { Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const LobbyScreen ( ) ) ) ; } ) , themeType ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( height: 12 ) ,
2026-03-14 00:00:01 +01:00
_buildCyberCard ( FeatureCard ( title: loc . cpuTitle , subtitle: loc . cpuSub , icon: Icons . smart_toy , color: Colors . purple . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > _showMatchSetupDialog ( true ) ) , themeType ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( height: 12 ) ,
2026-03-14 00:00:01 +01:00
_buildCyberCard ( FeatureCard ( title: loc . localTitle , subtitle: loc . localSub , icon: Icons . people_alt , color: Colors . red . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > _showMatchSetupDialog ( false ) ) , themeType ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( height: 12 ) ,
2026-03-01 20:59:06 +01:00
2026-03-13 23:00:01 +01:00
Row (
children: [
2026-03-14 00:00:01 +01:00
Expanded ( child: _buildCyberCard ( FeatureCard ( title: loc . leaderboardTitle , subtitle: " Top 50 Globale " , icon: Icons . leaderboard , color: Colors . amber . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > showDialog ( context: context , builder: ( ctx ) = > const LeaderboardDialog ( ) ) , compact: true ) , themeType ) ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( width: 12 ) ,
2026-03-14 00:00:01 +01:00
Expanded ( child: _buildCyberCard ( FeatureCard ( title: loc . questsTitle , subtitle: " Missioni " , icon: Icons . assignment_turned_in , color: Colors . green . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > showDialog ( context: context , builder: ( ctx ) = > const QuestsDialog ( ) ) , compact: true ) , themeType ) ) ,
2026-03-13 23:00:01 +01:00
] ,
) ,
2026-02-27 23:35:54 +01:00
2026-03-13 23:00:01 +01:00
const SizedBox ( height: 12 ) ,
2026-03-01 20:59:06 +01:00
2026-03-13 23:00:01 +01:00
Row (
children: [
2026-03-14 00:00:01 +01:00
Expanded ( child: _buildCyberCard ( FeatureCard ( title: loc . themesTitle , subtitle: " Personalizza " , icon: Icons . palette , color: Colors . teal . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > Navigator . push ( context , MaterialPageRoute ( builder: ( _ ) = > const SettingsScreen ( ) ) ) , compact: true ) , themeType ) ) ,
2026-03-13 23:00:01 +01:00
const SizedBox ( width: 12 ) ,
2026-03-14 00:00:01 +01:00
Expanded ( child: _buildCyberCard ( FeatureCard ( title: loc . tutorialTitle , subtitle: " Come giocare " , icon: Icons . school , color: Colors . indigo . shade200 , theme: theme , themeType: themeType , onTap: ( ) = > showDialog ( context: context , builder: ( ctx ) = > const TutorialDialog ( ) ) , compact: true ) , themeType ) ) ,
2026-03-13 23:00:01 +01:00
] ,
) ,
] ,
) ,
] ,
2026-03-01 20:59:06 +01:00
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 (
2026-03-15 03:32:09 +01:00
backgroundColor: bgImage ! = null ? Colors . transparent : theme . background ,
extendBodyBehindAppBar: true ,
appBar: AppBar ( backgroundColor: Colors . transparent , elevation: 0 , iconTheme: IconThemeData ( color: theme . text ) ) ,
2026-02-27 23:35:54 +01:00
body: Stack (
children: [
2026-03-14 19:00:00 +01:00
// 1. Sfondo base a tinta unita
2026-03-13 23:00:01 +01:00
Container ( color: themeType = = AppThemeType . doodle ? Colors . white : theme . background ) ,
2026-03-14 00:00:01 +01:00
2026-03-14 19:00:00 +01:00
// 2. Immagine di Sfondo per tutti i temi che la supportano
2026-02-27 23:35:54 +01:00
if ( bgImage ! = null )
2026-03-14 19:00:00 +01:00
Positioned . fill (
child: Image . asset (
bgImage ,
fit: BoxFit . cover ,
alignment: Alignment . center ,
) ,
) ,
2026-03-14 00:00:01 +01:00
2026-03-14 19:00:00 +01:00
// 3. Griglia a righe incrociate per il doodle
if ( themeType = = AppThemeType . doodle )
Positioned . fill (
child: CustomPaint (
painter: FullScreenGridPainter ( Colors . blue . withOpacity ( 0.15 ) ) ,
) ,
) ,
2026-03-15 03:00:01 +01:00
// 4. Patina scura (Cyberpunk, Music, Arcade e Grimorio)
if ( bgImage ! = null & & ( themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . music | | themeType = = AppThemeType . arcade | | themeType = = AppThemeType . grimorio ) )
2026-03-14 19:00:00 +01:00
Positioned . fill (
child: Container (
decoration: BoxDecoration (
gradient: LinearGradient (
begin: Alignment . topCenter , end: Alignment . bottomCenter ,
colors: [ Colors . black . withOpacity ( 0.4 ) , Colors . black . withOpacity ( 0.8 ) ]
)
) ,
) ,
) ,
2026-03-13 23:00:01 +01:00
2026-03-14 19:00:00 +01:00
// 5. Cavi musicali (Tema Musica)
2026-03-13 23:00:01 +01:00
if ( themeType = = AppThemeType . music )
2026-03-14 19:00:00 +01:00
Positioned . fill (
child: IgnorePointer (
child: CustomPaint (
painter: AudioCablesPainter ( ) ,
) ,
) ,
) ,
2026-03-13 23:00:01 +01:00
2026-03-14 19:00:00 +01:00
// 6. UI
2026-02-27 23:35:54 +01:00
Positioned . fill ( child: uiContent ) ,
] ,
) ,
) ;
}
}