diff --git a/.DS_Store b/.DS_Store index 95045bb..4f755ad 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index c7b17f2..a1e2169 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,31 +5,31 @@ - - - - - - - - - - - - - + android:icon="@mipmap/ic_launcher" + android:usesCleartextTraffic="true"> + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 49b472a..612921d 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,61 +2,67 @@ - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Tetraq - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - tetraq - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - com.sanza.tetraq - CFBundleURLSchemes - - tetraq - - - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Tetraq + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + tetraq + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + com.sanza.tetraq + CFBundleURLSchemes + + tetraq + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + - + \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 482936c..2288f80 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -19,5 +19,18 @@ "joinMatch": "JOIN", "gameOver": "GAME OVER", "mainMenu": "BACK TO MENU", - "exit": "EXIT" + "exit": "EXIT", + "roomSettings": "ROOM SETTINGS", + "arenaShape": "ARENA SHAPE", + "arenaSize": "SIZE", + "timeAndOptions": "TIME & OPTIONS", + "timeLabel": "TIME", + "btnStart": "START", + "btnCancel": "CANCEL", + "wordOr": "OR", + "codeHint": "CODE", + "publicLobbyTitle": "PUBLIC LOBBY", + "emptyLobbyMsg": "No public rooms right now.\nCreate one!", + "roomOf": "Room of", + "btnEnter": "ENTER" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 7e4e3d9..b8eb571 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -19,5 +19,18 @@ "joinMatch": "UNISCITI", "gameOver": "FINE PARTITA", "mainMenu": "TORNA AL MENU", - "exit": "ESCI" + "exit": "ESCI", + "roomSettings": "IMPOSTAZIONI STANZA", + "arenaShape": "FORMA ARENA", + "arenaSize": "GRANDEZZA", + "timeAndOptions": "TEMPO E OPZIONI", + "timeLabel": "TEMPO", + "btnStart": "AVVIA", + "btnCancel": "ANNULLA", + "wordOr": "OPPURE", + "codeHint": "CODICE", + "publicLobbyTitle": "LOBBY PUBBLICA", + "emptyLobbyMsg": "Nessuna stanza pubblica al momento.\nCreane una tu!", + "roomOf": "Stanza di", + "btnEnter": "ENTRA" } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 66b0b21..2b3d224 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -229,6 +229,84 @@ abstract class AppLocalizations { /// In it, this message translates to: /// **'ESCI'** String get exit; + + /// No description provided for @roomSettings. + /// + /// In it, this message translates to: + /// **'IMPOSTAZIONI STANZA'** + String get roomSettings; + + /// No description provided for @arenaShape. + /// + /// In it, this message translates to: + /// **'FORMA ARENA'** + String get arenaShape; + + /// No description provided for @arenaSize. + /// + /// In it, this message translates to: + /// **'GRANDEZZA'** + String get arenaSize; + + /// No description provided for @timeAndOptions. + /// + /// In it, this message translates to: + /// **'TEMPO E OPZIONI'** + String get timeAndOptions; + + /// No description provided for @timeLabel. + /// + /// In it, this message translates to: + /// **'TEMPO'** + String get timeLabel; + + /// No description provided for @btnStart. + /// + /// In it, this message translates to: + /// **'AVVIA'** + String get btnStart; + + /// No description provided for @btnCancel. + /// + /// In it, this message translates to: + /// **'ANNULLA'** + String get btnCancel; + + /// No description provided for @wordOr. + /// + /// In it, this message translates to: + /// **'OPPURE'** + String get wordOr; + + /// No description provided for @codeHint. + /// + /// In it, this message translates to: + /// **'CODICE'** + String get codeHint; + + /// No description provided for @publicLobbyTitle. + /// + /// In it, this message translates to: + /// **'LOBBY PUBBLICA'** + String get publicLobbyTitle; + + /// No description provided for @emptyLobbyMsg. + /// + /// In it, this message translates to: + /// **'Nessuna stanza pubblica al momento.\nCreane una tu!'** + String get emptyLobbyMsg; + + /// No description provided for @roomOf. + /// + /// In it, this message translates to: + /// **'Stanza di'** + String get roomOf; + + /// No description provided for @btnEnter. + /// + /// In it, this message translates to: + /// **'ENTRA'** + String get btnEnter; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 130b01b..61aa46e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -67,4 +67,44 @@ class AppLocalizationsDe extends AppLocalizations { @override String get exit => 'BEENDEN'; + + @override + String get roomSettings => 'IMPOSTAZIONI STANZA'; + + @override + String get arenaShape => 'FORMA ARENA'; + + @override + String get arenaSize => 'GRANDEZZA'; + + @override + String get timeAndOptions => 'TEMPO E OPZIONI'; + + @override + String get timeLabel => 'TEMPO'; + + @override + String get btnStart => 'AVVIA'; + + @override + String get btnCancel => 'ANNULLA'; + + @override + String get wordOr => 'OPPURE'; + + @override + String get codeHint => 'CODICE'; + + @override + String get publicLobbyTitle => 'LOBBY PUBBLICA'; + + @override + String get emptyLobbyMsg => + 'Nessuna stanza pubblica al momento.\nCreane una tu!'; + + @override + String get roomOf => 'Stanza di'; + + @override + String get btnEnter => 'ENTRA'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 31748d3..8f28c78 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -67,4 +67,43 @@ class AppLocalizationsEn extends AppLocalizations { @override String get exit => 'EXIT'; + + @override + String get roomSettings => 'ROOM SETTINGS'; + + @override + String get arenaShape => 'ARENA SHAPE'; + + @override + String get arenaSize => 'SIZE'; + + @override + String get timeAndOptions => 'TIME & OPTIONS'; + + @override + String get timeLabel => 'TIME'; + + @override + String get btnStart => 'START'; + + @override + String get btnCancel => 'CANCEL'; + + @override + String get wordOr => 'OR'; + + @override + String get codeHint => 'CODE'; + + @override + String get publicLobbyTitle => 'PUBLIC LOBBY'; + + @override + String get emptyLobbyMsg => 'No public rooms right now.\nCreate one!'; + + @override + String get roomOf => 'Room of'; + + @override + String get btnEnter => 'ENTER'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 06b88f7..4656b87 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -67,4 +67,44 @@ class AppLocalizationsEs extends AppLocalizations { @override String get exit => 'SALIR'; + + @override + String get roomSettings => 'IMPOSTAZIONI STANZA'; + + @override + String get arenaShape => 'FORMA ARENA'; + + @override + String get arenaSize => 'GRANDEZZA'; + + @override + String get timeAndOptions => 'TEMPO E OPZIONI'; + + @override + String get timeLabel => 'TEMPO'; + + @override + String get btnStart => 'AVVIA'; + + @override + String get btnCancel => 'ANNULLA'; + + @override + String get wordOr => 'OPPURE'; + + @override + String get codeHint => 'CODICE'; + + @override + String get publicLobbyTitle => 'LOBBY PUBBLICA'; + + @override + String get emptyLobbyMsg => + 'Nessuna stanza pubblica al momento.\nCreane una tu!'; + + @override + String get roomOf => 'Stanza di'; + + @override + String get btnEnter => 'ENTRA'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index c9c75c5..b061f20 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -67,4 +67,44 @@ class AppLocalizationsFr extends AppLocalizations { @override String get exit => 'QUITTER'; + + @override + String get roomSettings => 'IMPOSTAZIONI STANZA'; + + @override + String get arenaShape => 'FORMA ARENA'; + + @override + String get arenaSize => 'GRANDEZZA'; + + @override + String get timeAndOptions => 'TEMPO E OPZIONI'; + + @override + String get timeLabel => 'TEMPO'; + + @override + String get btnStart => 'AVVIA'; + + @override + String get btnCancel => 'ANNULLA'; + + @override + String get wordOr => 'OPPURE'; + + @override + String get codeHint => 'CODICE'; + + @override + String get publicLobbyTitle => 'LOBBY PUBBLICA'; + + @override + String get emptyLobbyMsg => + 'Nessuna stanza pubblica al momento.\nCreane una tu!'; + + @override + String get roomOf => 'Stanza di'; + + @override + String get btnEnter => 'ENTRA'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index ef8f2ad..e82da90 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -67,4 +67,44 @@ class AppLocalizationsIt extends AppLocalizations { @override String get exit => 'ESCI'; + + @override + String get roomSettings => 'IMPOSTAZIONI STANZA'; + + @override + String get arenaShape => 'FORMA ARENA'; + + @override + String get arenaSize => 'GRANDEZZA'; + + @override + String get timeAndOptions => 'TEMPO E OPZIONI'; + + @override + String get timeLabel => 'TEMPO'; + + @override + String get btnStart => 'AVVIA'; + + @override + String get btnCancel => 'ANNULLA'; + + @override + String get wordOr => 'OPPURE'; + + @override + String get codeHint => 'CODICE'; + + @override + String get publicLobbyTitle => 'LOBBY PUBBLICA'; + + @override + String get emptyLobbyMsg => + 'Nessuna stanza pubblica al momento.\nCreane una tu!'; + + @override + String get roomOf => 'Stanza di'; + + @override + String get btnEnter => 'ENTRA'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index ae8e66a..657eec5 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -67,4 +67,44 @@ class AppLocalizationsPt extends AppLocalizations { @override String get exit => 'SAIR'; + + @override + String get roomSettings => 'IMPOSTAZIONI STANZA'; + + @override + String get arenaShape => 'FORMA ARENA'; + + @override + String get arenaSize => 'GRANDEZZA'; + + @override + String get timeAndOptions => 'TEMPO E OPZIONI'; + + @override + String get timeLabel => 'TEMPO'; + + @override + String get btnStart => 'AVVIA'; + + @override + String get btnCancel => 'ANNULLA'; + + @override + String get wordOr => 'OPPURE'; + + @override + String get codeHint => 'CODICE'; + + @override + String get publicLobbyTitle => 'LOBBY PUBBLICA'; + + @override + String get emptyLobbyMsg => + 'Nessuna stanza pubblica al momento.\nCreane una tu!'; + + @override + String get roomOf => 'Stanza di'; + + @override + String get btnEnter => 'ENTRA'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index bec57e8..ad84cb2 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -67,4 +67,44 @@ class AppLocalizationsRu extends AppLocalizations { @override String get exit => 'ВЫХОД'; + + @override + String get roomSettings => 'IMPOSTAZIONI STANZA'; + + @override + String get arenaShape => 'FORMA ARENA'; + + @override + String get arenaSize => 'GRANDEZZA'; + + @override + String get timeAndOptions => 'TEMPO E OPZIONI'; + + @override + String get timeLabel => 'TEMPO'; + + @override + String get btnStart => 'AVVIA'; + + @override + String get btnCancel => 'ANNULLA'; + + @override + String get wordOr => 'OPPURE'; + + @override + String get codeHint => 'CODICE'; + + @override + String get publicLobbyTitle => 'LOBBY PUBBLICA'; + + @override + String get emptyLobbyMsg => + 'Nessuna stanza pubblica al momento.\nCreane una tu!'; + + @override + String get roomOf => 'Stanza di'; + + @override + String get btnEnter => 'ENTRA'; } diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 32a8905..2077126 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -67,4 +67,44 @@ class AppLocalizationsZh extends AppLocalizations { @override String get exit => '退出'; + + @override + String get roomSettings => 'IMPOSTAZIONI STANZA'; + + @override + String get arenaShape => 'FORMA ARENA'; + + @override + String get arenaSize => 'GRANDEZZA'; + + @override + String get timeAndOptions => 'TEMPO E OPZIONI'; + + @override + String get timeLabel => 'TEMPO'; + + @override + String get btnStart => 'AVVIA'; + + @override + String get btnCancel => 'ANNULLA'; + + @override + String get wordOr => 'OPPURE'; + + @override + String get codeHint => 'CODICE'; + + @override + String get publicLobbyTitle => 'LOBBY PUBBLICA'; + + @override + String get emptyLobbyMsg => + 'Nessuna stanza pubblica al momento.\nCreane una tu!'; + + @override + String get roomOf => 'Stanza di'; + + @override + String get btnEnter => 'ENTRA'; } diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index f404a38..585cf12 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -64,21 +64,48 @@ class StorageService { int get totalXP => _prefs.getInt('totalXP') ?? 0; + // --- SICUREZZA XP: Inviamo solo INCREMENTI al server --- Future addXP(int xp) async { + // Aggiorniamo il locale per la UI await _prefs.setInt('totalXP', totalXP + xp); - syncLeaderboard(); + + // Aggiorniamo il server in modo sicuro tramite incremento relativo + final user = FirebaseAuth.instance.currentUser; + if (user != null) { + await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({ + 'xp': FieldValue.increment(xp), + 'level': playerLevel, + }, SetOptions(merge: true)); + } } int get playerLevel => (totalXP / 100).floor() + 1; int get wins => _prefs.getInt('wins') ?? 0; + + // --- SICUREZZA WINS: Inviamo solo INCREMENTI al server --- Future addWin() async { await _prefs.setInt('wins', wins + 1); - syncLeaderboard(); + final user = FirebaseAuth.instance.currentUser; + if (user != null) { + await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({ + 'wins': FieldValue.increment(1), + }, SetOptions(merge: true)); + } } int get losses => _prefs.getInt('losses') ?? 0; - Future addLoss() async => await _prefs.setInt('losses', losses + 1); + + // --- SICUREZZA LOSSES: Inviamo solo INCREMENTI al server --- + Future addLoss() async { + await _prefs.setInt('losses', losses + 1); + final user = FirebaseAuth.instance.currentUser; + if (user != null) { + await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({ + 'losses': FieldValue.increment(1), + }, SetOptions(merge: true)); + } + } int get cpuLevel => _prefs.getInt('cpuLevel') ?? 1; Future saveCpuLevel(int level) async => await _prefs.setInt('cpuLevel', level); @@ -89,30 +116,25 @@ class StorageService { syncLeaderboard(); } - // --- SINCRONIZZAZIONE LEADERBOARD AGGIORNATA --- Future syncLeaderboard() async { try { final user = FirebaseAuth.instance.currentUser; - // BLOCCO TOTALE: Se non sei loggato, niente database! if (user == null) return; String name = playerName; - if (name.isEmpty) name = "GIOCATORE"; // Fallback di sicurezza + if (name.isEmpty) name = "GIOCATORE"; String targetUid = user.uid; - // Prepara i dati base + // --- SICUREZZA: Non inviamo PIÙ i valori assoluti di xp, wins e losses! --- + // Vengono aggiornati solo dagli incrementi protetti nelle funzioni sopra. Map dataToSave = { 'name': name, - 'xp': totalXP, 'level': playerLevel, - 'wins': wins, - 'losses': losses, 'lastActive': FieldValue.serverTimestamp(), }; - // IL TRUCCO: Aggiungiamo la data di registrazione estraendola da Firebase Auth! if (user.metadata.creationTime != null) { dataToSave['accountCreated'] = Timestamp.fromDate(user.metadata.creationTime!); } @@ -124,6 +146,19 @@ class StorageService { } } + Future isUserAdmin() async { + try { + final user = FirebaseAuth.instance.currentUser; + if (user == null) return false; + + final doc = await FirebaseFirestore.instance.collection('admins').doc(user.uid).get(); + return doc.exists; + } catch (e) { + debugPrint("Errore verifica admin: $e"); + return false; + } + } + List> get favorites { List favs = _prefs.getStringList('favorites') ?? []; return favs.map((e) => Map.from(jsonDecode(e))).toList(); diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index afdf21a..94df23e 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -7,6 +7,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/foundation.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; @@ -64,7 +65,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { super.initState(); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { - // MODIFICA QUI: Invece di currentUser == null, controlliamo se il nome è vuoto! if (StorageService.instance.playerName.isEmpty) { HomeModals.showNameDialog(context, () { StorageService.instance.syncLeaderboard(); @@ -81,6 +81,7 @@ class _HomeScreenState extends State with WidgetsBindingObserver { _initDeepLinks(); _listenToFavoritesOnline(); } + void _checkThemeSafety() { String themeStr = StorageService.instance.getTheme(); bool exists = AppThemeType.values.any((e) => e.toString() == themeStr); @@ -104,7 +105,8 @@ class _HomeScreenState extends State with WidgetsBindingObserver { if (state == AppLifecycleState.resumed) { _checkClipboardForInvite(); _listenToFavoritesOnline(); - } else if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { + } else if (state == AppLifecycleState.detached) { + // --- FIX BUG WHATSAPP: Rimossa l'eliminazione della stanza durante lo stato "paused" --- _cleanupGhostRoom(); } } @@ -198,7 +200,6 @@ class _HomeScreenState extends State with WidgetsBindingObserver { late OverlayEntry entry; bool removed = false; - // --- FIX OVERLAP POPUP: Più in basso (85) --- entry = OverlayEntry( builder: (context) => Positioned( top: MediaQuery.of(context).padding.top + 85, @@ -577,18 +578,38 @@ class _HomeScreenState extends State with WidgetsBindingObserver { child: Transform.rotate( angle: themeType == AppThemeType.doodle ? -0.04 : 0, child: GestureDetector( - onTap: () { - if (playerName.toUpperCase() == 'PAOLO') { - _debugTapCount++; - if (_debugTapCount == 5) { - StorageService.instance.addXP(2000); - setState(() {}); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("🛠 DEBUG MODE: +20 Livelli!", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), backgroundColor: Colors.purpleAccent, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))) - ); - } else if (_debugTapCount >= 7) { - _debugTapCount = 0; - Navigator.push(context, MaterialPageRoute(builder: (_) => AdminScreen())); + onTap: () async { + _debugTapCount++; + + // CHEAT LOCALE VIVO SOLO IN DEBUG MODE + if (kDebugMode && playerName.toUpperCase() == 'PAOLO' && _debugTapCount == 5) { + StorageService.instance.addXP(2000); + setState(() {}); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("🛠 DEBUG MODE: +20 Livelli!", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), backgroundColor: Colors.purpleAccent, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))) + ); + } + // ACCESSO DASHBOARD + else if (_debugTapCount >= 7) { + _debugTapCount = 0; + + if (kDebugMode && playerName.toUpperCase() == 'PAOLO') { + Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen())); + } else { + bool isAdmin = await StorageService.instance.isUserAdmin(); + + if (isAdmin && mounted) { + Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen())); + } else if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Accesso Negato: Non sei un Amministratore 🛑", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), + backgroundColor: Colors.redAccent, + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), + ) + ); + } } } }, @@ -791,7 +812,6 @@ class _FavoriteOnlinePopupState extends State with SingleTi position: _offsetAnimation, child: Material( color: Colors.transparent, - // --- FIX OVERLAP POPUP: Aggiunta Elevation Altissima (100) --- elevation: 100, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), diff --git a/lib/ui/multiplayer/lobby_screen.dart b/lib/ui/multiplayer/lobby_screen.dart index 0baf564..2e7e16b 100644 --- a/lib/ui/multiplayer/lobby_screen.dart +++ b/lib/ui/multiplayer/lobby_screen.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:tetraq/l10n/app_localizations.dart'; // <-- IMPORT DEL DIZIONARIO! import '../../logic/game_controller.dart'; import '../../models/game_board.dart'; @@ -71,7 +72,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { - if (state == AppLifecycleState.paused || state == AppLifecycleState.detached) { + if (state == AppLifecycleState.detached) { _cleanupGhostRoom(); } } @@ -222,12 +223,13 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { builder: (context) { final theme = context.watch().currentColors; final themeType = context.read().currentThemeType; + final loc = AppLocalizations.of(context)!; Widget dialogContent = Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(color: theme.playerRed), const SizedBox(height: 25), - Text("CODICE STANZA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))), + Text(loc.codeHint, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))), Text(code, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 40, fontWeight: FontWeight.w900, color: theme.playerRed, letterSpacing: 8, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 10)]))), const SizedBox(height: 25), Transform.rotate( @@ -305,7 +307,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { _cleanupGhostRoom(); Navigator.pop(context); }, - child: Text("ANNULLA", style: getLobbyTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))), + child: Text(loc.btnCancel.toUpperCase(), style: getLobbyTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))), ), ], ), @@ -348,6 +350,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { final themeManager = context.watch(); final themeType = themeManager.currentThemeType; final theme = themeManager.currentColors; + final loc = AppLocalizations.of(context)!; // <-- CHIAMATA AL DIZIONARIO String? bgImage; if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg'; @@ -369,7 +372,6 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { panelBackgroundColor = Colors.black.withOpacity(0.4); } - Widget hostPanel = Transform.rotate( angle: themeType == AppThemeType.doodle ? 0.01 : 0, child: Container( @@ -387,10 +389,10 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Center(child: Text("IMPOSTAZIONI STANZA", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))), + Center(child: Text(loc.roomSettings, textAlign: TextAlign.center, style: getLobbyTextStyle(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: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), + Text(loc.arenaShape, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), const SizedBox(height: 6), Row( @@ -412,7 +414,7 @@ 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("GRANDEZZA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), + Text(loc.arenaSize, style: getLobbyTextStyle(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), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -428,7 +430,7 @@ 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("TEMPO E OPZIONI", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))), + Text(loc.timeAndOptions, style: getLobbyTextStyle(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), Row( @@ -470,7 +472,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { onPressed: () => Navigator.pop(context), ), Expanded( - child: Text("MULTIPLAYER", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))), + child: Text(loc.onlineTitle.toUpperCase(), textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))), ), const SizedBox(width: 48), ], @@ -490,11 +492,11 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { Row( children: [ Expanded( - child: NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType), + child: NeonActionButton(label: loc.btnStart.toUpperCase(), color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType), ), const SizedBox(width: 10), Expanded( - child: NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType), + child: NeonActionButton(label: loc.btnCancel.toUpperCase(), color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType), ), ], ), @@ -503,12 +505,12 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { : Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType), + NeonActionButton(label: loc.createMatch.toUpperCase(), color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType), const SizedBox(height: 20), 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("OPPURE", style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))), + Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text(loc.wordOr.toUpperCase(), style: getLobbyTextStyle(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)), ], ), @@ -530,7 +532,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { style: getLobbyTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])), decoration: InputDecoration( contentPadding: const EdgeInsets.symmetric(vertical: 12), - hintText: "CODICE", hintStyle: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "", + hintText: loc.codeHint.toUpperCase(), hintStyle: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "", filled: themeType != AppThemeType.doodle, fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05), enabledBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2.0), borderRadius: BorderRadius.circular(15)), @@ -540,7 +542,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { ), ), const SizedBox(height: 15), - NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType), + NeonActionButton(label: loc.joinMatch.toUpperCase(), color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType), ], ), ), @@ -549,7 +551,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { 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: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))), + Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text(loc.publicLobbyTitle.toUpperCase(), style: getLobbyTextStyle(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)), ], ), @@ -565,7 +567,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { return Padding( padding: const EdgeInsets.symmetric(vertical: 40.0), - child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))), + child: Center(child: Text(loc.emptyLobbyMsg, textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))), ); } @@ -591,7 +593,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { if (docs.isEmpty) { return Padding( padding: const EdgeInsets.symmetric(vertical: 40.0), - child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))), + child: Center(child: Text(loc.emptyLobbyMsg, textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))), ); } @@ -610,7 +612,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { itemBuilder: (context, index) { var doc = docs[index]; var data = doc.data() as Map; - String host = data['hostName'] ?? 'Sconosciuto'; + String host = data['hostName'] ?? 'Guest'; int r = data['radius'] ?? 4; String shapeStr = data['shape'] ?? 'classic'; @@ -644,7 +646,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Stanza di $host", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 18))), + Text("${loc.roomOf} $host", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 18))), const SizedBox(height: 6), Text("Raggio: $r • $prettyShape • $prettyTime", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 12))), ], @@ -659,7 +661,7 @@ class _LobbyScreenState extends State with WidgetsBindingObserver { side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 2) : BorderSide.none, ), onPressed: () => _joinRoomByCode(doc.id), - child: Text("ENTRA", style: getLobbyTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))), + child: Text(loc.btnEnter.toUpperCase(), style: getLobbyTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))), ) ], ), diff --git a/pubspec.yaml b/pubspec.yaml index f22bc08..1d578cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: tetraq description: A new Flutter project. publish_to: 'none' -version: 1.1.5+7 +version: 1.1.6+8 environment: sdk: ^3.10.7