Auto-sync: 20260311_220000
This commit is contained in:
parent
37e59d5652
commit
5aa831f750
6 changed files with 277 additions and 38 deletions
|
|
@ -15,6 +15,9 @@ class AudioService extends ChangeNotifier {
|
||||||
final AudioPlayer _sfxPlayer = AudioPlayer();
|
final AudioPlayer _sfxPlayer = AudioPlayer();
|
||||||
final AudioPlayer _bgmPlayer = 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 {
|
Future<void> init() async {
|
||||||
// 1. Carica la preferenza salvata
|
// 1. Carica la preferenza salvata
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
@ -32,16 +35,20 @@ class AudioService extends ChangeNotifier {
|
||||||
await prefs.setBool('isMuted', isMuted);
|
await prefs.setBool('isMuted', isMuted);
|
||||||
|
|
||||||
if (isMuted) {
|
if (isMuted) {
|
||||||
_bgmPlayer.pause();
|
await _bgmPlayer.pause();
|
||||||
} else {
|
} else {
|
||||||
_bgmPlayer.resume();
|
// Se togliamo il muto, facciamo ripartire la canzone del tema attuale
|
||||||
|
playBgm(_currentTheme);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- BGM (Musica di sottofondo) ---
|
// --- BGM (Musica di sottofondo) ---
|
||||||
Future<void> playBgm(AppThemeType theme) async {
|
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;
|
if (isMuted) return;
|
||||||
|
|
||||||
|
|
@ -72,7 +79,8 @@ class AudioService extends ChangeNotifier {
|
||||||
|
|
||||||
if (audioPath.isNotEmpty) {
|
if (audioPath.isNotEmpty) {
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
debugPrint("Errore riproduzione BGM: $e");
|
debugPrint("Errore riproduzione BGM: $e");
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +109,8 @@ class AudioService extends ChangeNotifier {
|
||||||
|
|
||||||
if (file.isNotEmpty) {
|
if (file.isNotEmpty) {
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
debugPrint("Errore SFX Linea non trovato: $file");
|
debugPrint("Errore SFX Linea non trovato: $file");
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +134,7 @@ class AudioService extends ChangeNotifier {
|
||||||
|
|
||||||
if (file.isNotEmpty) {
|
if (file.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
await _sfxPlayer.play(AssetSource('audio/sfx/$file'));
|
await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Errore SFX Box non trovato: $file");
|
debugPrint("Errore SFX Box non trovato: $file");
|
||||||
}
|
}
|
||||||
|
|
@ -135,14 +144,14 @@ class AudioService extends ChangeNotifier {
|
||||||
void playBonusSfx() async {
|
void playBonusSfx() async {
|
||||||
if (isMuted) return;
|
if (isMuted) return;
|
||||||
try {
|
try {
|
||||||
await _sfxPlayer.play(AssetSource('audio/sfx/bonus.wav'));
|
await _sfxPlayer.play(AssetSource('audio/sfx/bonus.wav'), volume: 1.0);
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void playBombSfx() async {
|
void playBombSfx() async {
|
||||||
if (isMuted) return;
|
if (isMuted) return;
|
||||||
try {
|
try {
|
||||||
await _sfxPlayer.play(AssetSource('audio/sfx/bomb.wav'));
|
await _sfxPlayer.play(AssetSource('audio/sfx/bomb.wav'), volume: 1.0);
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ class MultiplayerService {
|
||||||
|
|
||||||
CollectionReference get _gamesCollection => _firestore.collection('games');
|
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();
|
String roomCode = _generateRoomCode();
|
||||||
int randomSeed = Random().nextInt(1000000);
|
int randomSeed = Random().nextInt(1000000);
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ class MultiplayerService {
|
||||||
'guestName': '',
|
'guestName': '',
|
||||||
'shape': shapeName,
|
'shape': shapeName,
|
||||||
'timeMode': isTimeMode,
|
'timeMode': isTimeMode,
|
||||||
// Nuovi campi per Emojis e Rivincita
|
'isPublic': isPublic,
|
||||||
'p1_reaction': null,
|
'p1_reaction': null,
|
||||||
'p2_reaction': null,
|
'p2_reaction': null,
|
||||||
'p1_rematch': false,
|
'p1_rematch': false,
|
||||||
|
|
@ -52,6 +52,14 @@ class MultiplayerService {
|
||||||
return null;
|
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) {
|
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";
|
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);
|
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 {
|
Future<void> sendReaction(String roomCode, bool isHost, String reaction) async {
|
||||||
try {
|
try {
|
||||||
String prefix = isHost ? 'p1' : 'p2';
|
String prefix = isHost ? 'p1' : 'p2';
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import '../../core/app_colors.dart';
|
||||||
import 'board_painter.dart';
|
import 'board_painter.dart';
|
||||||
import 'score_board.dart';
|
import 'score_board.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import '../../services/storage_service.dart';
|
||||||
|
|
||||||
TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
|
TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
|
||||||
if (themeType == AppThemeType.doodle) {
|
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;
|
int red = controller.board.scoreRed; int blue = controller.board.scoreBlue;
|
||||||
bool playerBeatCPU = controller.isVsCPU && red > blue;
|
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");
|
String nameBlue = controller.isOnline ? controller.onlineGuestName.toUpperCase() : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU");
|
||||||
if (controller.isVsCPU) nameBlue = "CPU";
|
if (controller.isVsCPU) nameBlue = "CPU";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import '../../models/game_board.dart';
|
||||||
import '../../core/theme_manager.dart';
|
import '../../core/theme_manager.dart';
|
||||||
import '../../services/audio_service.dart';
|
import '../../services/audio_service.dart';
|
||||||
import '../../core/app_colors.dart';
|
import '../../core/app_colors.dart';
|
||||||
|
import '../../services/storage_service.dart';
|
||||||
|
|
||||||
class ScoreBoard extends StatefulWidget {
|
class ScoreBoard extends StatefulWidget {
|
||||||
const ScoreBoard({super.key});
|
const ScoreBoard({super.key});
|
||||||
|
|
@ -32,14 +33,17 @@ class _ScoreBoardState extends State<ScoreBoard> {
|
||||||
bool isRedTurn = controller.board.currentPlayer == Player.red;
|
bool isRedTurn = controller.board.currentPlayer == Player.red;
|
||||||
bool isMuted = AudioService.instance.isMuted;
|
bool isMuted = AudioService.instance.isMuted;
|
||||||
|
|
||||||
String nameRed = "ROSSO";
|
String myName = StorageService.instance.playerName.toUpperCase();
|
||||||
String nameBlue = themeType == AppThemeType.cyberpunk ? "VERDE" : "BLU";
|
if (myName.isEmpty) myName = "TU";
|
||||||
|
|
||||||
|
String nameRed = myName;
|
||||||
|
String nameBlue = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU";
|
||||||
|
|
||||||
if (controller.isOnline) {
|
if (controller.isOnline) {
|
||||||
nameRed = controller.onlineHostName.toUpperCase();
|
nameRed = controller.onlineHostName.toUpperCase();
|
||||||
nameBlue = controller.onlineGuestName.toUpperCase();
|
nameBlue = controller.onlineGuestName.toUpperCase();
|
||||||
} else if (controller.isVsCPU) {
|
} else if (controller.isVsCPU) {
|
||||||
nameRed = "TU";
|
nameRed = myName;
|
||||||
nameBlue = "CPU";
|
nameBlue = "CPU";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
// ===========================================================================
|
||||||
|
// FILE: lib/ui/multiplayer/lobby_screen.dart
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
@ -240,14 +244,14 @@ class _NeonTimeSwitch extends StatelessWidget {
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 24),
|
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 20),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 8),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
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 ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.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 ? '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,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 24),
|
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 20),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 8),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
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 ? '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 ? '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 ? '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;
|
int _selectedRadius = 4;
|
||||||
ArenaShape _selectedShape = ArenaShape.classic;
|
ArenaShape _selectedShape = ArenaShape.classic;
|
||||||
bool _isTimeMode = true;
|
bool _isTimeMode = true;
|
||||||
|
bool _isPublicRoom = true; // Di default la stanza è visibile a tutti
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -456,23 +554,27 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
|
|
||||||
try {
|
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;
|
if (!mounted) return;
|
||||||
setState(() { _myRoomCode = code; _isLoading = false; });
|
setState(() { _myRoomCode = code; _isLoading = false; });
|
||||||
|
|
||||||
_multiplayerService.shareInviteLink(code);
|
if (!_isPublicRoom) {
|
||||||
|
_multiplayerService.shareInviteLink(code);
|
||||||
|
}
|
||||||
_showWaitingDialog(code);
|
_showWaitingDialog(code);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) { setState(() => _isLoading = false); _showError("Errore durante la creazione della partita."); }
|
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;
|
if (_isLoading) return;
|
||||||
FocusScope.of(context).unfocus();
|
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; }
|
if (code.isEmpty || code.length != 5) { _showError("Inserisci un codice valido di 5 caratteri."); return; }
|
||||||
|
|
||||||
setState(() => _isLoading = true);
|
setState(() => _isLoading = true);
|
||||||
|
|
@ -532,10 +634,10 @@ class _LobbyScreenState extends State<LobbyScreen> {
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12),
|
Icon(_isPublicRoom ? Icons.podcasts : 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))),
|
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),
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
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),
|
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))),
|
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),
|
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),
|
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),
|
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),
|
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),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
|
// ===========================================================================
|
||||||
|
// FILE: lib/widgets/game_over_dialog.dart
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../logic/game_controller.dart';
|
import '../logic/game_controller.dart';
|
||||||
import '../core/theme_manager.dart';
|
import '../core/theme_manager.dart';
|
||||||
import '../core/app_colors.dart';
|
import '../core/app_colors.dart';
|
||||||
|
import '../services/storage_service.dart';
|
||||||
|
|
||||||
class GameOverDialog extends StatelessWidget {
|
class GameOverDialog extends StatelessWidget {
|
||||||
const GameOverDialog({super.key});
|
const GameOverDialog({super.key});
|
||||||
|
|
@ -19,15 +24,18 @@ class GameOverDialog extends StatelessWidget {
|
||||||
|
|
||||||
bool playerBeatCPU = game.isVsCPU && red > blue;
|
bool playerBeatCPU = game.isVsCPU && red > blue;
|
||||||
|
|
||||||
|
String myName = StorageService.instance.playerName.toUpperCase();
|
||||||
|
if (myName.isEmpty) myName = "TU";
|
||||||
|
|
||||||
// --- LOGICA NOMI ---
|
// --- LOGICA NOMI ---
|
||||||
String nameRed = "ROSSO";
|
String nameRed = myName;
|
||||||
String nameBlue = themeType == AppThemeType.cyberpunk ? "VERDE" : "BLU";
|
String nameBlue = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU";
|
||||||
|
|
||||||
if (game.isOnline) {
|
if (game.isOnline) {
|
||||||
nameRed = game.onlineHostName.toUpperCase();
|
nameRed = game.onlineHostName.toUpperCase();
|
||||||
nameBlue = game.onlineGuestName.toUpperCase();
|
nameBlue = game.onlineGuestName.toUpperCase();
|
||||||
} else if (game.isVsCPU) {
|
} else if (game.isVsCPU) {
|
||||||
nameRed = "TU";
|
nameRed = myName;
|
||||||
nameBlue = "CPU";
|
nameBlue = "CPU";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue