Auto-sync: 20260321_000000

This commit is contained in:
Paolo 2026-03-21 00:00:01 +01:00
parent 209027b221
commit 5b99d5f0bb
18 changed files with 619 additions and 133 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -5,31 +5,31 @@
<application
android:label="tetraq"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tetraq" android:host="join" />
</intent-filter>
</activity>
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"> <activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tetraq" android:host="join" />
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />

View file

@ -2,61 +2,67 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Tetraq</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>tetraq</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.sanza.tetraq</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tetraq</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Tetraq</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>tetraq</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.sanza.tetraq</string>
<key>CFBundleURLSchemes</key>
<array>
<string>tetraq</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
</plist>

View file

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

View file

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

View file

@ -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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -64,21 +64,48 @@ class StorageService {
int get totalXP => _prefs.getInt('totalXP') ?? 0;
// --- SICUREZZA XP: Inviamo solo INCREMENTI al server ---
Future<void> 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<void> 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<void> addLoss() async => await _prefs.setInt('losses', losses + 1);
// --- SICUREZZA LOSSES: Inviamo solo INCREMENTI al server ---
Future<void> 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<void> saveCpuLevel(int level) async => await _prefs.setInt('cpuLevel', level);
@ -89,30 +116,25 @@ class StorageService {
syncLeaderboard();
}
// --- SINCRONIZZAZIONE LEADERBOARD AGGIORNATA ---
Future<void> 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<String, dynamic> 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<bool> 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<Map<String, String>> get favorites {
List<String> favs = _prefs.getStringList('favorites') ?? [];
return favs.map((e) => Map<String, String>.from(jsonDecode(e))).toList();

View file

@ -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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<HomeScreen> 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<FavoriteOnlinePopup> 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),

View file

@ -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<LobbyScreen> 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<LobbyScreen> with WidgetsBindingObserver {
builder: (context) {
final theme = context.watch<ThemeManager>().currentColors;
final themeType = context.read<ThemeManager>().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<LobbyScreen> 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<LobbyScreen> with WidgetsBindingObserver {
final themeManager = context.watch<ThemeManager>();
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<LobbyScreen> 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<LobbyScreen> 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<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("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<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("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<LobbyScreen> 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<LobbyScreen> 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<LobbyScreen> 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<LobbyScreen> 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<LobbyScreen> 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<LobbyScreen> 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<LobbyScreen> 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<LobbyScreen> 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<LobbyScreen> with WidgetsBindingObserver {
itemBuilder: (context, index) {
var doc = docs[index];
var data = doc.data() as Map<String, dynamic>;
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<LobbyScreen> 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<LobbyScreen> 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))),
)
],
),

View file

@ -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