diff --git a/lib/services/multiplayer_service.dart b/lib/services/multiplayer_service.dart index 5656c80..c69a93b 100644 --- a/lib/services/multiplayer_service.dart +++ b/lib/services/multiplayer_service.dart @@ -13,6 +13,7 @@ class MultiplayerService { final FirebaseAuth _auth = FirebaseAuth.instance; CollectionReference get _gamesCollection => _firestore.collection('games'); + CollectionReference get _invitesCollection => _firestore.collection('invites'); Future 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 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 listenForInvites(String myUid) { + return _invitesCollection.where('targetUid', isEqualTo: myUid).snapshots(); + } + + Future deleteInvite(String inviteId) async { + try { + await _invitesCollection.doc(inviteId).delete(); + } catch(e) { + debugPrint("Errore cancellazione invito: $e"); + } + } } \ No newline at end of file diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 42b5b8e..ab3aeb1 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -77,6 +77,27 @@ class StorageService { } } + // --- NUOVO: GESTIONE PREFERITI (RUBRICA LOCALE) --- + List> get favorites { + List favs = _prefs.getStringList('favorites') ?? []; + return favs.map((e) => Map.from(jsonDecode(e))).toList(); + } + + Future 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') ?? ''; diff --git a/lib/ui/home/dialog.dart b/lib/ui/home/dialog.dart index 68e7228..68fa7f0 100644 --- a/lib/ui/home/dialog.dart +++ b/lib/ui/home/dialog.dart @@ -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,29 +182,48 @@ class LeaderboardDialog extends StatelessWidget { var data = doc.data() as Map; String? myUid = FirebaseAuth.instance.currentUser?.uid; bool isMe = doc.id == myUid; + String playerName = data['name'] ?? 'Unknown'; - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - decoration: BoxDecoration( - color: isMe ? theme.playerBlue.withOpacity(0.2) : theme.text.withOpacity(0.05), - borderRadius: BorderRadius.circular(10), - border: isMe ? Border.all(color: theme.playerBlue, width: 1.5) : null - ), - child: Row( - 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)))), - 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)), - ], - ) - ], - ), + // 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), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: BoxDecoration( + color: isMe ? theme.playerBlue.withOpacity(0.2) : theme.text.withOpacity(0.05), + borderRadius: BorderRadius.circular(10), + border: isMe ? Border.all(color: theme.playerBlue, width: 1.5) : null + ), + child: Row( + 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(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), + ) + ] + ], + ), + ); + } ); } ); diff --git a/lib/ui/multiplayer/lobby_screen.dart b/lib/ui/multiplayer/lobby_screen.dart index 055f677..a93de25 100644 --- a/lib/ui/multiplayer/lobby_screen.dart +++ b/lib/ui/multiplayer/lobby_screen.dart @@ -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 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 with WidgetsBindingObserver { } } + // --- NUOVA LOGICA: CREA STANZA E INVITA IN UN COLPO SOLO --- + Future _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 _joinRoomByCode(String code) async { if (_isLoading) return; FocusScope.of(context).unfocus(); @@ -643,6 +760,60 @@ class _LobbyScreenState extends State 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(); + 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 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 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 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 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)]))), + IconButton( + icon: Icon(Icons.arrow_back_ios_new, color: theme.text), + onPressed: () => Navigator.pop(context), ), - 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))), - ), + 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 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))), - ], - ), - ) - 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 (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)] + ) + ), + ), + ), + if (themeType == AppThemeType.doodle) + Positioned.fill( + child: CustomPaint( + painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)), + ), + ), + Positioned.fill(child: uiContent), ], ), );