Auto-sync: 20260311_220000

This commit is contained in:
Paolo 2026-03-11 22:00:01 +01:00
parent 37e59d5652
commit 5aa831f750
6 changed files with 277 additions and 38 deletions

View file

@ -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<void> 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<void> 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) {}
}
}

View file

@ -12,7 +12,7 @@ class MultiplayerService {
CollectionReference get _gamesCollection => _firestore.collection('games');
Future<String> createGameRoom(int boardRadius, String hostName, String shapeName, bool isTimeMode) async {
Future<String> 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<QuerySnapshot> 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<void> sendReaction(String roomCode, bool isHost, String reaction) async {
try {
String prefix = isHost ? 'p1' : 'p2';

View file

@ -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<GameScreen> 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";

View file

@ -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<ScoreBoard> {
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";
}

View file

@ -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<LobbyScreen> {
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<LobbyScreen> {
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; });
if (!_isPublicRoom) {
_multiplayerService.shareInviteLink(code);
}
_showWaitingDialog(code);
} catch (e) {
if (mounted) { setState(() => _isLoading = false); _showError("Errore durante la creazione della partita."); }
}
}
Future<void> _joinRoom() async {
Future<void> _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<LobbyScreen> {
),
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<LobbyScreen> {
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<LobbyScreen> {
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<LobbyScreen> {
),
),
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<QuerySnapshot>(
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<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>;
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),
],
),
),

View file

@ -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";
}