Auto-sync: 20260315_130000
This commit is contained in:
parent
ea54bf3a12
commit
fe8d54b3e1
4 changed files with 315 additions and 82 deletions
|
|
@ -13,6 +13,7 @@ class MultiplayerService {
|
|||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
||||
CollectionReference get _gamesCollection => _firestore.collection('games');
|
||||
CollectionReference get _invitesCollection => _firestore.collection('invites');
|
||||
|
||||
Future<String> createGameRoom(int boardRadius, String hostName, String shapeName, bool isTimeMode, {bool isPublic = true}) async {
|
||||
String roomCode = _generateRoomCode();
|
||||
|
|
@ -27,7 +28,7 @@ class MultiplayerService {
|
|||
'moves': [],
|
||||
'seed': randomSeed,
|
||||
'hostName': hostName,
|
||||
'hostUid': _auth.currentUser?.uid, // NUOVO: Salviamo l'ID univoco del creatore
|
||||
'hostUid': _auth.currentUser?.uid,
|
||||
'guestName': '',
|
||||
'shape': shapeName,
|
||||
'timeMode': isTimeMode,
|
||||
|
|
@ -66,7 +67,10 @@ class MultiplayerService {
|
|||
String message = "Ehi! Giochiamo a TetraQ? 🎮\n\n"
|
||||
"Clicca su questo link per entrare direttamente in stanza:\n"
|
||||
"tetraq://join?code=$roomCode\n\n"
|
||||
"Oppure apri l'app e inserisci manualmente il codice: $roomCode";
|
||||
"Apri l'app e inserisci manualmente il codice: $roomCode\n"
|
||||
"Se non hai ancora scaricato il gioco lo trovi nei link sotto \n\n"
|
||||
"🍎 iOS: https://apps.apple.com/it/app/tetraq/id6759522394\n"
|
||||
"🤖 Android: https://play.google.com/store/apps/details?id=com.amastra.tetraq";
|
||||
Share.share(message);
|
||||
}
|
||||
|
||||
|
|
@ -122,4 +126,29 @@ class MultiplayerService {
|
|||
debugPrint("Errore reset partita: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendInvite(String targetUid, String roomCode, String hostName) async {
|
||||
try {
|
||||
await _invitesCollection.add({
|
||||
'targetUid': targetUid,
|
||||
'hostName': hostName,
|
||||
'roomCode': roomCode,
|
||||
'timestamp': FieldValue.serverTimestamp(),
|
||||
});
|
||||
} catch(e) {
|
||||
debugPrint("Errore invio invito: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Stream<QuerySnapshot> listenForInvites(String myUid) {
|
||||
return _invitesCollection.where('targetUid', isEqualTo: myUid).snapshots();
|
||||
}
|
||||
|
||||
Future<void> deleteInvite(String inviteId) async {
|
||||
try {
|
||||
await _invitesCollection.doc(inviteId).delete();
|
||||
} catch(e) {
|
||||
debugPrint("Errore cancellazione invito: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +77,27 @@ class StorageService {
|
|||
}
|
||||
}
|
||||
|
||||
// --- NUOVO: GESTIONE PREFERITI (RUBRICA LOCALE) ---
|
||||
List<Map<String, String>> get favorites {
|
||||
List<String> favs = _prefs.getStringList('favorites') ?? [];
|
||||
return favs.map((e) => Map<String, String>.from(jsonDecode(e))).toList();
|
||||
}
|
||||
|
||||
Future<void> toggleFavorite(String uid, String name) async {
|
||||
var favs = favorites;
|
||||
if (favs.any((f) => f['uid'] == uid)) {
|
||||
favs.removeWhere((f) => f['uid'] == uid); // Rimuove se esiste
|
||||
} else {
|
||||
favs.add({'uid': uid, 'name': name}); // Aggiunge se non esiste
|
||||
}
|
||||
await _prefs.setStringList('favorites', favs.map((e) => jsonEncode(e)).toList());
|
||||
}
|
||||
|
||||
bool isFavorite(String uid) {
|
||||
return favorites.any((f) => f['uid'] == uid);
|
||||
}
|
||||
// ---------------------------------------------------
|
||||
|
||||
void _checkDailyQuests() {
|
||||
String today = DateTime.now().toIso8601String().substring(0, 10);
|
||||
String lastDate = _prefs.getString('quest_date') ?? '';
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import '../../core/app_colors.dart';
|
|||
import '../../l10n/app_localizations.dart';
|
||||
import '../../widgets/painters.dart';
|
||||
import '../../widgets/cyber_border.dart';
|
||||
import '../../services/storage_service.dart'; // IMPORT AGGIUNTO
|
||||
|
||||
// ===========================================================================
|
||||
// 1. DIALOGO MISSIONI (QUESTS)
|
||||
|
|
@ -181,6 +182,12 @@ class LeaderboardDialog extends StatelessWidget {
|
|||
var data = doc.data() as Map<String, dynamic>;
|
||||
String? myUid = FirebaseAuth.instance.currentUser?.uid;
|
||||
bool isMe = doc.id == myUid;
|
||||
String playerName = data['name'] ?? 'Unknown';
|
||||
|
||||
// Avvolto in StatefulBuilder per gestire la stella dei preferiti
|
||||
return StatefulBuilder(
|
||||
builder: (context, setStateItem) {
|
||||
bool isFav = StorageService.instance.isFavorite(doc.id);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
|
|
@ -194,20 +201,33 @@ class LeaderboardDialog extends StatelessWidget {
|
|||
children: [
|
||||
Text("#${index + 1}", style: getSharedTextStyle(themeType, TextStyle(fontWeight: FontWeight.w900, color: index == 0 ? Colors.amber : (index == 1 ? Colors.grey.shade400 : (index == 2 ? Colors.brown.shade300 : theme.text.withOpacity(0.5)))))),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(child: Text(data['name'] ?? 'Unknown', style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: isMe ? FontWeight.w900 : FontWeight.bold, color: theme.text)))),
|
||||
Expanded(child: Text(playerName, style: getSharedTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: isMe ? FontWeight.w900 : FontWeight.bold, color: theme.text)))),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text("Lv. ${data['level'] ?? 1}", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 12)),
|
||||
Text("${data['xp'] ?? 0} XP", style: TextStyle(color: theme.text.withOpacity(0.6), fontSize: 10)),
|
||||
],
|
||||
),
|
||||
// IL BOTTONE DEI PREFERITI
|
||||
if (!isMe) ...[
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await StorageService.instance.toggleFavorite(doc.id, playerName);
|
||||
setStateItem(() {});
|
||||
},
|
||||
child: Icon(isFav ? Icons.star : Icons.star_border, color: Colors.amber, size: 24),
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../logic/game_controller.dart';
|
||||
import '../../models/game_board.dart';
|
||||
import '../../core/theme_manager.dart';
|
||||
|
|
@ -31,6 +32,10 @@ TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
|
|||
return baseStyle;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// WIDGET INTERNI DI SUPPORTO (PULSANTI NEON)
|
||||
// ===========================================================================
|
||||
|
||||
class _NeonShapeButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
|
@ -388,7 +393,7 @@ class _NeonPrivacySwitch extends StatelessWidget {
|
|||
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 ? 'STANZA PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 10, letterSpacing: 1.0))),
|
||||
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))),
|
||||
],
|
||||
),
|
||||
|
|
@ -399,6 +404,92 @@ class _NeonPrivacySwitch extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
// --- NUOVO WIDGET: BOTTONE INVITA PREFERITO ---
|
||||
class _NeonInviteFavoriteButton extends StatelessWidget {
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _NeonInviteFavoriteButton({required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = Colors.pink.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),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8), topRight: Radius.circular(15),
|
||||
bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6),
|
||||
),
|
||||
border: Border.all(color: doodleColor.withOpacity(0.5), width: 2.5),
|
||||
boxShadow: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.favorite, color: doodleColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('PREFERITI', style: _getTextStyle(themeType, TextStyle(color: doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
|
||||
Text('Invita amico', style: _getTextStyle(themeType, TextStyle(color: 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: [Colors.pinkAccent.withOpacity(0.25), Colors.pinkAccent.withOpacity(0.05)],
|
||||
),
|
||||
border: Border.all(color: Colors.pinkAccent, width: 1.5),
|
||||
boxShadow: [BoxShadow(color: Colors.pinkAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.favorite, color: Colors.pinkAccent, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('PREFERITI', style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))),
|
||||
Text('Invita amico', style: _getTextStyle(themeType, TextStyle(color: Colors.pinkAccent.shade200, fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NeonActionButton extends StatelessWidget {
|
||||
final String label;
|
||||
final Color color;
|
||||
|
|
@ -524,6 +615,10 @@ class _CyberBorderPainter extends CustomPainter {
|
|||
bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// SCHERMATA LOBBY (PRINCIPALE)
|
||||
// ===========================================================================
|
||||
|
||||
class LobbyScreen extends StatefulWidget {
|
||||
final String? initialRoomCode;
|
||||
|
||||
|
|
@ -541,7 +636,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
String? _myRoomCode;
|
||||
String _playerName = '';
|
||||
|
||||
// Variabile per gestire l'effetto "sipario"
|
||||
bool _isCreatingRoom = false;
|
||||
|
||||
int _selectedRadius = 4;
|
||||
|
|
@ -608,6 +702,29 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
}
|
||||
}
|
||||
|
||||
// --- NUOVA LOGICA: CREA STANZA E INVITA IN UN COLPO SOLO ---
|
||||
Future<void> _createRoomAndInvite(String targetUid, String targetName) async {
|
||||
if (_isLoading) return;
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
String code = await _multiplayerService.createGameRoom(
|
||||
_selectedRadius, _playerName, _selectedShape.name, _isTimeMode, isPublic: _isPublicRoom
|
||||
);
|
||||
|
||||
await _multiplayerService.sendInvite(targetUid, code, _playerName);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() { _myRoomCode = code; _isLoading = false; _roomStarted = false; });
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Sfida inviata a $targetName!"), backgroundColor: Colors.green));
|
||||
_showWaitingDialog(code);
|
||||
|
||||
} catch (e) {
|
||||
if (mounted) { setState(() => _isLoading = false); _showError("Errore durante la creazione della partita."); }
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _joinRoomByCode(String code) async {
|
||||
if (_isLoading) return;
|
||||
FocusScope.of(context).unfocus();
|
||||
|
|
@ -643,6 +760,60 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
|
||||
void _showError(String message) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message, style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red)); }
|
||||
|
||||
// --- FINESTRA PREFERITI ---
|
||||
void _showFavoritesDialogForCreation() {
|
||||
final favs = StorageService.instance.favorites;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
final themeManager = ctx.watch<ThemeManager>();
|
||||
final theme = themeManager.currentColors;
|
||||
final themeType = themeManager.currentThemeType;
|
||||
|
||||
return AlertDialog(
|
||||
backgroundColor: theme.background,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
title: Text("I TUOI PREFERITI", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
height: 300,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: theme.playerRed, width: 2),
|
||||
borderRadius: BorderRadius.circular(10)
|
||||
),
|
||||
child: favs.isEmpty
|
||||
? Center(child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Text("Non hai ancora aggiunto nessun preferito dalla Classifica!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6)))),
|
||||
))
|
||||
: ListView.builder(
|
||||
itemCount: favs.length,
|
||||
itemBuilder: (c, i) {
|
||||
return ListTile(
|
||||
title: Text(favs[i]['name']!, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 18, fontWeight: FontWeight.bold))),
|
||||
trailing: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_createRoomAndInvite(favs[i]['uid']!, favs[i]['name']!);
|
||||
},
|
||||
child: Text("SFIDA", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx), child: Text("CHIUDI", style: _getTextStyle(themeType, TextStyle(color: theme.playerRed))))
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void _showWaitingDialog(String code) {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
@ -673,7 +844,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
child: Column(
|
||||
children: [
|
||||
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))),
|
||||
Text(_isPublicRoom ? "Sei in Bacheca!" : "Condividi link", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))),
|
||||
const SizedBox(height: 8),
|
||||
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))),
|
||||
],
|
||||
|
|
@ -756,8 +927,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||
|
||||
bool isChaosUnlocked = true;
|
||||
Color doodlePenColor = const Color(0xFF00008B);
|
||||
bool isChaosUnlocked = StorageService.instance.playerLevel >= 10;
|
||||
|
||||
// --- PANNELLO IMPOSTAZIONI STANZA ---
|
||||
Widget hostPanel = Transform.rotate(
|
||||
|
|
@ -818,13 +988,23 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
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("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))),
|
||||
Text("TEMPO E OPZIONI", 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),
|
||||
|
||||
// RIGA 1: TEMPO (OCCUPA TUTTO LO SPAZIO)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode))),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// RIGA 2: VISIBILITÀ E INVITI
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _NeonPrivacySwitch(isPublic: _isPublicRoom, theme: theme, themeType: themeType, onTap: () => setState(() => _isPublicRoom = !_isPublicRoom))),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: _NeonInviteFavoriteButton(theme: theme, themeType: themeType, onTap: _showFavoritesDialogForCreation)),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -839,61 +1019,48 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
Widget uiContent = SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
// Padding inferiore aumentato a 60 per evitare il taglio dei pulsanti
|
||||
padding: EdgeInsets.only(left: 20.0, right: 20.0, top: 10.0, bottom: MediaQuery.of(context).padding.bottom + 60.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Transform.rotate(
|
||||
angle: themeType == AppThemeType.doodle ? -0.02 : 0,
|
||||
child: Text("MULTIPLAYER", style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(2, 2), blurRadius: 4)]))),
|
||||
),
|
||||
Transform.rotate(
|
||||
angle: themeType == AppThemeType.doodle ? 0.03 : 0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: themeType == AppThemeType.doodle ? Colors.white : theme.playerRed.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: themeType == AppThemeType.doodle ? theme.playerRed : theme.playerRed.withOpacity(0.5), width: themeType == AppThemeType.doodle ? 2.5 : 1.0),
|
||||
boxShadow: themeType == AppThemeType.doodle ? [BoxShadow(color: theme.text.withOpacity(0.6), offset: const Offset(2, 3), blurRadius: 0)] : []
|
||||
),
|
||||
child: Text("$_playerName", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: theme.playerRed, letterSpacing: 1))),
|
||||
IconButton(
|
||||
icon: Icon(Icons.arrow_back_ios_new, color: theme.text),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
Expanded(
|
||||
child: Text("MULTIPLAYER", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))),
|
||||
),
|
||||
const SizedBox(width: 48), // Bilanciamento
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// --- L'EFFETTO SIPARIO CON ANIMATED SIZE ---
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
alignment: Alignment.topCenter,
|
||||
child: _isCreatingRoom
|
||||
? Column( // MENU CREAZIONE (Aperto)
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
hostPanel,
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
children: [
|
||||
Expanded( // Entrambi in un Expanded "liscio" si dividono il 50% di spazio
|
||||
Expanded(
|
||||
child: _NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded( // Entrambi in un Expanded "liscio" si dividono il 50% di spazio
|
||||
Expanded(
|
||||
child: _NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column( // MENU BASE (Chiuso)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType),
|
||||
|
|
@ -1067,39 +1234,35 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
return Scaffold(
|
||||
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0, iconTheme: IconThemeData(color: theme.text)),
|
||||
body: Stack(
|
||||
children: [
|
||||
Container(
|
||||
decoration: bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover)) : null,
|
||||
child: bgImage != null && themeType == AppThemeType.cyberpunk
|
||||
? BackdropFilter(filter: ImageFilter.blur(sigmaX: 3.5, sigmaY: 3.5), child: Container(color: Colors.black.withOpacity(0.2)))
|
||||
: bgImage != null && themeType != AppThemeType.cyberpunk
|
||||
? BackdropFilter(filter: ImageFilter.blur(sigmaX: 3.5, sigmaY: 3.5), child: Container(color: themeType == AppThemeType.doodle ? Colors.white.withOpacity(0.1) : Colors.transparent))
|
||||
: null,
|
||||
),
|
||||
|
||||
if (themeType == AppThemeType.doodle)
|
||||
Positioned(
|
||||
top: 150, left: -20, right: -20,
|
||||
child: Stack(
|
||||
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
|
||||
if (bgImage != null)
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
bgImage,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Transform.rotate(angle: -0.06, child: Icon(Icons.wifi_tethering, size: 450, color: doodlePenColor.withOpacity(0.08))),
|
||||
Transform.rotate(angle: 0.04, child: Icon(Icons.wifi_tethering, size: 430, color: doodlePenColor.withOpacity(0.06))),
|
||||
Transform.rotate(angle: 0.01, child: Icon(Icons.wifi_tethering, size: 460, color: doodlePenColor.withOpacity(0.05))),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio))
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter, end: Alignment.bottomCenter,
|
||||
colors: [Colors.black.withOpacity(0.6), Colors.black.withOpacity(0.9)]
|
||||
)
|
||||
else
|
||||
Positioned(
|
||||
top: 70, left: -50, right: -50,
|
||||
child: Center(
|
||||
child: Icon(Icons.wifi_tethering, size: 450, color: theme.playerBlue.withOpacity(0.12)),
|
||||
),
|
||||
),
|
||||
|
||||
_isLoading ? Center(child: CircularProgressIndicator(color: theme.playerRed)) : uiContent,
|
||||
),
|
||||
if (themeType == AppThemeType.doodle)
|
||||
Positioned.fill(
|
||||
child: CustomPaint(
|
||||
painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)),
|
||||
),
|
||||
),
|
||||
Positioned.fill(child: uiContent),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue