2026-02-27 23:35:54 +01:00
// ===========================================================================
// FILE: lib/ui/settings/settings_screen.dart
// ===========================================================================
import ' package:flutter/material.dart ' ;
import ' package:provider/provider.dart ' ;
import ' ../../core/theme_manager.dart ' ;
import ' ../../core/app_colors.dart ' ;
2026-03-01 20:59:06 +01:00
import ' ../../services/storage_service.dart ' ;
2026-03-15 17:00:01 +01:00
import ' ../../widgets/painters.dart ' ;
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
class SettingsScreen extends StatefulWidget {
2026-02-27 23:35:54 +01:00
const SettingsScreen ( { super . key } ) ;
2026-03-01 20:59:06 +01:00
@ override
State < SettingsScreen > createState ( ) = > _SettingsScreenState ( ) ;
}
class _SettingsScreenState extends State < SettingsScreen > {
2026-02-27 23:35:54 +01:00
@ override
Widget build ( BuildContext context ) {
final themeManager = context . watch < ThemeManager > ( ) ;
final theme = themeManager . currentColors ;
2026-03-15 17:00:01 +01:00
final themeType = themeManager . currentThemeType ;
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
int playerLevel = StorageService . instance . playerLevel ;
2026-03-15 17:00:01 +01:00
final double screenHeight = MediaQuery . of ( context ) . size . height ;
final double vScale = ( screenHeight / 920.0 ) . clamp ( 0.7 , 1.2 ) ;
2026-02-27 23:35:54 +01:00
return Scaffold (
backgroundColor: theme . background ,
2026-03-15 17:00:01 +01:00
extendBodyBehindAppBar: true ,
2026-02-27 23:35:54 +01:00
appBar: AppBar (
2026-03-15 17:00:01 +01:00
toolbarHeight: 80 * vScale ,
title: Text (
" SELEZIONA TEMA " ,
style: getSharedTextStyle ( themeType , TextStyle (
fontWeight: FontWeight . w900 ,
color: Colors . white ,
letterSpacing: 2.0 ,
fontSize: 20 * vScale ,
shadows: [ Shadow ( color: Colors . black . withOpacity ( 0.8 ) , blurRadius: 5 , offset: const Offset ( 2 , 2 ) ) ]
) )
) ,
centerTitle: true ,
2026-02-27 23:35:54 +01:00
backgroundColor: Colors . transparent ,
elevation: 0 ,
2026-03-15 17:00:01 +01:00
iconTheme: IconThemeData ( color: Colors . white , size: 28 * vScale ) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-15 17:00:01 +01:00
body: Stack (
2026-02-27 23:35:54 +01:00
children: [
2026-03-15 17:00:01 +01:00
Container ( color: themeType = = AppThemeType . doodle ? Colors . white : theme . background ) ,
Positioned . fill (
child: Container (
decoration: BoxDecoration (
image: DecorationImage (
image: const AssetImage ( ' assets/images/sfondo_temi.jpg ' ) ,
fit: BoxFit . cover ,
colorFilter: ColorFilter . mode ( Colors . black . withOpacity ( 0.6 ) , BlendMode . darken ) ,
) ,
) ,
) ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-15 17:00:01 +01:00
ListView (
padding: EdgeInsets . only ( top: 120 * vScale , left: 20 * vScale , right: 20 * vScale , bottom: 40 * vScale ) ,
physics: const BouncingScrollPhysics ( ) ,
children: [
_ThemeCard (
title: " Quaderno " ,
subtitle: " Sfondo a quadretti, tratto a penna " ,
type: AppThemeType . doodle ,
previewColors: AppColors . doodle ,
requiredLevel: 1 ,
currentLevel: playerLevel ,
vScale: vScale ,
) ,
SizedBox ( height: 25 * vScale ) ,
_ThemeCard (
title: " Cyberpunk " ,
subtitle: " Nero profondo, luci al neon " ,
type: AppThemeType . cyberpunk ,
previewColors: AppColors . cyberpunk ,
requiredLevel: 3 ,
currentLevel: playerLevel ,
vScale: vScale ,
) ,
SizedBox ( height: 25 * vScale ) ,
_ThemeCard (
title: " 8-Bit Arcade " ,
subtitle: " Sale giochi, fosfori verdi e pixel " ,
type: AppThemeType . arcade ,
previewColors: AppColors . arcade ,
requiredLevel: 7 ,
currentLevel: playerLevel ,
vScale: vScale ,
) ,
SizedBox ( height: 25 * vScale ) ,
_ThemeCard (
title: " Grimorio " ,
subtitle: " Incantesimi antichi, rune magiche " ,
type: AppThemeType . grimorio ,
previewColors: AppColors . grimorio ,
requiredLevel: 10 ,
currentLevel: playerLevel ,
vScale: vScale ,
) ,
SizedBox ( height: 25 * vScale ) ,
_ThemeCard (
title: " Musica " ,
subtitle: " Vinili, cassette e vibrazioni sonore " ,
type: AppThemeType . music ,
previewColors: AppColors . music ,
requiredLevel: 15 ,
currentLevel: playerLevel ,
vScale: vScale ,
) ,
SizedBox ( height: 40 * vScale ) ,
] ,
2026-03-13 23:00:01 +01:00
) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ;
}
}
class _ThemeCard extends StatelessWidget {
final String title ;
final String subtitle ;
final AppThemeType type ;
final ThemeColors previewColors ;
2026-03-01 20:59:06 +01:00
final int requiredLevel ;
final int currentLevel ;
2026-03-15 17:00:01 +01:00
final double vScale ;
2026-02-27 23:35:54 +01:00
2026-03-01 20:59:06 +01:00
const _ThemeCard ( {
required this . title ,
required this . subtitle ,
required this . type ,
required this . previewColors ,
required this . requiredLevel ,
required this . currentLevel ,
2026-03-15 17:00:01 +01:00
required this . vScale ,
2026-03-01 20:59:06 +01:00
} ) ;
2026-02-27 23:35:54 +01:00
@ override
Widget build ( BuildContext context ) {
final themeManager = context . watch < ThemeManager > ( ) ;
bool isSelected = themeManager . currentThemeType = = type ;
2026-03-01 20:59:06 +01:00
bool isLocked = currentLevel < requiredLevel ;
2026-02-27 23:35:54 +01:00
2026-03-15 17:00:01 +01:00
String ? bgImage ;
if ( type = = AppThemeType . doodle ) bgImage = ' assets/images/doodle_bg.jpg ' ;
if ( type = = AppThemeType . cyberpunk ) bgImage = ' assets/images/cyber_bg.jpg ' ;
if ( type = = AppThemeType . music ) bgImage = ' assets/images/music_bg.jpg ' ;
if ( type = = AppThemeType . arcade ) bgImage = ' assets/images/arcade.jpg ' ;
if ( type = = AppThemeType . grimorio ) bgImage = ' assets/images/grimorio.jpg ' ;
Border border ;
// OMBRA MARCATA PER DARE SPESSORE AL TASTO (Offset 0, 10)
List < BoxShadow > shadows = [
BoxShadow ( color: Colors . black . withOpacity ( 0.8 ) , offset: const Offset ( 0 , 10 ) , blurRadius: 15 )
] ;
if ( type = = AppThemeType . doodle ) {
border = Border . all ( color: isSelected ? previewColors . playerBlue : const Color ( 0xFF111122 ) . withOpacity ( 0.8 ) , width: isSelected ? 4 : 2 ) ;
if ( isSelected ) shadows . add ( const BoxShadow ( color: Color ( 0xFF111122 ) , offset: Offset ( 4 , 5 ) ) ) ;
} else if ( type = = AppThemeType . cyberpunk | | type = = AppThemeType . music ) {
border = Border . all ( color: isSelected ? previewColors . playerBlue : previewColors . gridLine . withOpacity ( 0.8 ) , width: isSelected ? 3 : 1.5 ) ;
if ( isSelected ) shadows . add ( BoxShadow ( color: previewColors . playerBlue . withOpacity ( 0.8 ) , blurRadius: 25 , spreadRadius: 3 ) ) ;
} else if ( type = = AppThemeType . arcade ) {
border = Border . all ( color: isSelected ? previewColors . gridLine : Colors . white54 , width: isSelected ? 4 : 2 ) ;
if ( isSelected ) shadows . add ( BoxShadow ( color: previewColors . gridLine . withOpacity ( 0.5 ) , offset: const Offset ( 4 , 4 ) ) ) ;
} else {
border = Border . all ( color: isSelected ? Colors . amber : previewColors . gridLine . withOpacity ( 0.8 ) , width: isSelected ? 3 : 1.5 ) ;
if ( isSelected ) shadows . add ( BoxShadow ( color: Colors . amber . withOpacity ( 0.6 ) , blurRadius: 20 ) ) ;
}
2026-02-27 23:35:54 +01:00
return GestureDetector (
onTap: ( ) {
2026-03-01 20:59:06 +01:00
if ( isLocked ) {
ScaffoldMessenger . of ( context ) . showSnackBar (
SnackBar (
content: Text ( " Gioca per raggiungere il Liv. $ requiredLevel e sbloccare questo tema! " , style: const TextStyle ( fontWeight: FontWeight . bold , color: Colors . white ) ) ,
backgroundColor: Colors . redAccent ,
behavior: SnackBarBehavior . floating ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 10 ) ) ,
duration: const Duration ( seconds: 2 ) ,
)
) ;
return ;
}
2026-02-27 23:35:54 +01:00
themeManager . setTheme ( type ) ;
Navigator . pop ( context ) ;
} ,
child: AnimatedContainer (
duration: const Duration ( milliseconds: 300 ) ,
2026-03-15 17:00:01 +01:00
height: 140 * vScale , // Leggermente più alto per ospitare la cornice del testo
padding: EdgeInsets . symmetric ( horizontal: 20 * vScale , vertical: 15 * vScale ) ,
2026-02-27 23:35:54 +01:00
decoration: BoxDecoration (
2026-03-15 17:00:01 +01:00
color: isLocked ? Colors . black87 : previewColors . background ,
2026-02-27 23:35:54 +01:00
borderRadius: BorderRadius . circular ( 20 ) ,
2026-03-15 17:00:01 +01:00
border: border ,
boxShadow: shadows , // L'ombra per lo spessore viene applicata qui
image: bgImage ! = null ? DecorationImage (
image: AssetImage ( bgImage ! ) ,
fit: BoxFit . cover ,
// Scuriamo leggermente di più le card per far risaltare la targa chiara
colorFilter: type = = AppThemeType . doodle
? ColorFilter . mode ( Colors . white . withOpacity ( isLocked ? 0.9 : 0.4 ) , BlendMode . lighten )
: ColorFilter . mode ( Colors . black . withOpacity ( isLocked ? 0.85 : 0.5 ) , BlendMode . darken ) ,
) : null ,
2026-02-27 23:35:54 +01:00
) ,
2026-03-01 20:59:06 +01:00
child: Stack (
alignment: Alignment . center ,
2026-02-27 23:35:54 +01:00
children: [
2026-03-01 20:59:06 +01:00
Opacity (
2026-03-15 17:00:01 +01:00
opacity: isLocked ? 0.3 : 1.0 ,
2026-03-01 20:59:06 +01:00
child: Row (
2026-03-15 17:00:01 +01:00
crossAxisAlignment: CrossAxisAlignment . center ,
2026-02-27 23:35:54 +01:00
children: [
2026-03-01 20:59:06 +01:00
Expanded (
2026-03-15 17:00:01 +01:00
// --- LA NUOVA CORNICE CHIARA PER IL TESTO ---
child: Container (
padding: EdgeInsets . symmetric ( horizontal: 15 * vScale , vertical: 10 * vScale ) ,
decoration: BoxDecoration (
color: Colors . white . withOpacity ( 0.9 ) , // Sfondo chiaro semitrasparente
borderRadius: BorderRadius . circular ( 12 ) ,
border: Border . all ( color: Colors . white , width: 2 ) ,
boxShadow: [
BoxShadow ( color: Colors . black . withOpacity ( 0.3 ) , blurRadius: 8 , offset: const Offset ( 2 , 4 ) )
]
) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisAlignment: MainAxisAlignment . center ,
mainAxisSize: MainAxisSize . min ,
children: [
FittedBox (
fit: BoxFit . scaleDown ,
alignment: Alignment . centerLeft ,
child: Text (
title ,
style: getSharedTextStyle (
type ,
TextStyle (
fontSize: ( type = = AppThemeType . arcade ? 15 : 22 ) * vScale ,
fontWeight: FontWeight . w900 ,
color: const Color ( 0xFF111122 ) , // Testo scuro per contrastare lo sfondo chiaro
letterSpacing: type = = AppThemeType . music ? 1.5 : 0.5 ,
)
)
) ,
) ,
SizedBox ( height: 4 * vScale ) ,
FittedBox (
fit: BoxFit . scaleDown ,
alignment: Alignment . centerLeft ,
child: Text (
subtitle ,
style: getSharedTextStyle (
type ,
TextStyle (
fontSize: ( type = = AppThemeType . arcade ? 8 : 12 ) * vScale ,
color: const Color ( 0xFF333344 ) , // Grigio scuro per il sottotitolo
fontWeight: FontWeight . bold ,
)
)
) ,
) ,
] ,
) ,
2026-03-01 20:59:06 +01:00
) ,
) ,
2026-03-15 17:00:01 +01:00
SizedBox ( width: 15 * vScale ) ,
Container (
width: 28 * vScale , height: 28 * vScale ,
decoration: BoxDecoration (
color: previewColors . playerRed ,
shape: type = = AppThemeType . arcade ? BoxShape . rectangle : BoxShape . circle ,
border: Border . all ( color: Colors . white , width: 2 ) , // Bordino bianco per far risaltare il colore
boxShadow: [ BoxShadow ( color: Colors . black . withOpacity ( 0.5 ) , blurRadius: 5 , offset: const Offset ( 1 , 2 ) ) ]
)
) ,
SizedBox ( width: 12 * vScale ) ,
Container (
width: 28 * vScale , height: 28 * vScale ,
decoration: BoxDecoration (
color: previewColors . playerBlue ,
shape: type = = AppThemeType . arcade ? BoxShape . rectangle : BoxShape . circle ,
border: Border . all ( color: Colors . white , width: 2 ) , // Bordino bianco per far risaltare il colore
boxShadow: [ BoxShadow ( color: Colors . black . withOpacity ( 0.5 ) , blurRadius: 5 , offset: const Offset ( 1 , 2 ) ) ]
)
) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
2026-03-01 20:59:06 +01:00
if ( isLocked )
Container (
2026-03-15 17:00:01 +01:00
padding: EdgeInsets . symmetric ( horizontal: 16 * vScale , vertical: 10 * vScale ) ,
2026-03-01 20:59:06 +01:00
decoration: BoxDecoration (
2026-03-15 17:00:01 +01:00
color: Colors . black . withOpacity ( 0.95 ) ,
2026-03-01 20:59:06 +01:00
borderRadius: BorderRadius . circular ( 20 ) ,
2026-03-15 17:00:01 +01:00
border: Border . all ( color: previewColors . playerRed . withOpacity ( 0.8 ) , width: 2 ) ,
boxShadow: [ BoxShadow ( color: previewColors . playerRed . withOpacity ( 0.5 ) , blurRadius: 20 ) ] ,
2026-03-01 20:59:06 +01:00
) ,
child: Row (
mainAxisSize: MainAxisSize . min ,
children: [
2026-03-15 17:00:01 +01:00
Icon ( Icons . lock_rounded , color: Colors . white , size: 20 * vScale ) ,
SizedBox ( width: 8 * vScale ) ,
2026-03-01 20:59:06 +01:00
Text (
" LIV. $ requiredLevel " ,
2026-03-15 17:00:01 +01:00
style: getSharedTextStyle ( type , TextStyle ( color: Colors . white , fontWeight: FontWeight . w900 , fontSize: 16 * vScale , letterSpacing: 2 ) )
2026-03-01 20:59:06 +01:00
) ,
] ,
) ,
) ,
2026-02-27 23:35:54 +01:00
] ,
) ,
) ,
) ;
}
}