From 5aa831f750c49c4cf9627cb521c6d50956092be7 Mon Sep 17 00:00:00 2001 From: Paolo Date: Wed, 11 Mar 2026 22:00:01 +0100 Subject: [PATCH] Auto-sync: 20260311_220000 --- lib/services/audio_service.dart | 25 ++- lib/services/multiplayer_service.dart | 13 +- lib/ui/game/game_screen.dart | 7 +- lib/ui/game/score_board.dart | 10 +- lib/ui/multiplayer/lobby_screen.dart | 246 +++++++++++++++++++++++--- lib/widgets/game_over_dialog.dart | 14 +- 6 files changed, 277 insertions(+), 38 deletions(-) diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index aafcdbb..75a79c7 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -15,6 +15,9 @@ 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; + Future init() async { // 1. Carica la preferenza salvata final prefs = await SharedPreferences.getInstance(); @@ -32,16 +35,20 @@ class AudioService extends ChangeNotifier { await prefs.setBool('isMuted', isMuted); if (isMuted) { - _bgmPlayer.pause(); + await _bgmPlayer.pause(); } else { - _bgmPlayer.resume(); + // Se togliamo il muto, facciamo ripartire la canzone del tema attuale + playBgm(_currentTheme); } notifyListeners(); } // --- BGM (Musica di sottofondo) --- Future playBgm(AppThemeType theme) async { - await _bgmPlayer.stop(); // Ferma la canzone precedente + _currentTheme = theme; // Aggiorna sempre la memoria del tema + + // FERMA SEMPRE LA TRACCIA PRECEDENTE prima di far partire la nuova + await _bgmPlayer.stop(); if (isMuted) return; @@ -72,7 +79,8 @@ class AudioService extends ChangeNotifier { if (audioPath.isNotEmpty) { try { - await _bgmPlayer.play(AssetSource(audioPath), volume: 0.4); + // 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"); } @@ -101,7 +109,8 @@ class AudioService extends ChangeNotifier { if (file.isNotEmpty) { try { - await _sfxPlayer.play(AssetSource('audio/sfx/$file')); + // Effetti sonori forzati al 100% + await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0); } catch (e) { debugPrint("Errore SFX Linea non trovato: $file"); } @@ -125,7 +134,7 @@ class AudioService extends ChangeNotifier { if (file.isNotEmpty) { try { - await _sfxPlayer.play(AssetSource('audio/sfx/$file')); + await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0); } catch (e) { debugPrint("Errore SFX Box non trovato: $file"); } @@ -135,14 +144,14 @@ class AudioService extends ChangeNotifier { void playBonusSfx() async { if (isMuted) return; try { - await _sfxPlayer.play(AssetSource('audio/sfx/bonus.wav')); + await _sfxPlayer.play(AssetSource('audio/sfx/bonus.wav'), volume: 1.0); } catch(e) {} } void playBombSfx() async { if (isMuted) return; try { - await _sfxPlayer.play(AssetSource('audio/sfx/bomb.wav')); + await _sfxPlayer.play(AssetSource('audio/sfx/bomb.wav'), volume: 1.0); } catch(e) {} } } \ No newline at end of file diff --git a/lib/services/multiplayer_service.dart b/lib/services/multiplayer_service.dart index 6307a98..b601288 100644 --- a/lib/services/multiplayer_service.dart +++ b/lib/services/multiplayer_service.dart @@ -12,7 +12,7 @@ class MultiplayerService { CollectionReference get _gamesCollection => _firestore.collection('games'); - Future createGameRoom(int boardRadius, String hostName, String shapeName, bool isTimeMode) async { + Future createGameRoom(int boardRadius, String hostName, String shapeName, bool isTimeMode, {bool isPublic = true}) async { String roomCode = _generateRoomCode(); int randomSeed = Random().nextInt(1000000); @@ -28,7 +28,7 @@ class MultiplayerService { 'guestName': '', 'shape': shapeName, 'timeMode': isTimeMode, - // Nuovi campi per Emojis e Rivincita + 'isPublic': isPublic, 'p1_reaction': null, 'p2_reaction': null, 'p1_rematch': false, @@ -52,6 +52,14 @@ class MultiplayerService { return null; } + // QUERY BLINDATA: Chiede a Firebase solo le stanze waiting E pubbliche + Stream getPublicRooms() { + return _gamesCollection + .where('status', isEqualTo: 'waiting') + .where('isPublic', isEqualTo: true) + .snapshots(); + } + void shareInviteLink(String roomCode) { String message = "Ehi! Giochiamo a TetraQ? 🎮\nCopia questo intero messaggio e apri l'app per entrare direttamente, oppure inserisci manualmente il codice: $roomCode"; Share.share(message); @@ -69,7 +77,6 @@ class MultiplayerService { )); } - // --- NUOVI METODI PER REAZIONI E RIVINCITA --- Future sendReaction(String roomCode, bool isHost, String reaction) async { try { String prefix = isHost ? 'p1' : 'p2'; diff --git a/lib/ui/game/game_screen.dart b/lib/ui/game/game_screen.dart index c10cb96..9b3a00b 100644 --- a/lib/ui/game/game_screen.dart +++ b/lib/ui/game/game_screen.dart @@ -13,6 +13,7 @@ import '../../core/app_colors.dart'; import 'board_painter.dart'; import 'score_board.dart'; import 'package:google_fonts/google_fonts.dart'; +import '../../services/storage_service.dart'; TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) { if (themeType == AppThemeType.doodle) { @@ -74,7 +75,11 @@ class _GameScreenState extends State with TickerProviderStateMixin { int red = controller.board.scoreRed; int blue = controller.board.scoreBlue; bool playerBeatCPU = controller.isVsCPU && red > blue; - String nameRed = controller.isOnline ? controller.onlineHostName.toUpperCase() : "TU"; + + String myName = StorageService.instance.playerName.toUpperCase(); + if (myName.isEmpty) myName = "TU"; + + String nameRed = controller.isOnline ? controller.onlineHostName.toUpperCase() : myName; String nameBlue = controller.isOnline ? controller.onlineGuestName.toUpperCase() : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU"); if (controller.isVsCPU) nameBlue = "CPU"; diff --git a/lib/ui/game/score_board.dart b/lib/ui/game/score_board.dart index 39fe3a2..d4a0206 100644 --- a/lib/ui/game/score_board.dart +++ b/lib/ui/game/score_board.dart @@ -10,6 +10,7 @@ import '../../models/game_board.dart'; import '../../core/theme_manager.dart'; import '../../services/audio_service.dart'; import '../../core/app_colors.dart'; +import '../../services/storage_service.dart'; class ScoreBoard extends StatefulWidget { const ScoreBoard({super.key}); @@ -32,14 +33,17 @@ class _ScoreBoardState extends State { bool isRedTurn = controller.board.currentPlayer == Player.red; bool isMuted = AudioService.instance.isMuted; - String nameRed = "ROSSO"; - String nameBlue = themeType == AppThemeType.cyberpunk ? "VERDE" : "BLU"; + String myName = StorageService.instance.playerName.toUpperCase(); + if (myName.isEmpty) myName = "TU"; + + String nameRed = myName; + String nameBlue = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU"; if (controller.isOnline) { nameRed = controller.onlineHostName.toUpperCase(); nameBlue = controller.onlineGuestName.toUpperCase(); } else if (controller.isVsCPU) { - nameRed = "TU"; + nameRed = myName; nameBlue = "CPU"; } diff --git a/lib/ui/multiplayer/lobby_screen.dart b/lib/ui/multiplayer/lobby_screen.dart index b60b35f..b2f6a44 100644 --- a/lib/ui/multiplayer/lobby_screen.dart +++ b/lib/ui/multiplayer/lobby_screen.dart @@ -1,3 +1,7 @@ +// =========================================================================== +// FILE: lib/ui/multiplayer/lobby_screen.dart +// =========================================================================== + import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -240,14 +244,14 @@ class _NeonTimeSwitch extends StatelessWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 24), - const SizedBox(width: 12), + Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 20), + const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 14, letterSpacing: 2.0))), - Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 11, fontWeight: FontWeight.bold))), + Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))), + Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))), ], ), ], @@ -284,14 +288,107 @@ class _NeonTimeSwitch extends StatelessWidget { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 24), - const SizedBox(width: 12), + Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 20), + const SizedBox(width: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 13, letterSpacing: 1.5))), - Text(isTimeMode ? '15 sec a mossa' : 'Nessun limite', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 10, fontWeight: FontWeight.bold))), + Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))), + Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 9, fontWeight: FontWeight.bold))), + ], + ), + ], + ), + ), + ); + } +} + +class _NeonPrivacySwitch extends StatelessWidget { + final bool isPublic; + final ThemeColors theme; + final AppThemeType themeType; + final VoidCallback onTap; + + const _NeonPrivacySwitch({required this.isPublic, required this.theme, required this.themeType, required this.onTap}); + + @override + Widget build(BuildContext context) { + if (themeType == AppThemeType.doodle) { + Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600; + return Transform.rotate( + angle: 0.015, + child: GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0), + decoration: BoxDecoration( + color: isPublic ? doodleColor : Colors.white, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), topRight: Radius.circular(8), + bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15), + ), + border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5), + boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))), + Text(isPublic ? 'In Bacheca' : 'Solo Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))), + ], + ), + ], + ), + ), + ), + ); + } + + return GestureDetector( + onTap: onTap, + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: isPublic + ? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)] + : [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)], + ), + border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1), + boxShadow: isPublic + ? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)] + : [ + BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))), + Text(isPublic ? 'Tutti ti vedono' : 'Solo con Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold))), ], ), ], @@ -434,6 +531,7 @@ class _LobbyScreenState extends State { int _selectedRadius = 4; ArenaShape _selectedShape = ArenaShape.classic; bool _isTimeMode = true; + bool _isPublicRoom = true; // Di default la stanza è visibile a tutti @override void initState() { @@ -456,23 +554,27 @@ class _LobbyScreenState extends State { setState(() => _isLoading = true); try { - String code = await _multiplayerService.createGameRoom(_selectedRadius, _playerName, _selectedShape.name, _isTimeMode); + String code = await _multiplayerService.createGameRoom( + _selectedRadius, _playerName, _selectedShape.name, _isTimeMode, isPublic: _isPublicRoom + ); if (!mounted) return; setState(() { _myRoomCode = code; _isLoading = false; }); - _multiplayerService.shareInviteLink(code); + if (!_isPublicRoom) { + _multiplayerService.shareInviteLink(code); + } _showWaitingDialog(code); } catch (e) { if (mounted) { setState(() => _isLoading = false); _showError("Errore durante la creazione della partita."); } } } - Future _joinRoom() async { + Future _joinRoomByCode(String code) async { if (_isLoading) return; FocusScope.of(context).unfocus(); - String code = _codeController.text.trim().toUpperCase(); + code = code.trim().toUpperCase(); if (code.isEmpty || code.length != 5) { _showError("Inserisci un codice valido di 5 caratteri."); return; } setState(() => _isLoading = true); @@ -532,10 +634,10 @@ class _LobbyScreenState extends State { ), child: Column( children: [ - Icon(Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12), - Text("Invita un amico", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))), + Icon(_isPublicRoom ? Icons.podcasts : Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12), + Text(_isPublicRoom ? "Sei in Bacheca!" : "Invita un amico", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))), const SizedBox(height: 8), - Text("Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))), + Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))), ], ), ), @@ -625,7 +727,7 @@ class _LobbyScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Center(child: Text("IMPOSTAZIONI GRIGLIA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))), + Center(child: Text("IMPOSTAZIONI STANZA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))), const SizedBox(height: 10), Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), @@ -662,9 +764,15 @@ class _LobbyScreenState extends State { Divider(color: themeType == AppThemeType.doodle ? theme.text.withOpacity(0.5) : Colors.white.withOpacity(0.05), thickness: themeType == AppThemeType.doodle ? 2.5 : 1.5), const SizedBox(height: 12), - Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), + Text("REGOLE E VISIBILITÀ", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 8), - _NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode)), + Row( + children: [ + Expanded(child: _NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode))), + const SizedBox(width: 8), + Expanded(child: _NeonPrivacySwitch(isPublic: _isPublicRoom, theme: theme, themeType: themeType, onTap: () => setState(() => _isPublicRoom = !_isPublicRoom))), + ], + ) ], ), ), @@ -745,9 +853,107 @@ class _LobbyScreenState extends State { ), ), const SizedBox(height: 15), - _NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: _joinRoom, theme: theme, themeType: themeType), + _NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType), - const SizedBox(height: 10), + const SizedBox(height: 25), + Row( + children: [ + Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)), + Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("LOBBY PUBBLICA", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))), + Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)), + ], + ), + const SizedBox(height: 15), + + // --- LA VERA E PROPRIA BACHECA PUBBLICA --- + StreamBuilder( + stream: _multiplayerService.getPublicRooms(), + builder: (context, snapshot) { + // RIMOZIONE DELL'ERRORE ROSSO DALLA SCHERMATA + 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( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))), + ); + } + + // Ordiniamo le stanze dalla più recente + var docs = snapshot.data!.docs; + docs.sort((a, b) { + Timestamp? tA = (a.data() as Map)['createdAt'] as Timestamp?; + Timestamp? tB = (b.data() as Map)['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 host = data['hostName'] ?? 'Sconosciuto'; + int r = data['radius'] ?? 4; + String shapeStr = data['shape'] ?? 'classic'; + bool time = data['timeMode'] ?? true; + + // Formattazione del nome della forma + 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( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + 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: [ + CircleAvatar(backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed)), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Stanza di $host", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 16))), + const SizedBox(height: 4), + Text("Raggio: $r • $prettyShape • ${time ? 'A Tempo' : 'Relax'}", style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 11))), + ], + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + 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), + child: Text("ENTRA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))), + ) + ], + ), + ), + ); + } + ); + } + ), + const SizedBox(height: 20), ], ), ), diff --git a/lib/widgets/game_over_dialog.dart b/lib/widgets/game_over_dialog.dart index b5da460..b480f18 100644 --- a/lib/widgets/game_over_dialog.dart +++ b/lib/widgets/game_over_dialog.dart @@ -1,8 +1,13 @@ +// =========================================================================== +// FILE: lib/widgets/game_over_dialog.dart +// =========================================================================== + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../logic/game_controller.dart'; import '../core/theme_manager.dart'; import '../core/app_colors.dart'; +import '../services/storage_service.dart'; class GameOverDialog extends StatelessWidget { const GameOverDialog({super.key}); @@ -19,15 +24,18 @@ class GameOverDialog extends StatelessWidget { bool playerBeatCPU = game.isVsCPU && red > blue; + String myName = StorageService.instance.playerName.toUpperCase(); + if (myName.isEmpty) myName = "TU"; + // --- LOGICA NOMI --- - String nameRed = "ROSSO"; - String nameBlue = themeType == AppThemeType.cyberpunk ? "VERDE" : "BLU"; + String nameRed = myName; + String nameBlue = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU"; if (game.isOnline) { nameRed = game.onlineHostName.toUpperCase(); nameBlue = game.onlineGuestName.toUpperCase(); } else if (game.isVsCPU) { - nameRed = "TU"; + nameRed = myName; nameBlue = "CPU"; }