From 518cb22ec4fb054d3f75c3e8473a60785680f4ff Mon Sep 17 00:00:00 2001 From: Paolo Date: Fri, 13 Mar 2026 22:00:00 +0100 Subject: [PATCH] Auto-sync: 20260313_220000 --- ios/.DS_Store | Bin 8196 -> 8196 bytes lib/core/app_colors.dart | 16 +-- lib/logic/game_controller.dart | 48 +++++-- lib/services/audio_service.dart | 28 +--- lib/services/storage_service.dart | 20 +-- lib/ui/game/board_painter.dart | 5 +- lib/ui/game/game_screen.dart | 58 ++++---- lib/ui/home/home_screen.dart | 196 ++++++++++++++++----------- lib/ui/settings/settings_screen.dart | 21 +-- 9 files changed, 206 insertions(+), 186 deletions(-) diff --git a/ios/.DS_Store b/ios/.DS_Store index 1713241074a633eda7cd237ba229e9e69a4d2e02..cab77ac2d01a85258a5566dcfca6e01acd013bee 100644 GIT binary patch delta 36 ocmZp1XmQxEK#1eHo^eI!rsIy23xw1rpA+I@g9vQCC#1#;01W>Q9RL6T delta 36 ucmV+<0NekBK!iZBpb`k~=f~`*vByD^a1thy&Jqd+)tg@9sIjx&5+)5z=@EJW diff --git a/lib/core/app_colors.dart b/lib/core/app_colors.dart index b0e6603..56c4a96 100644 --- a/lib/core/app_colors.dart +++ b/lib/core/app_colors.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -enum AppThemeType { minimal, doodle, cyberpunk, wood, arcade, grimorio } +enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio } class ThemeColors { final Color background; @@ -24,11 +24,6 @@ class ThemeColors { } class AppColors { - static const ThemeColors minimal = ThemeColors( - background: Color(0xFFF5F7FA), gridLine: Color(0xFFCFD8DC), - playerRed: Color(0xFFE53935), playerBlue: Color(0xFF1E88E5), text: Color(0xFF263238), - ); - static const ThemeColors doodle = ThemeColors( background: Color(0xFFFFF9E6), gridLine: Color(0xFFB0BEC5), playerRed: Color(0xFFD32F2F), playerBlue: Color(0xFF1565C0), text: Color(0xFF37474F), @@ -56,10 +51,9 @@ class AppColors { static ThemeColors getTheme(AppThemeType type) { switch (type) { - case AppThemeType.minimal: return minimal; case AppThemeType.doodle: return doodle; - case AppThemeType.cyberpunk: return cyberpunk; case AppThemeType.wood: return wood; + case AppThemeType.cyberpunk: return cyberpunk; case AppThemeType.arcade: return arcade; case AppThemeType.grimorio: return grimorio; } @@ -69,7 +63,6 @@ class AppColors { class ThemeIcons { static IconData gold(AppThemeType type) { switch (type) { - case AppThemeType.minimal: return Icons.star_rounded; case AppThemeType.doodle: return FontAwesomeIcons.star; case AppThemeType.wood: return FontAwesomeIcons.gem; case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip; @@ -80,7 +73,6 @@ class ThemeIcons { static IconData bomb(AppThemeType type) { switch (type) { - case AppThemeType.minimal: return Icons.mood_bad_rounded; case AppThemeType.doodle: return FontAwesomeIcons.virus; case AppThemeType.wood: return FontAwesomeIcons.fire; case AppThemeType.cyberpunk: return FontAwesomeIcons.bug; @@ -91,7 +83,6 @@ class ThemeIcons { static IconData swap(AppThemeType type) { switch (type) { - case AppThemeType.minimal: return Icons.sync_rounded; case AppThemeType.doodle: return FontAwesomeIcons.arrowsRotate; case AppThemeType.wood: return FontAwesomeIcons.rightLeft; case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired; @@ -102,7 +93,6 @@ class ThemeIcons { static IconData joker(AppThemeType type) { switch (type) { - case AppThemeType.minimal: return Icons.sentiment_satisfied_alt; case AppThemeType.doodle: return FontAwesomeIcons.faceSmileBeam; case AppThemeType.wood: return FontAwesomeIcons.key; case AppThemeType.cyberpunk: return FontAwesomeIcons.robot; @@ -113,7 +103,6 @@ class ThemeIcons { static IconData block(AppThemeType type) { switch (type) { - case AppThemeType.minimal: return Icons.block; case AppThemeType.doodle: return FontAwesomeIcons.squareXmark; case AppThemeType.wood: return FontAwesomeIcons.ban; case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved; @@ -122,7 +111,6 @@ class ThemeIcons { } } - // --- NUOVE ICONE --- static IconData ice(AppThemeType type) { return FontAwesomeIcons.snowflake; } diff --git a/lib/logic/game_controller.dart b/lib/logic/game_controller.dart index 5116b18..46ce077 100644 --- a/lib/logic/game_controller.dart +++ b/lib/logic/game_controller.dart @@ -17,6 +17,12 @@ import '../services/storage_service.dart'; import '../services/multiplayer_service.dart'; import '../core/app_colors.dart'; +class CpuMatchSetup { + final int radius; + final ArenaShape shape; + CpuMatchSetup(this.radius, this.shape); +} + class GameController extends ChangeNotifier { late GameBoard board; bool isVsCPU = false; @@ -50,11 +56,9 @@ class GameController extends ChangeNotifier { bool opponentWantsRematch = false; int lastMatchXP = 0; - // --- VARIABILI PER IL LEVEL UP --- bool hasLeveledUp = false; int newlyReachedLevel = 1; List unlockedFeatures = []; - // --------------------------------- bool isSetupPhase = true; bool myJokerPlaced = false; @@ -67,7 +71,7 @@ class GameController extends ChangeNotifier { int cpuLevel = 1; int currentMatchLevel = 1; int? currentSeed; - AppThemeType _activeTheme = AppThemeType.cyberpunk; + AppThemeType _activeTheme = AppThemeType.doodle; String onlineHostName = "ROSSO"; String onlineGuestName = "BLU"; @@ -78,6 +82,25 @@ class GameController extends ChangeNotifier { startNewGame(radius); } + CpuMatchSetup _getSetupForCpuLevel(int level) { + final rand = Random(); + if (level == 1) return CpuMatchSetup(3, ArenaShape.classic); + if (level == 2) return CpuMatchSetup(4, ArenaShape.classic); + if (level == 3) return CpuMatchSetup(4, ArenaShape.cross); + if (level == 4) return CpuMatchSetup(4, ArenaShape.donut); + if (level == 5) return CpuMatchSetup(5, ArenaShape.classic); + if (level == 6) return CpuMatchSetup(4, ArenaShape.hourglass); + if (level == 7) return CpuMatchSetup(5, ArenaShape.cross); + if (level == 8) return CpuMatchSetup(5, ArenaShape.donut); + if (level == 9) return CpuMatchSetup(5, ArenaShape.hourglass); + + List hardShapes = [ArenaShape.classic, ArenaShape.cross, ArenaShape.donut, ArenaShape.hourglass, ArenaShape.chaos]; + ArenaShape chosenShape = hardShapes[rand.nextInt(hardShapes.length)]; + + int chosenRadius = (chosenShape == ArenaShape.chaos) ? (rand.nextInt(2) + 4) : (rand.nextInt(2) + 5); + return CpuMatchSetup(chosenRadius, chosenShape); + } + void startNewGame(int radius, {bool vsCPU = false, bool isOnline = false, String? roomCode, bool isHost = false, ArenaShape shape = ArenaShape.classic, bool timeMode = true}) { _onlineSubscription?.cancel(); _onlineSubscription = null; @@ -87,7 +110,6 @@ class GameController extends ChangeNotifier { _hasSavedResult = false; lastMatchXP = 0; - // Reset Level Up vars hasLeveledUp = false; unlockedFeatures.clear(); @@ -108,10 +130,19 @@ class GameController extends ChangeNotifier { this.isHost = isHost; this.isTimeMode = timeMode; - onlineShape = shape; + int finalRadius = radius; + ArenaShape finalShape = shape; + + if (this.isVsCPU) { + CpuMatchSetup setup = _getSetupForCpuLevel(cpuLevel); + finalRadius = setup.radius; + finalShape = setup.shape; + } + + onlineShape = finalShape; int levelToUse = isOnline ? (currentMatchLevel == 1 ? 2 : currentMatchLevel) : cpuLevel; - board = GameBoard(radius: radius, level: levelToUse, seed: currentSeed, shape: onlineShape); + board = GameBoard(radius: finalRadius, level: levelToUse, seed: currentSeed, shape: finalShape); board.currentPlayer = Player.red; isCPUThinking = false; @@ -513,12 +544,10 @@ class GameController extends ChangeNotifier { } } - // --- LOGICA LEVEL UP E SBLOCCHI --- List _getUnlocks(int oldLevel, int newLevel) { List unlocks = []; for(int i = oldLevel + 1; i <= newLevel; i++) { if (i == 3) unlocks.add("Tema: Legno & Fiammiferi"); - if (i == 5) unlocks.add("Tema: Quaderno (Doodle)"); if (i == 7) unlocks.add("Tema: Cyberpunk"); if (i == 10) { unlocks.add("Tema: 8-Bit Arcade"); @@ -538,7 +567,7 @@ class GameController extends ChangeNotifier { String myRealName = StorageService.instance.playerName; if (myRealName.isEmpty) myRealName = "IO"; - int oldLevel = StorageService.instance.playerLevel; // Salviamo il vecchio livello + int oldLevel = StorageService.instance.playerLevel; if (isOnline) { bool isWin = isHost ? board.scoreRed > board.scoreBlue : board.scoreBlue > board.scoreRed; @@ -574,7 +603,6 @@ class GameController extends ChangeNotifier { lastMatchXP = calculatedXP; StorageService.instance.addXP(calculatedXP); - // --- CONTROLLO LEVEL UP DOPO AVER DATO GLI XP --- int newLevel = StorageService.instance.playerLevel; if (newLevel > oldLevel) { hasLeveledUp = true; diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index 75a79c7..b49f706 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -15,46 +15,35 @@ class AudioService extends ChangeNotifier { final AudioPlayer _sfxPlayer = AudioPlayer(); final AudioPlayer _bgmPlayer = AudioPlayer(); - // Teniamo traccia del tema attuale per gestire bene quando togliamo il muto - AppThemeType _currentTheme = AppThemeType.cyberpunk; + AppThemeType _currentTheme = AppThemeType.doodle; Future init() async { - // 1. Carica la preferenza salvata final prefs = await SharedPreferences.getInstance(); isMuted = prefs.getBool('isMuted') ?? false; - - // 2. Imposta la musica in loop infinito await _bgmPlayer.setReleaseMode(ReleaseMode.loop); } void toggleMute() async { isMuted = !isMuted; - - // Salva la scelta per il prossimo riavvio final prefs = await SharedPreferences.getInstance(); await prefs.setBool('isMuted', isMuted); if (isMuted) { await _bgmPlayer.pause(); } else { - // Se togliamo il muto, facciamo ripartire la canzone del tema attuale playBgm(_currentTheme); } notifyListeners(); } - // --- BGM (Musica di sottofondo) --- Future playBgm(AppThemeType theme) async { - _currentTheme = theme; // Aggiorna sempre la memoria del tema - - // FERMA SEMPRE LA TRACCIA PRECEDENTE prima di far partire la nuova + _currentTheme = theme; await _bgmPlayer.stop(); if (isMuted) return; String audioPath = ''; - // Assegna a ogni tema la sua colonna sonora switch (theme) { case AppThemeType.cyberpunk: audioPath = 'audio/bgm/Cyber_Dystopia.mp3'; @@ -71,15 +60,10 @@ class AudioService extends ChangeNotifier { case AppThemeType.grimorio: audioPath = 'audio/bgm/Grimorio_Astral.mp3'; break; - case AppThemeType.minimal: - // Il tema minimal non ha musica (silenzio/focus) - audioPath = ''; - break; } if (audioPath.isNotEmpty) { try { - // IL VOLUME VA PASSATO QUI (0.15 = 15%), sennò viene ignorato! await _bgmPlayer.play(AssetSource(audioPath), volume: 0.15); } catch (e) { debugPrint("Errore riproduzione BGM: $e"); @@ -91,12 +75,10 @@ class AudioService extends ChangeNotifier { await _bgmPlayer.stop(); } - // --- SFX (Effetti sonori) --- void playLineSfx(AppThemeType theme) async { if (isMuted) return; String file = ''; switch (theme) { - case AppThemeType.minimal: case AppThemeType.arcade: file = 'minimal_line.wav'; break; case AppThemeType.doodle: @@ -109,10 +91,9 @@ class AudioService extends ChangeNotifier { if (file.isNotEmpty) { try { - // Effetti sonori forzati al 100% await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0); } catch (e) { - debugPrint("Errore SFX Linea non trovato: $file"); + debugPrint("Errore SFX Linea: $file"); } } } @@ -121,7 +102,6 @@ class AudioService extends ChangeNotifier { if (isMuted) return; String file = ''; switch (theme) { - case AppThemeType.minimal: case AppThemeType.arcade: file = 'minimal_box.wav'; break; case AppThemeType.doodle: @@ -136,7 +116,7 @@ class AudioService extends ChangeNotifier { try { await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0); } catch (e) { - debugPrint("Errore SFX Box non trovato: $file"); + debugPrint("Errore SFX Box: $file"); } } } diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 84d3db6..42b5b8e 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -17,10 +17,11 @@ class StorageService { Future init() async { _prefs = await SharedPreferences.getInstance(); - _checkDailyQuests(); // All'avvio controlliamo se ci sono nuove sfide + _checkDailyQuests(); } - int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.minimal.index; + // Doodle è il nuovo tema di partenza (index 0) + int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.doodle.index; Future saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index); int get savedRadius => _prefs.getInt('radius') ?? 2; @@ -31,7 +32,6 @@ class StorageService { int get totalXP => _prefs.getInt('totalXP') ?? 0; - // Modificato per sincronizzare automaticamente la classifica su Firebase Future addXP(int xp) async { await _prefs.setInt('totalXP', totalXP + xp); syncLeaderboard(); @@ -54,20 +54,17 @@ class StorageService { String get playerName => _prefs.getString('playerName') ?? ''; Future savePlayerName(String name) async { await _prefs.setString('playerName', name); - syncLeaderboard(); // Aggiorna il nome in classifica + syncLeaderboard(); } - // --- NUOVO: SINCRONIZZAZIONE CLASSIFICA ONLINE --- Future syncLeaderboard() async { if (playerName.isNotEmpty) { try { - // Recuperiamo il nostro ID segreto e univoco appena creato final user = FirebaseAuth.instance.currentUser; if (user != null) { - // Usiamo user.uid come nome del documento, NON più il playerName! await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({ - 'name': playerName, // Il nome rimane dentro per mostrarlo nella lista + 'name': playerName, 'xp': totalXP, 'level': playerLevel, 'wins': wins, @@ -75,32 +72,26 @@ class StorageService { }, SetOptions(merge: true)); } } catch(e) { - // Ignoriamo gli errori se manca la rete, si sincronizzerà dopo debugPrint("Errore sinc. classifica: $e"); } } } - // --- NUOVO: GESTIONE SFIDE GIORNALIERE --- void _checkDailyQuests() { String today = DateTime.now().toIso8601String().substring(0, 10); String lastDate = _prefs.getString('quest_date') ?? ''; if (today != lastDate) { - // Nuovo giorno, nuove sfide! _prefs.setString('quest_date', today); - // Sfida 1: Gioca partite online _prefs.setInt('q1_type', 0); _prefs.setInt('q1_prog', 0); _prefs.setInt('q1_target', 3); - // Sfida 2: Vinci contro la CPU _prefs.setInt('q2_type', 1); _prefs.setInt('q2_prog', 0); _prefs.setInt('q2_target', 2); - // Sfida 3: Partite con forme speciali (Croce, Caos, ecc) _prefs.setInt('q3_type', 2); _prefs.setInt('q3_prog', 0); _prefs.setInt('q3_target', 2); @@ -119,7 +110,6 @@ class StorageService { } } - // --- STORICO PARTITE --- List> get matchHistory { List history = _prefs.getStringList('matchHistory') ?? []; return history.map((e) => jsonDecode(e) as Map).toList(); diff --git a/lib/ui/game/board_painter.dart b/lib/ui/game/board_painter.dart index ff8f6ce..ef8a5e3 100644 --- a/lib/ui/game/board_painter.dart +++ b/lib/ui/game/board_painter.dart @@ -66,7 +66,6 @@ class BoardPainter extends CustomPainter { continue; } - // Sfondo azzurrino se è di ghiaccio (anche prima di chiuderla) if (box.type == BoxType.ice && box.owner == Player.none) { canvas.drawRect(rect.deflate(2.0), Paint()..color = Colors.cyanAccent.withOpacity(0.05)..style=PaintingStyle.fill); } @@ -127,10 +126,9 @@ class BoardPainter extends CustomPainter { Offset p1 = getScreenPos(line.p1.x, line.p1.y); Offset p2 = getScreenPos(line.p2.x, line.p2.y); - // --- DISEGNO DELLA LINEA "INCRINATA" DAL GHIACCIO --- if (line.isIceCracked) { _drawCrackedIceLine(canvas, p1, p2, blinkValue); - continue; // Non ha ancora un proprietario, passiamo alla prossima! + continue; } bool isLastMove = (line == board.lastMove); @@ -220,7 +218,6 @@ class BoardPainter extends CustomPainter { ..strokeCap = StrokeCap.round ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0); - // Effetto linea frammentata canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0); Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy); diff --git a/lib/ui/game/game_screen.dart b/lib/ui/game/game_screen.dart index 9b3a00b..186206b 100644 --- a/lib/ui/game/game_screen.dart +++ b/lib/ui/game/game_screen.dart @@ -41,7 +41,6 @@ class _GameScreenState extends State with TickerProviderStateMixin { bool _gameOverDialogShown = false; bool _opponentLeftDialogShown = false; - // Variabili per coprire il posizionamento del Jolly in Locale bool _hideJokerMessage = false; bool _wasSetupPhase = false; Player _lastJokerTurn = Player.red; @@ -100,7 +99,6 @@ class _GameScreenState extends State with TickerProviderStateMixin { Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration(color: theme.text.withOpacity(0.05), borderRadius: BorderRadius.circular(15)), - // AGGIUNTO FITTEDBOX QUI child: FittedBox( fit: BoxFit.scaleDown, child: Row( @@ -196,8 +194,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { titleText = "Nascondi il tuo Jolly!"; subtitleText = "(Tocca qui per nascondere)"; } else { - // --- TESTI MODALITÀ LOCALE --- - String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU"); + String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? "VERDE" : "BLU"); titleText = "TURNO GIOCATORE $pName"; subtitleText = "Passa il dispositivo.\nL'avversario NON deve guardare!\n\n(Tocca qui quando sei pronto)"; } @@ -207,7 +204,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.yellowAccent : theme.playerBlue, size: 50), + Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.yellowAccent : theme.playerBlue, size: 50), const SizedBox(height: 15), Text( titleText, @@ -243,7 +240,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { } else if (themeType == AppThemeType.grimorio) { return Container(decoration: BoxDecoration(color: const Color(0xFF2C1E3D), borderRadius: BorderRadius.circular(30), border: Border.all(color: const Color(0xFFBCAAA4), width: 3), boxShadow: [BoxShadow(color: Colors.deepPurpleAccent.withOpacity(0.5), blurRadius: 20, spreadRadius: 5)]), child: content); } else { - return Container(decoration: BoxDecoration(color: theme.background, borderRadius: BorderRadius.circular(20), border: Border.all(color: theme.gridLine, width: 2), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.15), blurRadius: 20, offset: const Offset(0, 10))]), child: content); + return Container(decoration: BoxDecoration(color: theme.background.withOpacity(0.95), borderRadius: BorderRadius.circular(20), border: Border.all(color: theme.gridLine.withOpacity(0.5), width: 2), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 10))]), child: content); } } @@ -254,13 +251,10 @@ class _GameScreenState extends State with TickerProviderStateMixin { final theme = themeManager.currentColors; final gameController = context.watch(); - // --- LOGICA CAMBIO TURNO E SCHERMATA JOLLY --- if (gameController.isSetupPhase && !_wasSetupPhase) { - // È appena iniziata una nuova partita _hideJokerMessage = false; _lastJokerTurn = Player.red; } else if (gameController.isSetupPhase && gameController.jokerTurn != _lastJokerTurn) { - // È cambiato il turno durante il setup (in modalità locale), rifacciamo apparire la copertura _hideJokerMessage = false; _lastJokerTurn = gameController.jokerTurn; } @@ -296,7 +290,7 @@ class _GameScreenState extends State with TickerProviderStateMixin { if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg'; if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; - Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.white : Colors.black; + Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : Colors.black; Widget emojiBar = const SizedBox(); if (gameController.isOnline && !gameController.isGameOver) { @@ -304,9 +298,9 @@ class _GameScreenState extends State with TickerProviderStateMixin { emojiBar = Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration( - color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8), + color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(30), - border: Border.all(color: themeType == AppThemeType.cyberpunk ? theme.playerBlue.withOpacity(0.3) : Colors.black12, width: 2), + border: Border.all(color: themeType == AppThemeType.cyberpunk ? theme.playerBlue.withOpacity(0.3) : Colors.white24, width: 2), ), child: Row( mainAxisSize: MainAxisSize.min, @@ -388,10 +382,10 @@ class _GameScreenState extends State with TickerProviderStateMixin { Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.4), offset: const Offset(0, 4), blurRadius: 5)]), child: TextButton.icon( - style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))), - icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, size: 20), + style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))), + icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : theme.text, size: 20), onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(context); }, - label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))), + label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))), ), ), ], @@ -412,11 +406,21 @@ class _GameScreenState extends State with TickerProviderStateMixin { canPop: true, onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); }, child: Scaffold( - backgroundColor: bgImage != null ? Colors.transparent : theme.background, - body: CustomPaint( - painter: themeType == AppThemeType.minimal ? FullScreenGridPainter(Colors.black.withOpacity(0.06)) : null, - child: Container( - decoration: bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover, colorFilter: themeType == AppThemeType.doodle ? ColorFilter.mode(Colors.white.withOpacity(0.7), BlendMode.lighten) : null)) : null, + // L'impostazione dello sfondo dipende dal tema + backgroundColor: themeType == AppThemeType.aurora ? Colors.transparent : (bgImage != null ? Colors.transparent : theme.background), + body: Container( + // GRADIENTE AURORA IN BACKGROUND! + decoration: themeType == AppThemeType.aurora + ? const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF2A0845), Color(0xFFDD2476), Color(0xFFFF512F)], + ), + ) + : (bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover, colorFilter: themeType == AppThemeType.doodle ? ColorFilter.mode(Colors.white.withOpacity(0.7), BlendMode.lighten) : null)) : null), + child: CustomPaint( + // painter rimosso per supportare il nuovo sfondo a gradiente child: Stack( children: [ if (gameController.isTimeMode && !gameController.isCPUThinking && !gameController.isGameOver && gameController.timeLeft > 0 && gameController.timeLeft <= 5 && !gameController.isSetupPhase) @@ -427,13 +431,11 @@ class _GameScreenState extends State with TickerProviderStateMixin { Positioned.fill(child: gameContent), - // --- SCHERMATA COPRENTE PER IL PASSAGGIO DEL TELEFONO IN LOCALE --- if (gameController.isSetupPhase && !_hideJokerMessage) Positioned.fill( child: Container( - // Il colore di sfondo riempie tutto lo schermo per non far sbirciare la griglia - color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade - ? Colors.black + color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora + ? Colors.black.withOpacity(0.98) : theme.background.withOpacity(0.98), child: Center( child: Padding( @@ -518,7 +520,7 @@ class _WinnerVFXOverlayState extends State with SingleTickerPr } void _initParticles(Size screenSize) { - int particleCount = widget.themeType == AppThemeType.cyberpunk ? 150 : 100; + int particleCount = widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.aurora ? 150 : 100; if (widget.themeType == AppThemeType.arcade) particleCount = 80; if (widget.themeType == AppThemeType.grimorio) particleCount = 120; @@ -528,6 +530,7 @@ class _WinnerVFXOverlayState extends State with SingleTickerPr else if (widget.themeType == AppThemeType.wood) { palette = [Colors.orangeAccent, Colors.yellow, Colors.red, Colors.white]; } else if (widget.themeType == AppThemeType.arcade) { palette = [widget.winnerColor, Colors.white, Colors.greenAccent]; } else if (widget.themeType == AppThemeType.grimorio) { palette = [widget.winnerColor, Colors.deepPurpleAccent, Colors.white]; } + else if (widget.themeType == AppThemeType.aurora) { palette = [widget.winnerColor, Colors.white, Colors.orangeAccent, Colors.pinkAccent]; } for (int i = 0; i < particleCount; i++) { double speed = _rand.nextDouble() * 20 + 5; @@ -540,7 +543,7 @@ class _WinnerVFXOverlayState extends State with SingleTickerPr setState(() { for (var p in _particles) { p.x += p.vx; p.y += p.vy; - if (widget.themeType == AppThemeType.cyberpunk) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; } + if (widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.aurora) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; } else if (widget.themeType == AppThemeType.wood) { p.vy -= 0.2; p.x += math.sin(p.y * 0.05) * 2; } else if (widget.themeType == AppThemeType.arcade) { p.vy += 0.3; p.spin = 0; p.angle = 0; } else if (widget.themeType == AppThemeType.grimorio) { p.vy -= 0.1; p.x += math.sin(p.y * 0.02) * 1.5; p.size *= 0.995; } @@ -563,7 +566,7 @@ class _VFXPainter extends CustomPainter { for (var p in particles) { if (p.size < 0.5) continue; final paint = Paint()..color = p.color..style = PaintingStyle.fill; - if (themeType == AppThemeType.cyberpunk) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); } + if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.aurora) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); } canvas.save(); canvas.translate(p.x, p.y); canvas.rotate(p.angle); if (themeType == AppThemeType.doodle) { @@ -600,6 +603,7 @@ class _BouncingEmojiState extends State<_BouncingEmoji> with SingleTickerProvide @override Widget build(BuildContext context) { return AnimatedBuilder(animation: _anim, builder: (ctx, child) => Transform.translate(offset: Offset(0, _anim.value), child: Container(padding: const EdgeInsets.all(8), decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle, boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 5)]), child: Text(widget.emoji, style: const TextStyle(fontSize: 32))))); } } +// Mantengo la definizione per evitare buchi, ma ora Aurora usa il LinearGradient! 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); } diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 11d5916..1499cdb 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -405,13 +405,13 @@ class _HomeScreenState extends State with WidgetsBindingObserver { StorageService.instance.syncLeaderboard(); }); _checkClipboardForInvite(); - _initDeepLinks(); // <--- AGGIUNGI QUESTO + _initDeepLinks(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); - _linkSubscription?.cancel(); // <--- AGGIUNGI QUESTO + _linkSubscription?.cancel(); super.dispose(); } @@ -541,13 +541,11 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); } - // --- LOGICA DEEP LINKS --- Future _initDeepLinks() async { _appLinks = AppLinks(); - // 1. Controlla se l'app è stata aperta DA CHIUSA tramite un link try { - final initialUri = await _appLinks.getInitialLink(); // <--- ECCO LA PAROLA CORRETTA! + final initialUri = await _appLinks.getInitialLink(); if (initialUri != null) { _handleDeepLink(initialUri); } @@ -555,7 +553,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { debugPrint("Errore lettura link iniziale: $e"); } - // 2. Rimane in ascolto se l'app era IN BACKGROUND _linkSubscription = _appLinks.uriLinkStream.listen((uri) { _handleDeepLink(uri); }, onError: (err) { @@ -568,7 +565,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { if (uri.scheme == 'tetraq' && uri.host == 'join') { String? code = uri.queryParameters['code']; if (code != null && code.length == 5) { - // Usa un piccolo delay per assicurarsi che l'app sia pronta Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { _promptJoinRoom(code.toUpperCase()); @@ -577,7 +573,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { } } } - // ------------------------- Future _checkClipboardForInvite() async { try { @@ -662,41 +657,68 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(isVsCPU ? loc.cpuTitle : loc.localTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2))), - const SizedBox(height: 25), - - Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), - const SizedBox(height: 15), - Wrap( - spacing: 12, runSpacing: 12, alignment: WrapAlignment.center, - children: [ - _NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)), - _NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)), - _NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)), - _NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)), - _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), - ], - ), - - const SizedBox(height: 25), - Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), - const SizedBox(height: 20), - - Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), - const SizedBox(height: 15), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)), - _NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)), - _NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)), - _NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), + 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: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2))), + ), + const SizedBox(width: 40), ], ), - const SizedBox(height: 25), - Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), - const SizedBox(height: 20), + + if (isVsCPU) ...[ + Icon(Icons.smart_toy, size: 50, color: inkColor.withOpacity(0.6)), + const SizedBox(height: 10), + Text("MODALITÀ CAMPAGNA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: inkColor))), + const SizedBox(height: 10), + Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: _getTextStyle(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), + ] else ...[ + Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), + const SizedBox(height: 15), + Wrap( + spacing: 12, runSpacing: 12, alignment: WrapAlignment.center, + children: [ + _NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)), + _NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)), + _NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)), + _NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)), + _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), + ], + ), + + const SizedBox(height: 25), + Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), + const SizedBox(height: 20), + + Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), + const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)), + _NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)), + _NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)), + _NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), + ], + ), + + const SizedBox(height: 25), + Divider(color: inkColor.withOpacity(0.3), thickness: 2.5), + const SizedBox(height: 20), + ], Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))), const SizedBox(height: 10), @@ -742,41 +764,68 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(isVsCPU ? loc.cpuTitle : loc.localTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))), - const SizedBox(height: 20), - - Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), - const SizedBox(height: 10), - Wrap( - spacing: 10, runSpacing: 10, alignment: WrapAlignment.center, - children: [ - _NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)), - _NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)), - _NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)), - _NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)), - _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), - ], - ), - - const SizedBox(height: 20), - Divider(color: Colors.white.withOpacity(0.05), thickness: 2), - const SizedBox(height: 20), - - Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), - const SizedBox(height: 10), Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)), - _NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)), - _NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)), - _NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), + 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: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))), + ), + const SizedBox(width: 40), ], ), + const SizedBox(height: 20), - const SizedBox(height: 20), - Divider(color: Colors.white.withOpacity(0.05), thickness: 2), - const SizedBox(height: 20), + if (isVsCPU) ...[ + Icon(Icons.smart_toy, size: 50, color: theme.playerBlue), + const SizedBox(height: 10), + Text("MODALITÀ CAMPAGNA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), + const SizedBox(height: 10), + Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: _getTextStyle(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), + ] else ...[ + Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), + const SizedBox(height: 10), + Wrap( + spacing: 10, runSpacing: 10, alignment: WrapAlignment.center, + children: [ + _NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)), + _NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)), + _NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)), + _NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)), + _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)), + ], + ), + + const SizedBox(height: 20), + Divider(color: Colors.white.withOpacity(0.05), thickness: 2), + const SizedBox(height: 20), + + Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)), + _NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)), + _NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)), + _NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)), + ], + ), + + const SizedBox(height: 20), + Divider(color: Colors.white.withOpacity(0.05), thickness: 2), + const SizedBox(height: 20), + ], Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 10), @@ -813,7 +862,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); } - // --- NUOVA FUNZIONE: MOSTRA LE SFIDE GIORNALIERE --- Future _showDailyQuestsDialog() async { final prefs = await SharedPreferences.getInstance(); @@ -846,7 +894,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { Text(loc.questsTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), const SizedBox(height: 25), - // Generiamo dinamicamente le 3 missioni salvate in memoria ...List.generate(3, (index) { int i = index + 1; int type = prefs.getInt('q${i}_type') ?? 0; @@ -916,7 +963,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { ); } - // --- NUOVA FUNZIONE: MOSTRA LA CLASSIFICA GLOBALE --- void _showLeaderboardDialog() { showDialog( context: context, @@ -943,7 +989,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { Text(loc.leaderboardTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))), const SizedBox(height: 20), - // Lista giocatori pescata da Firebase! SizedBox( height: 350, child: StreamBuilder( @@ -959,7 +1004,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { final docs = snapshot.data!.docs; return ListView.builder( physics: const BouncingScrollPhysics(), - itemCount: docs.length, // <--- ECCO LA RIGA MAGICA AGGIUNTA! + itemCount: docs.length, itemBuilder: (context, index) { var data = docs[index].data() as Map; @@ -1320,7 +1365,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { const Spacer(), - // --- NUOVA LISTA BOTTONI CON CLASSIFICHE E SFIDE --- + // --- NUOVA LISTA BOTTONI --- Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -1331,7 +1376,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { _buildCyberCard(_FeatureCard(title: loc.localTitle, subtitle: loc.localSub, icon: Icons.people_alt, color: Colors.red.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), themeType), const SizedBox(height: 12), - // NUOVI BOTTONI PER LA VERSIONE 2.0 Row( children: [ Expanded(child: _buildCyberCard(_FeatureCard(title: loc.leaderboardTitle, subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: _showLeaderboardDialog, compact: true), themeType)), @@ -1457,7 +1501,6 @@ class _FeatureCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - // --- MAGIA QUI: FittedBox impedisce di andare a capo --- FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, @@ -1522,7 +1565,6 @@ class _FeatureCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - // --- MAGIA QUI: FittedBox impedisce di andare a capo --- FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, diff --git a/lib/ui/settings/settings_screen.dart b/lib/ui/settings/settings_screen.dart index 03a37d2..c0a9fc4 100644 --- a/lib/ui/settings/settings_screen.dart +++ b/lib/ui/settings/settings_screen.dart @@ -35,10 +35,10 @@ class _SettingsScreenState extends State { padding: const EdgeInsets.all(20), children: [ _ThemeCard( - title: "Minimal", - subtitle: "Linee pulite, sfondo chiaro", - type: AppThemeType.minimal, - previewColors: AppColors.minimal, + title: "Quaderno (Doodle)", + subtitle: "Sfondo a quadretti, tratto a penna", + type: AppThemeType.doodle, + previewColors: AppColors.doodle, requiredLevel: 1, currentLevel: playerLevel, ), @@ -52,15 +52,6 @@ class _SettingsScreenState extends State { currentLevel: playerLevel, ), const SizedBox(height: 15), - _ThemeCard( - title: "Quaderno (Doodle)", - subtitle: "Sfondo a quadretti, tratto a penna", - type: AppThemeType.doodle, - previewColors: AppColors.doodle, - requiredLevel: 5, - currentLevel: playerLevel, - ), - const SizedBox(height: 15), _ThemeCard( title: "Cyberpunk", subtitle: "Nero profondo, luci al neon", @@ -164,9 +155,9 @@ class _ThemeCard extends StatelessWidget { ], ), ), - Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerRed, shape: BoxShape.circle)), + Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerRed, shape: BoxShape.circle, boxShadow: [BoxShadow(color: previewColors.playerRed.withOpacity(0.5), blurRadius: 4)])), const SizedBox(width: 10), - Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerBlue, shape: BoxShape.circle)), + Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerBlue, shape: BoxShape.circle, boxShadow: [BoxShadow(color: previewColors.playerBlue.withOpacity(0.5), blurRadius: 4)])), ], ), ),