2026-03-11 22:00:01 +01:00
// ===========================================================================
// FILE: lib/ui/multiplayer/lobby_screen.dart
// ===========================================================================
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 ' ;
2026-03-11 23:00:01 +01:00
import ' package:firebase_auth/firebase_auth.dart ' ;
2026-03-21 11:00:05 +01:00
import ' package:tetraq/l10n/app_localizations.dart ' ;
2026-03-15 13:00:00 +01:00
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 ' ;
2026-03-15 17:00:01 +01:00
import ' ../../core/app_colors.dart ' ;
2026-02-27 23:35:54 +01:00
import ' ../../services/multiplayer_service.dart ' ;
import ' ../../services/storage_service.dart ' ;
import ' ../game/game_screen.dart ' ;
2026-03-20 22:00:01 +01:00
import ' ../../widgets/cyber_border.dart ' ;
2026-03-21 11:00:05 +01:00
import ' ../../widgets/painters.dart ' ; // <--- ECCO L'IMPORT MANCANTE!
2026-03-15 17:00:01 +01:00
import ' lobby_widgets.dart ' ;
2026-03-15 13:00:00 +01:00
2026-02-27 23:35:54 +01:00
class LobbyScreen extends StatefulWidget {
final String ? initialRoomCode ;
const LobbyScreen ( { super . key , this . initialRoomCode } ) ;
@ override
State < LobbyScreen > createState ( ) = > _LobbyScreenState ( ) ;
}
2026-03-11 23:00:01 +01:00
class _LobbyScreenState extends State < LobbyScreen > with WidgetsBindingObserver {
2026-02-27 23:35:54 +01:00
final MultiplayerService _multiplayerService = MultiplayerService ( ) ;
late TextEditingController _codeController ;
bool _isLoading = false ;
String ? _myRoomCode ;
String _playerName = ' ' ;
2026-03-12 14:00:01 +01:00
bool _isCreatingRoom = false ;
2026-02-27 23:35:54 +01:00
int _selectedRadius = 4 ;
ArenaShape _selectedShape = ArenaShape . classic ;
2026-03-20 22:00:01 +01:00
String _timeModeSetting = ' fixed ' ;
2026-03-11 23:00:01 +01:00
bool _isPublicRoom = true ;
2026-03-12 14:00:01 +01:00
bool _roomStarted = false ;
2026-02-27 23:35:54 +01:00
@ override
void initState ( ) {
super . initState ( ) ;
2026-03-12 14:00:01 +01:00
WidgetsBinding . instance . addObserver ( this ) ;
2026-02-27 23:35:54 +01:00
_codeController = TextEditingController ( ) ;
_playerName = StorageService . instance . playerName ;
if ( widget . initialRoomCode ! = null & & widget . initialRoomCode ! . isNotEmpty ) {
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
setState ( ( ) { _codeController . text = widget . initialRoomCode ! ; } ) ;
} ) ;
}
}
@ override
2026-03-11 23:00:01 +01:00
void dispose ( ) {
2026-03-12 14:00:01 +01:00
WidgetsBinding . instance . removeObserver ( this ) ;
_cleanupGhostRoom ( ) ;
2026-03-11 23:00:01 +01:00
_codeController . dispose ( ) ;
super . dispose ( ) ;
}
@ override
void didChangeAppLifecycleState ( AppLifecycleState state ) {
2026-03-21 00:00:01 +01:00
if ( state = = AppLifecycleState . detached ) {
2026-03-11 23:00:01 +01:00
_cleanupGhostRoom ( ) ;
}
}
void _cleanupGhostRoom ( ) {
if ( _myRoomCode ! = null & & ! _roomStarted ) {
FirebaseFirestore . instance . collection ( ' games ' ) . doc ( _myRoomCode ) . delete ( ) ;
2026-03-12 14:00:01 +01:00
_myRoomCode = null ;
2026-03-11 23:00:01 +01:00
}
}
2026-02-27 23:35:54 +01:00
Future < void > _createRoom ( ) async {
if ( _isLoading ) return ;
setState ( ( ) = > _isLoading = true ) ;
try {
2026-03-11 22:00:01 +01:00
String code = await _multiplayerService . createGameRoom (
2026-03-20 22:00:01 +01:00
_selectedRadius , _playerName , _selectedShape . name , _timeModeSetting , isPublic: _isPublicRoom
2026-03-11 22:00:01 +01:00
) ;
2026-02-27 23:35:54 +01:00
if ( ! mounted ) return ;
2026-03-11 23:00:01 +01:00
setState ( ( ) { _myRoomCode = code ; _isLoading = false ; _roomStarted = false ; } ) ;
2026-02-27 23:35:54 +01:00
2026-03-11 22:00:01 +01:00
if ( ! _isPublicRoom ) {
_multiplayerService . shareInviteLink ( code ) ;
}
2026-02-27 23:35:54 +01:00
_showWaitingDialog ( code ) ;
} catch ( e ) {
if ( mounted ) { setState ( ( ) = > _isLoading = false ) ; _showError ( " Errore durante la creazione della partita. " ) ; }
}
}
2026-03-15 13:00:00 +01:00
Future < void > _createRoomAndInvite ( String targetUid , String targetName ) async {
if ( _isLoading ) return ;
setState ( ( ) = > _isLoading = true ) ;
try {
String code = await _multiplayerService . createGameRoom (
2026-03-20 22:00:01 +01:00
_selectedRadius , _playerName , _selectedShape . name , _timeModeSetting , isPublic: _isPublicRoom
2026-03-15 13:00:00 +01:00
) ;
await _multiplayerService . sendInvite ( targetUid , code , _playerName ) ;
if ( ! mounted ) return ;
setState ( ( ) { _myRoomCode = code ; _isLoading = false ; _roomStarted = false ; } ) ;
ScaffoldMessenger . of ( context ) . showSnackBar ( SnackBar ( content: Text ( " Sfida inviata a $ targetName ! " ) , backgroundColor: Colors . green ) ) ;
_showWaitingDialog ( code ) ;
} catch ( e ) {
if ( mounted ) { setState ( ( ) = > _isLoading = false ) ; _showError ( " Errore durante la creazione della partita. " ) ; }
}
}
2026-03-11 22:00:01 +01:00
Future < void > _joinRoomByCode ( String code ) async {
2026-02-27 23:35:54 +01:00
if ( _isLoading ) return ;
FocusScope . of ( context ) . unfocus ( ) ;
2026-03-11 22:00:01 +01:00
code = code . trim ( ) . toUpperCase ( ) ;
2026-02-27 23:35:54 +01:00
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 ) ;
2026-03-20 22:00:01 +01:00
String hostTimeMode = roomData [ ' timeMode ' ] is String ? roomData [ ' timeMode ' ] : ( roomData [ ' timeMode ' ] = = true ? ' fixed ' : ' relax ' ) ;
2026-02-27 23:35:54 +01:00
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 ) ) ; }
2026-03-15 13:00:00 +01:00
void _showFavoritesDialogForCreation ( ) {
final favs = StorageService . instance . favorites ;
showDialog (
context: context ,
builder: ( ctx ) {
final themeManager = ctx . watch < ThemeManager > ( ) ;
final theme = themeManager . currentColors ;
final themeType = themeManager . currentThemeType ;
return AlertDialog (
backgroundColor: theme . background ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 20 ) ,
) ,
2026-03-15 15:00:01 +01:00
title: Text ( " I TUOI PREFERITI " , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text , fontWeight: FontWeight . bold ) ) ) ,
2026-03-15 13:00:00 +01:00
content: Container (
width: double . maxFinite ,
height: 300 ,
decoration: BoxDecoration (
border: Border . all ( color: theme . playerRed , width: 2 ) ,
borderRadius: BorderRadius . circular ( 10 )
) ,
child: favs . isEmpty
? Center ( child: Padding (
padding: const EdgeInsets . all ( 20.0 ) ,
2026-03-15 15:00:01 +01:00
child: Text ( " Non hai ancora aggiunto nessun preferito dalla Classifica! " , textAlign: TextAlign . center , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.6 ) ) ) ) ,
2026-03-15 13:00:00 +01:00
) )
: ListView . builder (
itemCount: favs . length ,
itemBuilder: ( c , i ) {
return ListTile (
2026-03-15 15:00:01 +01:00
title: Text ( favs [ i ] [ ' name ' ] ! , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text , fontSize: 18 , fontWeight: FontWeight . bold ) ) ) ,
2026-03-15 13:00:00 +01:00
trailing: ElevatedButton (
style: ElevatedButton . styleFrom ( backgroundColor: theme . playerBlue , shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 10 ) ) ) ,
onPressed: ( ) {
Navigator . pop ( ctx ) ;
_createRoomAndInvite ( favs [ i ] [ ' uid ' ] ! , favs [ i ] [ ' name ' ] ! ) ;
} ,
2026-03-15 15:00:01 +01:00
child: Text ( " SFIDA " , style: getLobbyTextStyle ( themeType , const TextStyle ( color: Colors . white , fontWeight: FontWeight . bold ) ) ) ,
2026-03-15 13:00:00 +01:00
) ,
) ;
} ,
) ,
) ,
actions: [
2026-03-15 15:00:01 +01:00
TextButton ( onPressed: ( ) = > Navigator . pop ( ctx ) , child: Text ( " CHIUDI " , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . playerRed ) ) ) )
2026-03-15 13:00:00 +01:00
] ,
) ;
}
) ;
}
2026-02-27 23:35:54 +01:00
void _showWaitingDialog ( String code ) {
showDialog (
context: context ,
barrierDismissible: false ,
2026-03-21 11:00:05 +01:00
builder: ( dialogContext ) {
final theme = dialogContext . watch < ThemeManager > ( ) . currentColors ;
final themeType = dialogContext . read < ThemeManager > ( ) . currentThemeType ;
2026-03-21 00:00:01 +01:00
final loc = AppLocalizations . of ( context ) ! ;
2026-02-27 23:35:54 +01:00
Widget dialogContent = Column (
mainAxisSize: MainAxisSize . min ,
children: [
CircularProgressIndicator ( color: theme . playerRed ) , const SizedBox ( height: 25 ) ,
2026-03-21 11:00:05 +01:00
Text ( loc . codeHint , 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 ) ] ) ) ) ,
2026-02-27 23:35:54 +01:00
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: [
2026-03-11 22:00:01 +01:00
Icon ( _isPublicRoom ? Icons . podcasts : Icons . share , color: theme . playerBlue , size: 32 ) , const SizedBox ( height: 12 ) ,
2026-03-21 11:00:05 +01:00
Text ( _isPublicRoom ? " Sei in Bacheca! " : " Invito inviato " , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( color: theme . text , fontWeight: FontWeight . w900 , fontSize: 18 ) ) ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 8 ) ,
2026-03-21 11:00:05 +01:00
Text ( _isPublicRoom ? " Aspettiamo che uno sfidante si unisca dalla lobby pubblica. " : " Attendi che il tuo amico accetti la sfida. Non chiudere questa finestra. " , textAlign: TextAlign . center , style: getSharedTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.8 ) , fontSize: 14 , height: 1.5 ) ) ) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
) ,
] ,
) ;
2026-03-15 17:00:01 +01:00
if ( themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . music ) {
2026-03-15 15:00:01 +01:00
dialogContent = AnimatedCyberBorder ( child: dialogContent ) ;
2026-02-27 23:35:54 +01:00
} 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 ) ,
2026-03-21 11:00:05 +01:00
builder: ( ctx , snapshot ) {
2026-02-27 23:35:54 +01:00
if ( snapshot . hasData & & snapshot . data ! . exists ) {
var data = snapshot . data ! . data ( ) as Map < String , dynamic > ;
if ( data [ ' status ' ] = = ' playing ' ) {
2026-03-12 14:00:01 +01:00
_roomStarted = true ;
2026-02-27 23:35:54 +01:00
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
2026-03-21 11:00:05 +01:00
Navigator . pop ( ctx ) ;
2026-03-20 22:00:01 +01:00
context . read < GameController > ( ) . startNewGame ( _selectedRadius , isOnline: true , roomCode: code , isHost: true , shape: _selectedShape , timeMode: _timeModeSetting ) ;
2026-02-27 23:35:54 +01:00
Navigator . pushReplacement ( context , MaterialPageRoute ( builder: ( _ ) = > const GameScreen ( ) ) ) ;
} ) ;
}
}
2026-03-11 23:00:01 +01:00
return PopScope (
canPop: false ,
onPopInvoked: ( didPop ) {
if ( didPop ) return ;
2026-03-12 14:00:01 +01:00
_cleanupGhostRoom ( ) ;
2026-03-21 11:00:05 +01:00
Navigator . pop ( ctx ) ;
2026-03-11 23:00:01 +01:00
} ,
child: Dialog (
backgroundColor: Colors . transparent ,
insetPadding: const EdgeInsets . all ( 20 ) ,
child: Column (
mainAxisSize: MainAxisSize . min ,
children: [
dialogContent ,
const SizedBox ( height: 20 ) ,
TextButton (
onPressed: ( ) {
2026-03-12 14:00:01 +01:00
_cleanupGhostRoom ( ) ;
2026-03-21 11:00:05 +01:00
Navigator . pop ( ctx ) ;
2026-03-11 23:00:01 +01:00
} ,
2026-03-21 11:00:05 +01:00
child: Text ( loc . btnCancel . toUpperCase ( ) , 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-11 23:00:01 +01:00
) ,
] ,
) ,
2026-02-27 23:35:54 +01:00
) ,
) ;
} ,
) ;
}
) ;
}
2026-03-20 22:00:01 +01:00
Widget _buildTimeOption ( String label , String sub , String value , ThemeColors theme , AppThemeType type ) {
bool isSel = value = = _timeModeSetting ;
return Expanded (
child: GestureDetector (
onTap: ( ) = > setState ( ( ) = > _timeModeSetting = value ) ,
child: Container (
margin: const EdgeInsets . symmetric ( horizontal: 4 ) ,
height: 50 ,
decoration: BoxDecoration (
color: isSel ? Colors . orange . shade600 : ( type = = AppThemeType . doodle ? Colors . white : theme . text . withOpacity ( 0.05 ) ) ,
borderRadius: BorderRadius . circular ( 12 ) ,
border: Border . all ( color: isSel ? Colors . orange . shade800 : ( type = = AppThemeType . doodle ? const Color ( 0xFF111122 ) : Colors . white24 ) , width: isSel ? 2 : 1.5 ) ,
boxShadow: isSel & & type ! = AppThemeType . doodle ? [ BoxShadow ( color: Colors . orange . withOpacity ( 0.5 ) , blurRadius: 8 ) ] : [ ] ,
) ,
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Text ( label , style: getLobbyTextStyle ( type , TextStyle ( color: isSel ? Colors . white : ( type = = AppThemeType . doodle ? const Color ( 0xFF111122 ) : theme . text ) , fontWeight: FontWeight . w900 , fontSize: 13 ) ) ) ,
if ( sub . isNotEmpty ) Text ( sub , style: getLobbyTextStyle ( type , TextStyle ( color: isSel ? Colors . white70 : ( type = = AppThemeType . doodle ? Colors . black54 : theme . text . withOpacity ( 0.5 ) ) , fontWeight: FontWeight . bold , fontSize: 8 ) ) ) ,
] ,
) ,
) ,
) ,
) ;
}
2026-02-27 23:35:54 +01:00
@ override
Widget build ( BuildContext context ) {
final themeManager = context . watch < ThemeManager > ( ) ;
final themeType = themeManager . currentThemeType ;
final theme = themeManager . currentColors ;
2026-03-21 11:00:05 +01:00
final loc = AppLocalizations . of ( context ) ! ;
2026-02-27 23:35:54 +01:00
String ? bgImage ;
if ( themeType = = AppThemeType . doodle ) bgImage = ' assets/images/doodle_bg.jpg ' ;
if ( themeType = = AppThemeType . cyberpunk ) bgImage = ' assets/images/cyber_bg.jpg ' ;
2026-03-15 17:00:01 +01:00
if ( themeType = = AppThemeType . music ) bgImage = ' assets/images/music_bg.jpg ' ;
if ( themeType = = AppThemeType . arcade ) bgImage = ' assets/images/arcade.jpg ' ;
if ( themeType = = AppThemeType . grimorio ) bgImage = ' assets/images/grimorio.jpg ' ;
2026-02-27 23:35:54 +01:00
2026-03-15 17:00:01 +01:00
bool isChaosUnlocked = StorageService . instance . playerLevel > = 7 ;
2026-02-27 23:35:54 +01:00
2026-03-15 21:00:01 +01:00
Color panelBackgroundColor = Colors . transparent ;
if ( themeType = = AppThemeType . cyberpunk ) {
2026-03-15 22:00:01 +01:00
panelBackgroundColor = Colors . black . withOpacity ( 0.1 ) ;
2026-03-15 21:00:01 +01:00
} else if ( themeType = = AppThemeType . doodle ) {
panelBackgroundColor = Colors . white . withOpacity ( 0.5 ) ;
} else if ( themeType = = AppThemeType . grimorio ) {
2026-03-20 22:00:01 +01:00
panelBackgroundColor = Colors . white . withOpacity ( 0.2 ) ;
2026-03-15 22:00:01 +01:00
} else if ( themeType = = AppThemeType . arcade ) {
2026-03-20 22:00:01 +01:00
panelBackgroundColor = Colors . black . withOpacity ( 0.4 ) ;
2026-03-15 21:00:01 +01:00
}
2026-02-27 23:35:54 +01:00
Widget hostPanel = Transform . rotate (
angle: themeType = = AppThemeType . doodle ? 0.01 : 0 ,
child: Container (
padding: const EdgeInsets . symmetric ( horizontal: 12 , vertical: 15 ) ,
decoration: BoxDecoration (
2026-03-15 21:00:01 +01:00
color: panelBackgroundColor ,
2026-02-27 23:35:54 +01:00
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: [
2026-03-21 00:00:01 +01:00
Center ( child: Text ( loc . roomSettings , textAlign: TextAlign . center , style: getLobbyTextStyle ( themeType , TextStyle ( fontSize: 12 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.6 ) , letterSpacing: 2.0 ) ) ) ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 10 ) ,
2026-03-21 00:00:01 +01:00
Text ( loc . arenaShape , style: getLobbyTextStyle ( themeType , TextStyle ( fontSize: 10 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 6 ) ,
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
2026-03-15 15:00:01 +01:00
Expanded ( child: NeonShapeButton ( icon: Icons . diamond_outlined , label: ' Rombo ' , isSelected: _selectedShape = = ArenaShape . classic , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . classic ) ) ) ,
2026-03-12 21:32:11 +01:00
const SizedBox ( width: 4 ) ,
2026-03-15 15:00:01 +01:00
Expanded ( child: NeonShapeButton ( icon: Icons . add , label: ' Croce ' , isSelected: _selectedShape = = ArenaShape . cross , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . cross ) ) ) ,
2026-03-12 21:32:11 +01:00
const SizedBox ( width: 4 ) ,
2026-03-15 15:00:01 +01:00
Expanded ( child: NeonShapeButton ( icon: Icons . donut_large , label: ' Buco ' , isSelected: _selectedShape = = ArenaShape . donut , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . donut ) ) ) ,
2026-03-12 21:32:11 +01:00
const SizedBox ( width: 4 ) ,
2026-03-15 15:00:01 +01:00
Expanded ( child: NeonShapeButton ( icon: Icons . hourglass_bottom , label: ' Clessidra ' , isSelected: _selectedShape = = ArenaShape . hourglass , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . hourglass ) ) ) ,
2026-03-12 21:32:11 +01:00
const SizedBox ( width: 4 ) ,
2026-03-15 15:00:01 +01:00
Expanded ( child: NeonShapeButton ( icon: Icons . all_inclusive , label: ' Caos ' , isSelected: _selectedShape = = ArenaShape . chaos , theme: theme , themeType: themeType , isSpecial: true , isLocked: ! isChaosUnlocked , onTap: ( ) = > setState ( ( ) = > _selectedShape = ArenaShape . chaos ) ) ) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
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 ) ,
2026-03-21 00:00:01 +01:00
Text ( loc . arenaSize , style: getLobbyTextStyle ( themeType , TextStyle ( fontSize: 10 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 8 ) ,
Row (
mainAxisAlignment: MainAxisAlignment . spaceEvenly ,
children: [
2026-03-15 15:00:01 +01:00
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 ) ) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
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 ) ,
2026-03-21 00:00:01 +01:00
Text ( loc . timeAndOptions , style: getLobbyTextStyle ( themeType , TextStyle ( fontSize: 10 , fontWeight: FontWeight . w900 , color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , letterSpacing: 1.5 ) ) ) ,
2026-02-27 23:35:54 +01:00
const SizedBox ( height: 8 ) ,
2026-03-15 13:00:00 +01:00
2026-03-11 22:00:01 +01:00
Row (
children: [
2026-03-20 22:00:01 +01:00
_buildTimeOption ( ' 10s ' , ' FISSO ' , ' fixed ' , theme , themeType ) ,
_buildTimeOption ( ' RELAX ' , ' INFINITO ' , ' relax ' , theme , themeType ) ,
_buildTimeOption ( ' DINAMICO ' , ' -2s ' , ' dynamic ' , theme , themeType ) ,
2026-03-15 13:00:00 +01:00
] ,
) ,
const SizedBox ( height: 10 ) ,
Row (
children: [
2026-03-15 15:00:01 +01:00
Expanded ( child: NeonPrivacySwitch ( isPublic: _isPublicRoom , theme: theme , themeType: themeType , onTap: ( ) = > setState ( ( ) = > _isPublicRoom = ! _isPublicRoom ) ) ) ,
2026-03-15 13:00:00 +01:00
const SizedBox ( width: 8 ) ,
2026-03-15 15:00:01 +01:00
Expanded ( child: NeonInviteFavoriteButton ( theme: theme , themeType: themeType , onTap: _showFavoritesDialogForCreation ) ) ,
2026-03-11 22:00:01 +01:00
] ,
)
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
) ;
2026-03-15 17:00:01 +01:00
if ( themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . music ) {
2026-03-15 15:00:01 +01:00
hostPanel = AnimatedCyberBorder ( child: hostPanel ) ;
2026-02-27 23:35:54 +01:00
}
Widget uiContent = SafeArea (
child: SingleChildScrollView (
2026-03-12 14:00:01 +01:00
physics: const BouncingScrollPhysics ( ) ,
padding: EdgeInsets . only ( left: 20.0 , right: 20.0 , top: 10.0 , bottom: MediaQuery . of ( context ) . padding . bottom + 60.0 ) ,
2026-02-27 23:35:54 +01:00
child: Column (
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
Row (
children: [
2026-03-15 13:00:00 +01:00
IconButton (
icon: Icon ( Icons . arrow_back_ios_new , color: theme . text ) ,
onPressed: ( ) = > Navigator . pop ( context ) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-15 13:00:00 +01:00
Expanded (
2026-03-21 00:00:01 +01:00
child: Text ( loc . onlineTitle . toUpperCase ( ) , textAlign: TextAlign . center , style: getLobbyTextStyle ( themeType , TextStyle ( fontSize: 20 , fontWeight: FontWeight . w900 , color: theme . text , letterSpacing: 2 ) ) ) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-15 17:00:01 +01:00
const SizedBox ( width: 48 ) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
const SizedBox ( height: 20 ) ,
2026-03-12 14:00:01 +01:00
AnimatedSize (
duration: const Duration ( milliseconds: 300 ) ,
curve: Curves . easeInOut ,
alignment: Alignment . topCenter ,
child: _isCreatingRoom
2026-03-15 13:00:00 +01:00
? Column (
2026-03-12 14:00:01 +01:00
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
hostPanel ,
const SizedBox ( height: 15 ) ,
Row (
children: [
2026-03-15 13:00:00 +01:00
Expanded (
2026-03-21 00:00:01 +01:00
child: NeonActionButton ( label: loc . btnStart . toUpperCase ( ) , color: theme . playerRed , onTap: _createRoom , theme: theme , themeType: themeType ) ,
2026-03-12 14:00:01 +01:00
) ,
const SizedBox ( width: 10 ) ,
2026-03-15 13:00:00 +01:00
Expanded (
2026-03-21 00:00:01 +01:00
child: NeonActionButton ( label: loc . btnCancel . toUpperCase ( ) , color: Colors . grey . shade600 , onTap: ( ) = > setState ( ( ) = > _isCreatingRoom = false ) , theme: theme , themeType: themeType ) ,
2026-03-12 14:00:01 +01:00
) ,
] ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-12 14:00:01 +01:00
] ,
)
2026-03-15 13:00:00 +01:00
: Column (
2026-03-12 14:00:01 +01:00
crossAxisAlignment: CrossAxisAlignment . stretch ,
children: [
2026-03-21 00:00:01 +01:00
NeonActionButton ( label: loc . createMatch . toUpperCase ( ) , color: theme . playerRed , onTap: ( ) { FocusScope . of ( context ) . unfocus ( ) ; setState ( ( ) = > _isCreatingRoom = true ) ; } , theme: theme , themeType: themeType ) ,
2026-03-12 14:00:01 +01:00
const SizedBox ( height: 20 ) ,
Row (
children: [
Expanded ( child: Divider ( color: theme . text . withOpacity ( 0.4 ) , thickness: themeType = = AppThemeType . doodle ? 2 : 1.0 ) ) ,
2026-03-21 00:00:01 +01:00
Padding ( padding: const EdgeInsets . symmetric ( horizontal: 10 ) , child: Text ( loc . wordOr . toUpperCase ( ) , style: getLobbyTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , fontWeight: FontWeight . bold , letterSpacing: 2.0 , fontSize: 13 ) ) ) ) ,
2026-03-12 14:00:01 +01:00
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 ,
2026-03-15 15:00:01 +01:00
style: getLobbyTextStyle ( 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 ) ] ) ) ,
2026-03-12 14:00:01 +01:00
decoration: InputDecoration (
contentPadding: const EdgeInsets . symmetric ( vertical: 12 ) ,
2026-03-21 00:00:01 +01:00
hintText: loc . codeHint . toUpperCase ( ) , hintStyle: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.3 ) , letterSpacing: 10 , fontSize: 20 ) ) , counterText: " " ,
2026-03-12 14:00:01 +01:00
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 ) ,
2026-03-21 00:00:01 +01:00
NeonActionButton ( label: loc . joinMatch . toUpperCase ( ) , color: theme . playerBlue , onTap: ( ) = > _joinRoomByCode ( _codeController . text ) , theme: theme , themeType: themeType ) ,
2026-03-12 14:00:01 +01:00
] ,
2026-02-27 23:35:54 +01:00
) ,
) ,
2026-03-11 22:00:01 +01:00
const SizedBox ( height: 25 ) ,
Row (
children: [
Expanded ( child: Divider ( color: theme . text . withOpacity ( 0.4 ) , thickness: themeType = = AppThemeType . doodle ? 2 : 1.0 ) ) ,
2026-03-21 00:00:01 +01:00
Padding ( padding: const EdgeInsets . symmetric ( horizontal: 10 ) , child: Text ( loc . publicLobbyTitle . toUpperCase ( ) , style: getLobbyTextStyle ( themeType , TextStyle ( color: themeType = = AppThemeType . doodle ? theme . text : theme . text . withOpacity ( 0.5 ) , fontWeight: FontWeight . bold , letterSpacing: 2.0 , fontSize: 13 ) ) ) ) ,
2026-03-11 22:00:01 +01:00
Expanded ( child: Divider ( color: theme . text . withOpacity ( 0.4 ) , thickness: themeType = = AppThemeType . doodle ? 2 : 1.0 ) ) ,
] ,
) ,
const SizedBox ( height: 15 ) ,
StreamBuilder < QuerySnapshot > (
stream: _multiplayerService . getPublicRooms ( ) ,
builder: ( context , snapshot ) {
if ( snapshot . connectionState = = ConnectionState . waiting ) {
return Padding ( padding: const EdgeInsets . all ( 20 ) , child: Center ( child: CircularProgressIndicator ( color: theme . playerBlue ) ) ) ;
}
if ( ! snapshot . hasData | | snapshot . data ! . docs . isEmpty ) {
return Padding (
2026-03-15 15:00:01 +01:00
padding: const EdgeInsets . symmetric ( vertical: 40.0 ) ,
2026-03-21 00:00:01 +01:00
child: Center ( child: Text ( loc . emptyLobbyMsg , textAlign: TextAlign . center , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.6 ) , height: 1.5 , fontSize: 16 ) ) ) ) ,
2026-03-11 22:00:01 +01:00
) ;
}
2026-03-12 14:00:01 +01:00
DateTime now = DateTime . now ( ) ;
2026-03-11 23:00:01 +01:00
String ? myUid = FirebaseAuth . instance . currentUser ? . uid ;
var docs = snapshot . data ! . docs . where ( ( doc ) {
var data = doc . data ( ) as Map < String , dynamic > ;
if ( data [ ' isPublic ' ] ! = true ) return false ;
if ( data [ ' hostUid ' ] ! = null & & data [ ' hostUid ' ] = = myUid ) return false ;
Timestamp ? createdAt = data [ ' createdAt ' ] as Timestamp ? ;
if ( createdAt ! = null ) {
int ageInMinutes = now . difference ( createdAt . toDate ( ) ) . inMinutes ;
if ( ageInMinutes > 15 ) {
FirebaseFirestore . instance . collection ( ' games ' ) . doc ( doc . id ) . delete ( ) ;
return false ;
}
}
2026-03-12 14:00:01 +01:00
return true ;
2026-03-11 23:00:01 +01:00
} ) . toList ( ) ;
if ( docs . isEmpty ) {
return Padding (
2026-03-15 15:00:01 +01:00
padding: const EdgeInsets . symmetric ( vertical: 40.0 ) ,
2026-03-21 00:00:01 +01:00
child: Center ( child: Text ( loc . emptyLobbyMsg , textAlign: TextAlign . center , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.6 ) , height: 1.5 , fontSize: 16 ) ) ) ) ,
2026-03-11 23:00:01 +01:00
) ;
}
2026-03-11 22:00:01 +01:00
docs . sort ( ( a , b ) {
Timestamp ? tA = ( a . data ( ) as Map < String , dynamic > ) [ ' createdAt ' ] as Timestamp ? ;
Timestamp ? tB = ( b . data ( ) as Map < String , dynamic > ) [ ' createdAt ' ] as Timestamp ? ;
if ( tA = = null | | tB = = null ) return 0 ;
return tB . compareTo ( tA ) ;
} ) ;
return ListView . builder (
shrinkWrap: true ,
physics: const NeverScrollableScrollPhysics ( ) ,
padding: EdgeInsets . zero ,
itemCount: docs . length ,
itemBuilder: ( context , index ) {
var doc = docs [ index ] ;
var data = doc . data ( ) as Map < String , dynamic > ;
2026-03-21 00:00:01 +01:00
String host = data [ ' hostName ' ] ? ? ' Guest ' ;
2026-03-11 22:00:01 +01:00
int r = data [ ' radius ' ] ? ? 4 ;
String shapeStr = data [ ' shape ' ] ? ? ' classic ' ;
2026-03-20 22:00:01 +01:00
String tMode = data [ ' timeMode ' ] is String ? data [ ' timeMode ' ] : ( data [ ' timeMode ' ] = = true ? ' fixed ' : ' relax ' ) ;
String prettyTime = " 10s " ;
if ( tMode = = ' relax ' ) prettyTime = " Relax " ;
else if ( tMode = = ' dynamic ' ) prettyTime = " Dinamico " ;
2026-03-11 22:00:01 +01:00
String prettyShape = " Rombo " ;
if ( shapeStr = = ' cross ' ) prettyShape = " Croce " ;
else if ( shapeStr = = ' donut ' ) prettyShape = " Buco " ;
else if ( shapeStr = = ' hourglass ' ) prettyShape = " Clessidra " ;
else if ( shapeStr = = ' chaos ' ) prettyShape = " Caos " ;
return Transform . rotate (
angle: themeType = = AppThemeType . doodle ? ( index % 2 = = 0 ? 0.01 : - 0.01 ) : 0 ,
child: Container (
2026-03-15 15:00:01 +01:00
margin: const EdgeInsets . only ( bottom: 15 ) ,
padding: const EdgeInsets . all ( 16 ) ,
2026-03-11 22:00:01 +01:00
decoration: BoxDecoration (
color: themeType = = AppThemeType . doodle ? Colors . white : theme . text . withOpacity ( 0.05 ) ,
borderRadius: BorderRadius . circular ( 15 ) ,
border: Border . all ( color: themeType = = AppThemeType . doodle ? theme . text : theme . playerBlue . withOpacity ( 0.3 ) , width: themeType = = AppThemeType . doodle ? 2 : 1 ) ,
boxShadow: themeType = = AppThemeType . doodle ? [ BoxShadow ( color: theme . text . withOpacity ( 0.6 ) , offset: const Offset ( 3 , 4 ) ) ] : [ ] ,
) ,
child: Row (
children: [
2026-03-15 15:00:01 +01:00
CircleAvatar ( radius: 25 , backgroundColor: theme . playerRed . withOpacity ( 0.2 ) , child: Icon ( Icons . person , color: theme . playerRed , size: 28 ) ) ,
const SizedBox ( width: 15 ) ,
2026-03-11 22:00:01 +01:00
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
2026-03-21 00:00:01 +01:00
Text ( " ${ loc . roomOf } $ host " , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text , fontWeight: FontWeight . bold , fontSize: 18 ) ) ) ,
2026-03-15 15:00:01 +01:00
const SizedBox ( height: 6 ) ,
2026-03-20 22:00:01 +01:00
Text ( " Raggio: $ r • $ prettyShape • $ prettyTime " , style: getLobbyTextStyle ( themeType , TextStyle ( color: theme . text . withOpacity ( 0.6 ) , fontSize: 12 ) ) ) ,
2026-03-11 22:00:01 +01:00
] ,
) ,
) ,
ElevatedButton (
style: ElevatedButton . styleFrom (
2026-03-15 15:00:01 +01:00
padding: const EdgeInsets . symmetric ( horizontal: 20 , vertical: 12 ) ,
2026-03-11 22:00:01 +01:00
backgroundColor: theme . playerBlue , foregroundColor: Colors . white ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 10 ) ) ,
elevation: themeType = = AppThemeType . doodle ? 0 : 2 ,
side: themeType = = AppThemeType . doodle ? BorderSide ( color: theme . text , width: 2 ) : BorderSide . none ,
) ,
onPressed: ( ) = > _joinRoomByCode ( doc . id ) ,
2026-03-21 00:00:01 +01:00
child: Text ( loc . btnEnter . toUpperCase ( ) , style: getLobbyTextStyle ( themeType , const TextStyle ( fontWeight: FontWeight . w900 , letterSpacing: 1.0 ) ) ) ,
2026-03-11 22:00:01 +01:00
)
] ,
) ,
) ,
) ;
}
) ;
}
) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
) ;
return Scaffold (
backgroundColor: bgImage ! = null ? Colors . transparent : theme . background ,
extendBodyBehindAppBar: true ,
body: Stack (
children: [
2026-03-15 13:00:00 +01:00
Container ( color: themeType = = AppThemeType . doodle ? Colors . white : theme . background ) ,
2026-03-15 16:00:01 +01:00
if ( themeType = = AppThemeType . doodle )
Positioned . fill (
child: CustomPaint (
painter: FullScreenGridPainter ( Colors . blue . withOpacity ( 0.15 ) ) ,
) ,
) ,
2026-03-15 13:00:00 +01:00
if ( bgImage ! = null )
Positioned . fill (
2026-03-15 16:00:01 +01:00
child: Container (
decoration: BoxDecoration (
image: DecorationImage (
image: AssetImage ( bgImage ! ) ,
fit: BoxFit . cover ,
colorFilter: themeType = = AppThemeType . doodle
? ColorFilter . mode ( Colors . white . withOpacity ( 0.5 ) , BlendMode . lighten )
: null ,
) ,
) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-15 13:00:00 +01:00
) ,
2026-03-15 16:00:01 +01:00
2026-03-15 13:00:00 +01:00
if ( bgImage ! = null & & ( themeType = = AppThemeType . cyberpunk | | themeType = = AppThemeType . music | | themeType = = AppThemeType . arcade | | themeType = = AppThemeType . grimorio ) )
Positioned . fill (
child: Container (
decoration: BoxDecoration (
gradient: LinearGradient (
begin: Alignment . topCenter , end: Alignment . bottomCenter ,
2026-03-15 16:00:01 +01:00
colors: [ Colors . black . withOpacity ( 0.4 ) , Colors . black . withOpacity ( 0.8 ) ]
2026-03-15 13:00:00 +01:00
)
) ,
2026-02-27 23:35:54 +01:00
) ,
) ,
2026-03-15 16:00:01 +01:00
if ( themeType = = AppThemeType . music )
2026-03-15 13:00:00 +01:00
Positioned . fill (
2026-03-15 16:00:01 +01:00
child: IgnorePointer (
child: CustomPaint (
painter: AudioCablesPainter ( ) ,
) ,
2026-03-15 13:00:00 +01:00
) ,
) ,
2026-03-15 16:00:01 +01:00
2026-03-15 13:00:00 +01:00
Positioned . fill ( child: uiContent ) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ;
}
2026-03-21 11:00:05 +01:00
}
class FullScreenGridPainter extends CustomPainter {
final Color gridColor ;
FullScreenGridPainter ( this . gridColor ) ;
@ override
void paint ( Canvas canvas , Size size ) {
final Paint paperGridPaint = Paint ( ) . . color = gridColor . . strokeWidth = 1.0 . . style = PaintingStyle . stroke ;
double paperStep = 20.0 ;
for ( double i = 0 ; i < = size . width ; i + = paperStep ) canvas . drawLine ( Offset ( i , 0 ) , Offset ( i , size . height ) , paperGridPaint ) ;
for ( double i = 0 ; i < = size . height ; i + = paperStep ) canvas . drawLine ( Offset ( 0 , i ) , Offset ( size . width , i ) , paperGridPaint ) ;
}
@ override
bool shouldRepaint ( covariant CustomPainter oldDelegate ) = > false ;
2026-02-27 23:35:54 +01:00
}