Auto-sync: 20260315_170000
This commit is contained in:
parent
8caecd401e
commit
917a532529
23 changed files with 637 additions and 313 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
|
|
@ -1,2 +1,3 @@
|
|||
404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3
|
||||
index.html,1773586765860,5737ce966fa8786becaf7f36a32992cf44102fb3a217c226c30576c993b33e63
|
||||
404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3
|
||||
report.html,1773588057140,876c6baaa912c9abfb81ee70e9868d84476b1c204ebca4c99f458f300661a36b
|
||||
|
|
|
|||
BIN
assets/.DS_Store
vendored
BIN
assets/.DS_Store
vendored
Binary file not shown.
BIN
assets/audio/.DS_Store
vendored
BIN
assets/audio/.DS_Store
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
BIN
assets/images/sfondo_temi.jpg
Normal file
BIN
assets/images/sfondo_temi.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB |
|
|
@ -1206,6 +1206,8 @@ PODS:
|
|||
- Firebase/Firestore (= 12.9.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Firebase/Auth (12.9.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseAuth (~> 12.9.0)
|
||||
|
|
@ -1410,6 +1412,7 @@ DEPENDENCIES:
|
|||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
|
||||
- cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- firebase_app_check (from `.symlinks/plugins/firebase_app_check/ios`)
|
||||
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
|
|
@ -1450,6 +1453,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/audioplayers_darwin/ios"
|
||||
cloud_firestore:
|
||||
:path: ".symlinks/plugins/cloud_firestore/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
firebase_app_check:
|
||||
:path: ".symlinks/plugins/firebase_app_check/ios"
|
||||
firebase_auth:
|
||||
|
|
@ -1472,6 +1477,7 @@ SPEC CHECKSUMS:
|
|||
audioplayers_darwin: ccf9c770ee768abb07e26d90af093f7bab1c12ab
|
||||
BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508
|
||||
cloud_firestore: 81f6c428ecee874dc3808afe0e0c48a87beb5bdf
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
|
||||
firebase_app_check: 33f1df6830ec8ebadee0db0120956c44a65c7213
|
||||
firebase_auth: fecf9fe293464b52063f5f2a7110e63ff2ab3403
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio, music }
|
||||
enum AppThemeType { doodle, cyberpunk, arcade, grimorio, music }
|
||||
|
||||
class ThemeColors {
|
||||
final Color background;
|
||||
|
|
@ -34,18 +34,13 @@ class AppColors {
|
|||
playerRed: Color(0xFFFF007F), playerBlue: Color(0xFF69F0AE), text: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
static const ThemeColors wood = ThemeColors(
|
||||
background: Color(0xFF905D3B), gridLine: Color(0xFF4A301E),
|
||||
playerRed: Color(0xFFE53935), playerBlue: Color(0xFF29B6F6), text: Color(0xFFFBE9E7),
|
||||
);
|
||||
|
||||
static const ThemeColors arcade = ThemeColors(
|
||||
background: Color(0xFF111111), gridLine: Color(0xFF00FF00),
|
||||
playerRed: Color(0xFFFF004D), playerBlue: Color(0xFF00E5FF), text: Color(0xFFFFFFFF),
|
||||
);
|
||||
|
||||
static const ThemeColors grimorio = ThemeColors(
|
||||
background: Color(0xFF1E112A), gridLine: Colors.black, // <--- Modificato in nero!
|
||||
background: Color(0xFF1E112A), gridLine: Colors.black,
|
||||
playerRed: Color(0xFFE91E63), playerBlue: Color(0xFF4FC3F7), text: Color(0xFFFFF3E0),
|
||||
);
|
||||
|
||||
|
|
@ -60,7 +55,6 @@ class AppColors {
|
|||
static ThemeColors getTheme(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.doodle: return doodle;
|
||||
case AppThemeType.wood: return wood;
|
||||
case AppThemeType.cyberpunk: return cyberpunk;
|
||||
case AppThemeType.arcade: return arcade;
|
||||
case AppThemeType.grimorio: return grimorio;
|
||||
|
|
@ -73,7 +67,6 @@ class ThemeIcons {
|
|||
static IconData gold(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.star;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.gem;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip;
|
||||
case AppThemeType.arcade: return FontAwesomeIcons.coins;
|
||||
case AppThemeType.grimorio: return FontAwesomeIcons.crown;
|
||||
|
|
@ -84,7 +77,6 @@ class ThemeIcons {
|
|||
static IconData bomb(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.virus;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.fire;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.bug;
|
||||
case AppThemeType.arcade: return FontAwesomeIcons.ghost;
|
||||
case AppThemeType.grimorio: return FontAwesomeIcons.hatWizard;
|
||||
|
|
@ -95,7 +87,6 @@ class ThemeIcons {
|
|||
static IconData swap(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.arrowsRotate;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.rightLeft;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired;
|
||||
case AppThemeType.arcade: return FontAwesomeIcons.shuffle;
|
||||
case AppThemeType.grimorio: return FontAwesomeIcons.hurricane;
|
||||
|
|
@ -106,7 +97,6 @@ class ThemeIcons {
|
|||
static IconData joker(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.faceSmileBeam;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.key;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.robot;
|
||||
case AppThemeType.arcade: return FontAwesomeIcons.gamepad;
|
||||
case AppThemeType.grimorio: return FontAwesomeIcons.masksTheater;
|
||||
|
|
@ -117,7 +107,6 @@ class ThemeIcons {
|
|||
static IconData block(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.squareXmark;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.ban;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved;
|
||||
case AppThemeType.arcade: return FontAwesomeIcons.powerOff;
|
||||
case AppThemeType.grimorio: return FontAwesomeIcons.meteor;
|
||||
|
|
|
|||
|
|
@ -3,31 +3,64 @@
|
|||
// ===========================================================================
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'app_colors.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../services/audio_service.dart'; // <-- NUOVO IMPORT PER LA MUSICA
|
||||
|
||||
class ThemeManager extends ChangeNotifier {
|
||||
late AppThemeType _currentThemeType;
|
||||
// --- ENUM DEI TEMI AGGIORNATO ---
|
||||
const Map<AppThemeType, IconData> themeIcons = {
|
||||
AppThemeType.cyberpunk: Icons.electric_bolt,
|
||||
AppThemeType.doodle: Icons.brush,
|
||||
AppThemeType.music: Icons.headset_mic,
|
||||
AppThemeType.arcade: Icons.videogame_asset,
|
||||
AppThemeType.grimorio: Icons.auto_stories,
|
||||
};
|
||||
|
||||
ThemeManager() {
|
||||
// Quando l'app parte, legge il tema dalla memoria!
|
||||
_currentThemeType = AppThemeType.values[StorageService.instance.savedThemeIndex];
|
||||
const Map<AppThemeType, String> themeNames = {
|
||||
AppThemeType.cyberpunk: "Cyberpunk",
|
||||
AppThemeType.doodle: "Doodle",
|
||||
AppThemeType.music: "Music",
|
||||
AppThemeType.arcade: "Arcade",
|
||||
AppThemeType.grimorio: "Grimorio",
|
||||
};
|
||||
|
||||
// Fai partire subito la colonna sonora del tema salvato!
|
||||
AudioService.instance.playBgm(_currentThemeType);
|
||||
}
|
||||
class ThemeManager with ChangeNotifier {
|
||||
AppThemeType _currentThemeType = AppThemeType.doodle;
|
||||
ThemeColors _currentColors = AppColors.getTheme(AppThemeType.doodle);
|
||||
|
||||
AppThemeType get currentThemeType => _currentThemeType;
|
||||
ThemeColors get currentColors => AppColors.getTheme(_currentThemeType);
|
||||
ThemeColors get currentColors => _currentColors;
|
||||
|
||||
ThemeManager() {
|
||||
_loadTheme();
|
||||
}
|
||||
|
||||
void _loadTheme() async {
|
||||
String themeStr = StorageService.instance.getTheme();
|
||||
AppThemeType loadedType = AppThemeType.values.firstWhere(
|
||||
(e) => e.toString() == themeStr,
|
||||
orElse: () => AppThemeType.doodle
|
||||
);
|
||||
_currentThemeType = loadedType;
|
||||
_currentColors = AppColors.getTheme(loadedType);
|
||||
_updateSystemUI();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setTheme(AppThemeType type) {
|
||||
_currentThemeType = type;
|
||||
StorageService.instance.saveTheme(type); // Salva la scelta nel "disco fisso"
|
||||
|
||||
// Cambia magicamente la canzone in sottofondo!
|
||||
AudioService.instance.playBgm(type);
|
||||
|
||||
_currentColors = AppColors.getTheme(type);
|
||||
StorageService.instance.saveTheme(type.toString());
|
||||
_updateSystemUI();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _updateSystemUI() {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: _currentThemeType == AppThemeType.doodle ? Brightness.dark : Brightness.light,
|
||||
systemNavigationBarColor: _currentColors.background,
|
||||
systemNavigationBarIconBrightness: _currentThemeType == AppThemeType.doodle ? Brightness.dark : Brightness.light,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -326,20 +326,14 @@ class GameController extends ChangeNotifier {
|
|||
void _handleTimeOut() {
|
||||
if (!isTimeMode || isSetupPhase) return;
|
||||
|
||||
// Solo chi deve giocare può subire il timeout (se è online)
|
||||
if (isOnline && board.currentPlayer != myPlayer) return;
|
||||
|
||||
// 1. Raccogliamo TUTTE le linee ancora libere e giocabili
|
||||
List<Line> availableLines = board.lines.where((l) => l.owner == Player.none && l.isPlayable).toList();
|
||||
|
||||
// Sicurezza: se non ci sono mosse, non facciamo nulla
|
||||
if (availableLines.isEmpty) return;
|
||||
|
||||
// 2. Scegliamo una linea in modo PURAMENTE CASUALE (nessuna intelligenza artificiale)
|
||||
final random = Random();
|
||||
Line randomMove = availableLines[random.nextInt(availableLines.length)];
|
||||
|
||||
// 3. Eseguiamo la mossa forzata
|
||||
handleLineTap(randomMove, _activeTheme, forced: true);
|
||||
}
|
||||
|
||||
|
|
@ -549,16 +543,17 @@ class GameController extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
// --- NUOVI LIVELLI DI SBLOCCO ---
|
||||
List<String> _getUnlocks(int oldLevel, int newLevel) {
|
||||
List<String> unlocks = [];
|
||||
for(int i = oldLevel + 1; i <= newLevel; i++) {
|
||||
if (i == 3) unlocks.add("Tema: Legno & Fiammiferi");
|
||||
if (i == 7) unlocks.add("Tema: Cyberpunk");
|
||||
if (i == 10) {
|
||||
if (i == 3) unlocks.add("Tema: Cyberpunk");
|
||||
if (i == 7) {
|
||||
unlocks.add("Tema: 8-Bit Arcade");
|
||||
unlocks.add("Forma Arena: Caos");
|
||||
}
|
||||
if (i == 15) unlocks.add("Tema: Grimorio");
|
||||
if (i == 10) unlocks.add("Tema: Grimorio");
|
||||
if (i == 15) unlocks.add("Tema: Musica");
|
||||
}
|
||||
return unlocks;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,9 @@ class AudioService extends ChangeNotifier {
|
|||
await prefs.setBool('isMuted', isMuted);
|
||||
|
||||
if (isMuted) {
|
||||
// Se abbiamo appena silenziato, FERMA TUTTO immediatamente.
|
||||
await _bgmPlayer.pause();
|
||||
await _sfxPlayer.stop();
|
||||
} else {
|
||||
// Se riaccendiamo, fai ripartire la canzone
|
||||
playBgm(_currentTheme);
|
||||
}
|
||||
notifyListeners();
|
||||
|
|
@ -54,9 +52,6 @@ class AudioService extends ChangeNotifier {
|
|||
case AppThemeType.doodle:
|
||||
audioPath = 'audio/bgm/Quad_Dreams.mp3';
|
||||
break;
|
||||
case AppThemeType.wood:
|
||||
audioPath = 'audio/bgm/Legno_Canopy.mp3';
|
||||
break;
|
||||
case AppThemeType.arcade:
|
||||
audioPath = 'audio/bgm/8-bit_Prowler.mp3';
|
||||
break;
|
||||
|
|
@ -64,7 +59,7 @@ class AudioService extends ChangeNotifier {
|
|||
audioPath = 'audio/bgm/Grimorio_Astral.mp3';
|
||||
break;
|
||||
case AppThemeType.music:
|
||||
audioPath = 'audio/bgm/Music_Loop.mp3'; // <-- DEVI INSERIRE QUESTO FILE IN ASSETS
|
||||
audioPath = 'audio/bgm/Music_Loop.mp3';
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -86,10 +81,9 @@ class AudioService extends ChangeNotifier {
|
|||
String file = '';
|
||||
switch (theme) {
|
||||
case AppThemeType.arcade:
|
||||
case AppThemeType.music: // Usiamo l'effetto arcade o cyber per la musica
|
||||
case AppThemeType.music:
|
||||
file = 'minimal_line.wav'; break;
|
||||
case AppThemeType.doodle:
|
||||
case AppThemeType.wood:
|
||||
file = 'doodle_line.wav'; break;
|
||||
case AppThemeType.cyberpunk:
|
||||
case AppThemeType.grimorio:
|
||||
|
|
@ -113,7 +107,6 @@ class AudioService extends ChangeNotifier {
|
|||
case AppThemeType.music:
|
||||
file = 'minimal_box.wav'; break;
|
||||
case AppThemeType.doodle:
|
||||
case AppThemeType.wood:
|
||||
file = 'doodle_box.wav'; break;
|
||||
case AppThemeType.cyberpunk:
|
||||
case AppThemeType.grimorio:
|
||||
|
|
|
|||
|
|
@ -64,13 +64,17 @@ class MultiplayerService {
|
|||
}
|
||||
|
||||
void shareInviteLink(String roomCode) {
|
||||
// ECCO IL TUO SMART LINK FIREBASE!
|
||||
String smartLink = "https://tetraq-32a4a.web.app";
|
||||
|
||||
String message = "Ehi! Giochiamo a TetraQ? 🎮\n\n"
|
||||
"Clicca su questo link per entrare direttamente in stanza:\n"
|
||||
"Apri l'app e inserisci il codice stanza:\n"
|
||||
"👉 $roomCode\n\n"
|
||||
"Oppure clicca qui se il tuo telefono lo supporta:\n"
|
||||
"tetraq://join?code=$roomCode\n\n"
|
||||
"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";
|
||||
"Non hai ancora il gioco? Scaricalo da qui:\n"
|
||||
"$smartLink";
|
||||
|
||||
Share.share(message);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import '../core/app_colors.dart';
|
|||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart'; // <-- NUOVO IMPORT
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
class StorageService {
|
||||
static final StorageService instance = StorageService._internal();
|
||||
|
|
@ -26,7 +26,6 @@ class StorageService {
|
|||
_sessionStart = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
// --- RECUPERO IP E CITTÀ IN BACKGROUND ---
|
||||
Future<void> _fetchLocationData() async {
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
|
|
@ -43,10 +42,10 @@ class StorageService {
|
|||
|
||||
String get lastIp => _prefs.getString('last_ip') ?? 'Sconosciuto';
|
||||
String get lastCity => _prefs.getString('last_city') ?? 'Sconosciuta';
|
||||
// ------------------------------------------------
|
||||
|
||||
int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.doodle.index;
|
||||
Future<void> saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index);
|
||||
// --- METODI TEMA AGGIORNATI A STRINGHE ---
|
||||
String getTheme() => _prefs.getString('theme') ?? AppThemeType.doodle.toString();
|
||||
Future<void> saveTheme(String themeStr) async => await _prefs.setString('theme', themeStr);
|
||||
|
||||
int get savedRadius => _prefs.getInt('radius') ?? 2;
|
||||
Future<void> saveRadius(int radius) async => await _prefs.setInt('radius', radius);
|
||||
|
|
@ -87,19 +86,16 @@ class StorageService {
|
|||
final user = FirebaseAuth.instance.currentUser;
|
||||
|
||||
if (user != null) {
|
||||
// IDENTIFICA IL SISTEMA OPERATIVO E LA VERSIONE APP
|
||||
String currentPlatform = "Sconosciuta";
|
||||
String appVersion = "N/D";
|
||||
String deviceModel = "Sconosciuto"; // <-- NUOVO: MODELLO HARDWARE
|
||||
String deviceModel = "Sconosciuto";
|
||||
|
||||
if (!kIsWeb) {
|
||||
// Leggi Piattaforma base
|
||||
if (Platform.isAndroid) currentPlatform = "Android";
|
||||
else if (Platform.isIOS) currentPlatform = "iOS";
|
||||
else if (Platform.isMacOS) currentPlatform = "macOS";
|
||||
else if (Platform.isWindows) currentPlatform = "Windows";
|
||||
|
||||
// Leggi Versione App
|
||||
try {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
appVersion = "${packageInfo.version}+${packageInfo.buildNumber}";
|
||||
|
|
@ -107,25 +103,23 @@ class StorageService {
|
|||
debugPrint("Errore lettura versione: $e");
|
||||
}
|
||||
|
||||
// Leggi Modello Hardware
|
||||
try {
|
||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||
if (Platform.isAndroid) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||
deviceModel = "${androidInfo.manufacturer} ${androidInfo.model}"; // Es. samsung SM-S928B
|
||||
deviceModel = "${androidInfo.manufacturer} ${androidInfo.model}";
|
||||
} else if (Platform.isIOS) {
|
||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||
deviceModel = iosInfo.utsname.machine; // Es. iPhone13,2
|
||||
deviceModel = iosInfo.utsname.machine;
|
||||
} else if (Platform.isMacOS) {
|
||||
MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo;
|
||||
deviceModel = macInfo.model; // Es. MacBookPro16,1
|
||||
deviceModel = macInfo.model;
|
||||
}
|
||||
} catch(e) {
|
||||
debugPrint("Errore lettura hardware: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// AGGIORNA IL TEMPO DI UTILIZZO
|
||||
if (_sessionStart != 0) {
|
||||
int now = DateTime.now().millisecondsSinceEpoch;
|
||||
int sessionSeconds = (now - _sessionStart) ~/ 1000;
|
||||
|
|
@ -145,7 +139,7 @@ class StorageService {
|
|||
'city': lastCity,
|
||||
'playtime': totalPlaytime,
|
||||
'appVersion': appVersion,
|
||||
'deviceModel': deviceModel, // Salva il modello hardware
|
||||
'deviceModel': deviceModel,
|
||||
}, SetOptions(merge: true));
|
||||
}
|
||||
} catch(e) {
|
||||
|
|
@ -154,7 +148,6 @@ class StorageService {
|
|||
}
|
||||
}
|
||||
|
||||
// --- GESTIONE PREFERITI (RUBRICA LOCALE) ---
|
||||
List<Map<String, String>> get favorites {
|
||||
List<String> favs = _prefs.getStringList('favorites') ?? [];
|
||||
return favs.map((e) => Map<String, String>.from(jsonDecode(e))).toList();
|
||||
|
|
|
|||
|
|
@ -84,10 +84,6 @@ class BoardPainter extends CustomPainter {
|
|||
if (themeType == AppThemeType.music) {
|
||||
fillPaint.color = Colors.white.withOpacity(0.08);
|
||||
canvas.drawPath(arenaShape, fillPaint);
|
||||
} else if (themeType == AppThemeType.wood) {
|
||||
fillPaint.color = Colors.black.withOpacity(0.3);
|
||||
fillPaint.maskFilter = const MaskFilter.blur(BlurStyle.normal, 15.0);
|
||||
canvas.drawPath(arenaShape, fillPaint);
|
||||
} else if (themeType == AppThemeType.cyberpunk) {
|
||||
fillPaint.color = theme.playerBlue.withOpacity(0.1);
|
||||
canvas.drawPath(arenaShape, fillPaint);
|
||||
|
|
@ -99,7 +95,7 @@ class BoardPainter extends CustomPainter {
|
|||
|
||||
final outlinePaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = baseStroke * 0.5 // Moltiplicato per 0.5 = grande la metà delle linee interne!
|
||||
..strokeWidth = baseStroke * 0.5
|
||||
..strokeJoin = StrokeJoin.round;
|
||||
|
||||
if (themeType == AppThemeType.cyberpunk) {
|
||||
|
|
@ -108,12 +104,8 @@ class BoardPainter extends CustomPainter {
|
|||
}
|
||||
else if (themeType == AppThemeType.arcade) { outlinePaint.color = Colors.white; }
|
||||
else if (themeType == AppThemeType.grimorio) { outlinePaint.color = theme.gridLine.withOpacity(0.6); }
|
||||
else if (themeType == AppThemeType.music) { outlinePaint.color = Colors.black; } // Rimosso lo spessore forzato a 8.0!
|
||||
else if (themeType == AppThemeType.doodle) { outlinePaint.color = const Color(0xFF111122); } // Rimosso lo spessore forzato a 6.0!
|
||||
else if (themeType == AppThemeType.wood) {
|
||||
outlinePaint.color = const Color(0xFF3E2723);
|
||||
outlinePaint.maskFilter = const MaskFilter.blur(BlurStyle.normal, 2.0);
|
||||
}
|
||||
else if (themeType == AppThemeType.music) { outlinePaint.color = Colors.black; }
|
||||
else if (themeType == AppThemeType.doodle) { outlinePaint.color = const Color(0xFF111122); }
|
||||
else { outlinePaint.color = theme.gridLine.withOpacity(0.8); }
|
||||
|
||||
// Disegniamo il contorno
|
||||
|
|
@ -143,9 +135,7 @@ class BoardPainter extends CustomPainter {
|
|||
..style = PaintingStyle.fill
|
||||
..color = box.owner == Player.red ? theme.playerRed.withOpacity(0.6) : theme.playerBlue.withOpacity(0.6);
|
||||
|
||||
if (themeType == AppThemeType.wood) {
|
||||
_drawFlameBox(canvas, rect, box.owner == Player.red);
|
||||
} else if (themeType == AppThemeType.doodle) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color penColor = box.owner == Player.red ? Colors.redAccent.shade700 : Colors.blueAccent.shade700;
|
||||
_drawScribbleBox(canvas, rect, penColor);
|
||||
} else if (themeType == AppThemeType.arcade) {
|
||||
|
|
@ -197,7 +187,7 @@ class BoardPainter extends CustomPainter {
|
|||
// --- DISEGNO DELLA LINEA "INCRINATA" DAL GHIACCIO ---
|
||||
if (line.isIceCracked) {
|
||||
_drawCrackedIceLine(canvas, p1, p2, blinkValue);
|
||||
continue; // Non ha ancora un proprietario, passiamo alla prossima!
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isLastMove = (line == board.lastMove);
|
||||
|
|
@ -205,19 +195,11 @@ class BoardPainter extends CustomPainter {
|
|||
? theme.gridLine.withOpacity(0.4)
|
||||
: (line.owner == Player.red ? theme.playerRed : theme.playerBlue);
|
||||
|
||||
if (isLastMove && line.owner != Player.none && themeType != AppThemeType.wood && themeType != AppThemeType.cyberpunk && themeType != AppThemeType.arcade && themeType != AppThemeType.grimorio) {
|
||||
if (isLastMove && line.owner != Player.none && themeType != AppThemeType.cyberpunk && themeType != AppThemeType.arcade && themeType != AppThemeType.grimorio) {
|
||||
canvas.drawLine(p1, p2, Paint()..color = Colors.white.withOpacity(blinkValue * 0.5)..strokeWidth = 16.0..strokeCap = StrokeCap.round..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6.0));
|
||||
}
|
||||
|
||||
if (themeType == AppThemeType.wood) {
|
||||
if (line.owner == Player.none) {
|
||||
canvas.drawLine(p1, p2, Paint()..color = const Color(0xFF3E2723).withOpacity(0.3)..strokeWidth = 4.5..strokeCap = StrokeCap.round);
|
||||
} else {
|
||||
Color headColor = lineColor;
|
||||
if (isLastMove) headColor = Color.lerp(headColor, Colors.yellow, blinkValue * 0.8) ?? headColor;
|
||||
_drawRealisticMatch(canvas, p1, p2, headColor, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||
}
|
||||
} else if (themeType == AppThemeType.cyberpunk) {
|
||||
if (themeType == AppThemeType.cyberpunk) {
|
||||
_drawNeonLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||
} else if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = line.owner == Player.none ? Colors.black.withOpacity(0.05) : lineColor;
|
||||
|
|
@ -228,7 +210,6 @@ class BoardPainter extends CustomPainter {
|
|||
} else if (themeType == AppThemeType.grimorio) {
|
||||
_drawGrimorioLine(canvas, p1, p2, lineColor, line.owner != Player.none, isLastMove: isLastMove, blinkValue: blinkValue);
|
||||
} else if (themeType == AppThemeType.music) {
|
||||
// Linee nere per la base nel tema musica
|
||||
if (line.owner == Player.none) lineColor = Colors.black.withOpacity(0.4);
|
||||
canvas.drawLine(p1, p2, Paint()..color = lineColor..strokeWidth = isLastMove ? 6.0 + (2.0 * blinkValue) : 6.0..strokeCap = StrokeCap.round);
|
||||
} else {
|
||||
|
|
@ -247,9 +228,7 @@ class BoardPainter extends CustomPainter {
|
|||
|
||||
for (var dot in activeDots) {
|
||||
Offset pos = getScreenPos(dot.x, dot.y);
|
||||
if (themeType == AppThemeType.wood) {
|
||||
canvas.drawCircle(pos, 3.5, dotPaint..color = const Color(0xFF3E2723).withOpacity(0.2));
|
||||
} else if (themeType == AppThemeType.cyberpunk) {
|
||||
if (themeType == AppThemeType.cyberpunk) {
|
||||
canvas.drawCircle(pos, 6.0, Paint()..color = theme.gridLine.withOpacity(0.3));
|
||||
canvas.drawCircle(pos, 3.0, Paint()..color = Colors.white.withOpacity(0.5));
|
||||
} else if (themeType == AppThemeType.doodle) {
|
||||
|
|
@ -262,7 +241,6 @@ class BoardPainter extends CustomPainter {
|
|||
Path crystal = Path()..moveTo(pos.dx, pos.dy - 5)..lineTo(pos.dx + 3, pos.dy)..lineTo(pos.dx, pos.dy + 5)..lineTo(pos.dx - 3, pos.dy)..close();
|
||||
canvas.drawPath(crystal, dotPaint..color = theme.gridLine.withOpacity(0.8));
|
||||
} else if (themeType == AppThemeType.music) {
|
||||
// Pallini (dots) neri per staccare dal fondo chiaro
|
||||
canvas.drawCircle(pos, 4.5, dotPaint..color = Colors.black87);
|
||||
} else {
|
||||
canvas.drawCircle(pos, 5.0, dotPaint..color = theme.text.withOpacity(0.6));
|
||||
|
|
@ -294,7 +272,6 @@ class BoardPainter extends CustomPainter {
|
|||
..strokeCap = StrokeCap.round
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 2.0);
|
||||
|
||||
// Effetto linea frammentata
|
||||
canvas.drawLine(p1, p2, Paint()..color = Colors.cyan.withOpacity(0.2)..strokeWidth=6.0);
|
||||
|
||||
Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy);
|
||||
|
|
@ -366,19 +343,6 @@ class BoardPainter extends CustomPainter {
|
|||
canvas.drawPath(thread1, threadPaint); canvas.drawPath(thread2, threadPaint..color = Colors.white.withOpacity(0.5));
|
||||
}
|
||||
|
||||
void _drawFlameBox(Canvas canvas, Rect baseRect, bool isRed) {
|
||||
final rand = Random((baseRect.left + baseRect.top).toInt());
|
||||
Offset center = baseRect.center; double w = baseRect.width * 0.35; double h = baseRect.height * 0.55; Offset bottomCenter = Offset(center.dx, center.dy + h * 0.5);
|
||||
Color outerColor = isRed ? Colors.red.shade600.withOpacity(0.85) : Colors.blue.shade700.withOpacity(0.85); Color midColor = isRed ? Colors.orangeAccent : Colors.lightBlueAccent; Color coreColor = isRed ? Colors.yellowAccent : Colors.white;
|
||||
canvas.drawOval(Rect.fromCenter(center: bottomCenter, width: w * 1.5, height: w * 0.5), Paint()..color = Colors.black.withOpacity(0.4)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0));
|
||||
void drawFlameLayer(double scale, Color color, double tipOffsetX) {
|
||||
Path path = Path(); double fw = w * scale; double fh = h * scale;
|
||||
path.moveTo(bottomCenter.dx, bottomCenter.dy); path.cubicTo(bottomCenter.dx + fw, bottomCenter.dy, bottomCenter.dx + fw * 0.8, bottomCenter.dy - fh * 0.6, bottomCenter.dx + tipOffsetX, bottomCenter.dy - fh); path.cubicTo(bottomCenter.dx - fw * 0.8, bottomCenter.dy - fh * 0.6, bottomCenter.dx - fw, bottomCenter.dy, bottomCenter.dx, bottomCenter.dy);
|
||||
canvas.drawPath(path, Paint()..color = color..style = PaintingStyle.fill..maskFilter = const MaskFilter.blur(BlurStyle.normal, 1.5));
|
||||
}
|
||||
double randomTipX = (rand.nextDouble() - 0.5) * w * 0.8; drawFlameLayer(1.0, outerColor, randomTipX); drawFlameLayer(0.65, midColor.withOpacity(0.9), randomTipX * 0.6); drawFlameLayer(0.35, coreColor.withOpacity(0.9), randomTipX * 0.2);
|
||||
}
|
||||
|
||||
void _drawScribbleBox(Canvas canvas, Rect baseRect, Color color) {
|
||||
final rand = Random((baseRect.left + baseRect.top).toInt());
|
||||
final paint = Paint()..color = color.withOpacity(0.85)..style = PaintingStyle.stroke..strokeWidth = 3.5..strokeCap = StrokeCap.round..strokeJoin = StrokeJoin.round;
|
||||
|
|
@ -388,14 +352,6 @@ class BoardPainter extends CustomPainter {
|
|||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
void _drawRealisticMatch(Canvas canvas, Offset p1, Offset p2, Color headColor, {bool isLastMove = false, double blinkValue = 0.0}) {
|
||||
int seed = (p1.dx * 1000 + p1.dy).toInt(); Random rand = Random(seed); Vector2 dir = Vector2(p2.dx - p1.dx, p2.dy - p1.dy).normalized(); double shrink = 8.0; Offset start = Offset(p1.dx + dir.x * shrink, p1.dy + dir.y * shrink); Offset end = Offset(p2.dx - dir.x * shrink, p2.dy - dir.y * shrink); start += Offset(rand.nextDouble() * 4 - 2, rand.nextDouble() * 4 - 2); end += Offset(rand.nextDouble() * 4 - 2, rand.nextDouble() * 4 - 2); bool headAtEnd = rand.nextBool(); Offset headPos = headAtEnd ? end : start; Offset tailPos = headAtEnd ? start : end; Vector2 matchDir = Vector2(headPos.dx - tailPos.dx, headPos.dy - tailPos.dy).normalized();
|
||||
canvas.drawLine(tailPos + const Offset(4, 4), headPos + const Offset(4, 4), Paint()..color = Colors.black.withOpacity(0.6)..strokeWidth = 7.0..strokeCap = StrokeCap.round);
|
||||
if (isLastMove) { canvas.drawCircle(headPos, 8.0 + (blinkValue * 6.0), Paint()..color = Colors.orangeAccent.withOpacity(0.6 * blinkValue)..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6.0)); }
|
||||
canvas.drawLine(tailPos, headPos, Paint()..color = const Color(0xFF6D4C41)..strokeWidth = 7.0..strokeCap = StrokeCap.round); canvas.drawLine(tailPos, headPos, Paint()..color = const Color(0xFFEDC498)..strokeWidth = 4.0..strokeCap = StrokeCap.round); Offset burnPos = Offset(headPos.dx - matchDir.x * 8, headPos.dy - matchDir.y * 8); canvas.drawLine(burnPos, headPos, Paint()..color = const Color(0xFF2E1A14)..strokeWidth = 6.0..strokeCap = StrokeCap.round);
|
||||
canvas.save(); canvas.translate(headPos.dx, headPos.dy); double angle = atan2(matchDir.y, matchDir.x); canvas.rotate(angle); Rect headOval = Rect.fromCenter(center: Offset.zero, width: 18.0, height: 13.0); canvas.drawOval(headOval.shift(const Offset(1, 2)), Paint()..color = Colors.black.withOpacity(0.6)); canvas.drawOval(headOval, Paint()..color = headColor); canvas.restore();
|
||||
}
|
||||
|
||||
void _drawNeonLine(Canvas canvas, Offset p1, Offset p2, Color color, bool isConquered, {bool isLastMove = false, double blinkValue = 0.0}) {
|
||||
double mainWidth = isConquered ? (isLastMove ? 6.0 + (blinkValue * 3.0) : 6.0) : 3.0; Color coreColor = isConquered ? (isLastMove ? Color.lerp(Colors.white, color, 1.0 - blinkValue)! : Colors.white.withOpacity(0.9)) : color.withOpacity(0.6);
|
||||
canvas.drawLine(p1, p2, Paint()..color = color.withOpacity(isConquered ? (isLastMove ? 0.4 + (0.4 * blinkValue) : 0.4) : 0.2)..strokeWidth = mainWidth * 4..strokeCap = StrokeCap.round..maskFilter = MaskFilter.blur(BlurStyle.normal, isConquered ? 12.0 : 6.0));
|
||||
|
|
|
|||
|
|
@ -236,8 +236,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
return Container(decoration: BoxDecoration(color: Colors.black.withOpacity(0.9), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.purpleAccent, width: 2), boxShadow: [BoxShadow(color: Colors.purpleAccent.withOpacity(0.6), blurRadius: 15, spreadRadius: 0)]), child: content);
|
||||
} else if (themeType == AppThemeType.doodle) {
|
||||
return Container(decoration: BoxDecoration(color: const Color(0xFFF9F9F9), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 3), boxShadow: const [BoxShadow(color: Colors.black26, offset: Offset(6, 6))]), child: content);
|
||||
} else if (themeType == AppThemeType.wood) {
|
||||
return Container(decoration: BoxDecoration(color: const Color(0xFF5D4037), borderRadius: BorderRadius.circular(15), border: Border.all(color: const Color(0xFF3E2723), width: 4), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.6), blurRadius: 15, offset: const Offset(0, 8))]), child: content);
|
||||
} else if (themeType == AppThemeType.arcade) {
|
||||
return Container(decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.zero, border: Border.all(color: Colors.greenAccent, width: 4)), child: content);
|
||||
} else if (themeType == AppThemeType.grimorio) {
|
||||
|
|
@ -290,7 +288,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
});
|
||||
|
||||
String? bgImage;
|
||||
if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg';
|
||||
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
|
||||
|
|
@ -601,7 +598,6 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
|
|||
List<Color> palette = [widget.winnerColor, widget.winnerColor.withOpacity(0.7), Colors.white];
|
||||
if (widget.themeType == AppThemeType.cyberpunk) { palette.add(Colors.cyanAccent); palette.add(Colors.yellowAccent); }
|
||||
else if (widget.themeType == AppThemeType.doodle) { palette.add(const Color(0xFF00008B)); palette.add(Colors.redAccent); }
|
||||
else if (widget.themeType == AppThemeType.wood) { palette = [Colors.orangeAccent, Colors.yellow, Colors.red, Colors.white]; }
|
||||
else if (widget.themeType == AppThemeType.arcade) { palette = [widget.winnerColor, Colors.white, Colors.greenAccent]; }
|
||||
else if (widget.themeType == AppThemeType.grimorio) { palette = [widget.winnerColor, Colors.deepPurpleAccent, Colors.white]; }
|
||||
else if (widget.themeType == AppThemeType.music) { palette.add(Colors.pinkAccent); palette.add(Colors.cyanAccent); }
|
||||
|
|
@ -618,7 +614,6 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
|
|||
for (var p in _particles) {
|
||||
p.x += p.vx; p.y += p.vy;
|
||||
if (widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.music) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; }
|
||||
else if (widget.themeType == AppThemeType.wood) { p.vy -= 0.2; p.x += math.sin(p.y * 0.05) * 2; }
|
||||
else if (widget.themeType == AppThemeType.arcade) { p.vy += 0.3; p.spin = 0; p.angle = 0; }
|
||||
else if (widget.themeType == AppThemeType.grimorio) { p.vy -= 0.1; p.x += math.sin(p.y * 0.02) * 1.5; p.size *= 0.995; }
|
||||
else { p.vy += 0.5; }
|
||||
|
|
@ -645,8 +640,6 @@ class _VFXPainter extends CustomPainter {
|
|||
if (themeType == AppThemeType.doodle) {
|
||||
paint.style = PaintingStyle.stroke; paint.strokeWidth = 2.0;
|
||||
if (p.type == 0) { canvas.drawCircle(Offset.zero, p.size, paint); } else { canvas.drawRect(Rect.fromCenter(center: Offset.zero, width: p.size*2, height: p.size*2), paint); }
|
||||
} else if (themeType == AppThemeType.wood) {
|
||||
paint.maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0); canvas.drawCircle(Offset.zero, p.size, paint);
|
||||
} else if (themeType == AppThemeType.arcade) {
|
||||
canvas.drawRect(Rect.fromCenter(center: Offset.zero, width: p.size * 1.5, height: p.size * 1.5), paint);
|
||||
} else if (themeType == AppThemeType.grimorio) {
|
||||
|
|
|
|||
|
|
@ -13,7 +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
|
||||
import '../../services/storage_service.dart';
|
||||
|
||||
// ===========================================================================
|
||||
// 1. DIALOGO MISSIONI (QUESTS)
|
||||
|
|
@ -159,17 +159,14 @@ class LeaderboardDialog extends StatelessWidget {
|
|||
return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5))));
|
||||
}
|
||||
|
||||
// 1. ESTRAIAMO TUTTI I DOCUMENTI
|
||||
final rawDocs = snapshot.data!.docs;
|
||||
|
||||
// 2. APPLICHIAMO IL FILTRO PER NASCONDERE "PAOLO"
|
||||
final filteredDocs = rawDocs.where((doc) {
|
||||
var data = doc.data() as Map<String, dynamic>;
|
||||
String name = (data['name'] ?? '').toString().toUpperCase();
|
||||
return name != 'PAOLO';
|
||||
}).toList();
|
||||
|
||||
// 3. SE DOPO IL FILTRO NON C'E' NESSUNO
|
||||
if (filteredDocs.isEmpty) {
|
||||
return Center(child: Text("Ancora nessun campione...", style: TextStyle(color: theme.text.withOpacity(0.5))));
|
||||
}
|
||||
|
|
@ -184,7 +181,6 @@ class LeaderboardDialog extends StatelessWidget {
|
|||
bool isMe = doc.id == myUid;
|
||||
String playerName = data['name'] ?? 'Unknown';
|
||||
|
||||
// Avvolto in StatefulBuilder per gestire la stella dei preferiti
|
||||
return StatefulBuilder(
|
||||
builder: (context, setStateItem) {
|
||||
bool isFav = StorageService.instance.isFavorite(doc.id);
|
||||
|
|
@ -209,7 +205,6 @@ class LeaderboardDialog extends StatelessWidget {
|
|||
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(
|
||||
|
|
@ -301,11 +296,6 @@ class TutorialDialog extends StatelessWidget {
|
|||
jokerLabel = "BOT:";
|
||||
swapLabel = "NETWORK:";
|
||||
blockLabel = "FIREWALL:";
|
||||
} else if (themeType == AppThemeType.wood) {
|
||||
goldLabel = "GEMMA:";
|
||||
bombLabel = "FUOCO:";
|
||||
jokerLabel = "CHIAVE:";
|
||||
blockLabel = "DIVIETO:";
|
||||
} else if (themeType == AppThemeType.doodle) {
|
||||
bombLabel = "VIRUS:";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,6 @@ import '../../widgets/home_buttons.dart';
|
|||
import '../../widgets/custom_settings_button.dart';
|
||||
import 'dialog.dart';
|
||||
|
||||
// ===========================================================================
|
||||
// CLASSE PRINCIPALE HOME
|
||||
// ===========================================================================
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
|
|
@ -69,11 +66,20 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkPlayerName();
|
||||
StorageService.instance.syncLeaderboard();
|
||||
_checkThemeSafety();
|
||||
});
|
||||
_checkClipboardForInvite();
|
||||
_initDeepLinks();
|
||||
}
|
||||
|
||||
void _checkThemeSafety() {
|
||||
String themeStr = StorageService.instance.getTheme();
|
||||
bool exists = AppThemeType.values.any((e) => e.toString() == themeStr);
|
||||
if (!exists) {
|
||||
context.read<ThemeManager>().setTheme(AppThemeType.doodle);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
|
|
@ -330,7 +336,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
// --- FINESTRA DI REGISTRAZIONE/LOGIN ---
|
||||
void _showNameDialog() {
|
||||
final TextEditingController nameController = TextEditingController(text: StorageService.instance.playerName);
|
||||
final TextEditingController passController = TextEditingController();
|
||||
|
|
@ -457,7 +462,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// MESSAGGIO DI ERRORE
|
||||
if (_errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
|
|
@ -538,7 +542,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// MESSAGGIO DI ERRORE
|
||||
if (_errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
|
|
@ -590,7 +593,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
|
||||
void _showMatchSetupDialog(bool isVsCPU) {
|
||||
int localRadius = 4; ArenaShape localShape = ArenaShape.classic; bool localTimeMode = true;
|
||||
bool isChaosUnlocked = StorageService.instance.playerLevel >= 10;
|
||||
bool isChaosUnlocked = StorageService.instance.playerLevel >= 7;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
showDialog(
|
||||
|
|
@ -741,14 +744,9 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// INTERFACCIA PRINCIPALE
|
||||
// ===========================================================================
|
||||
|
||||
BoxDecoration _glassBoxDecoration(ThemeColors theme, AppThemeType themeType) {
|
||||
return BoxDecoration(
|
||||
color: themeType == AppThemeType.doodle ? Colors.white : null,
|
||||
// Sfumatura bianca inserita come richiesto!
|
||||
gradient: themeType == AppThemeType.doodle ? null : LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
|
|
@ -772,13 +770,11 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
Color inkColor = const Color(0xFF111122);
|
||||
|
||||
return Padding(
|
||||
// Padding ridotto al minimo
|
||||
padding: const EdgeInsets.only(top: 5.0, left: 15.0, right: 15.0, bottom: 10.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// --- SINISTRA: RIQUADRO GIOCATORE ---
|
||||
GestureDetector(
|
||||
onTap: _showNameDialog,
|
||||
child: Container(
|
||||
|
|
@ -806,7 +802,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
),
|
||||
|
||||
// --- DESTRA: RIQUADRO STATISTICHE E AUDIO ---
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
decoration: _glassBoxDecoration(theme, themeType),
|
||||
|
|
@ -825,7 +820,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
Container(width: 1, height: 20, color: (themeType == AppThemeType.doodle ? inkColor : Colors.white).withOpacity(0.2)),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// --- ICONA VOLUME REINTEGRATA ---
|
||||
AnimatedBuilder(
|
||||
animation: AudioService.instance,
|
||||
builder: (context, child) {
|
||||
|
|
@ -868,7 +862,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
String? bgImage;
|
||||
if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg';
|
||||
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
|
||||
|
|
@ -885,10 +878,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
Widget uiContent = SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// 1. TOP BAR (Sempre visibile in alto)
|
||||
_buildTopBar(context, theme, themeType, playerName, playerLevel),
|
||||
|
||||
// 2. CONTENUTO SCORREVOLE (Logo + Bottoni) con altezze dinamiche!
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
|
|
@ -927,9 +918,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
color: themeType == AppThemeType.doodle ? inkColor : theme.text,
|
||||
letterSpacing: 10 * vScale,
|
||||
shadows: themeType == AppThemeType.doodle
|
||||
// Ombra chiara se il testo è scuro
|
||||
? [const Shadow(color: Colors.white, offset: Offset(2.5, 2.5), blurRadius: 2), const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 2)]
|
||||
// Ombra scura e visibile per tutti gli altri temi
|
||||
: [Shadow(color: Colors.black.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 8), Shadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20)]
|
||||
))
|
||||
),
|
||||
|
|
@ -939,7 +928,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
SizedBox(height: 40 * vScale),
|
||||
|
||||
// --- MENU IN BASE AL TEMA ---
|
||||
if (themeType == AppThemeType.music) ...[
|
||||
MusicCassetteCard(title: loc.onlineTitle, subtitle: loc.onlineSub, neonColor: Colors.blueAccent, angle: -0.04, leftIcon: FontAwesomeIcons.sliders, rightIcon: FontAwesomeIcons.globe, themeType: themeType, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }),
|
||||
SizedBox(height: 12 * vScale),
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import 'package:firebase_auth/firebase_auth.dart';
|
|||
import '../../logic/game_controller.dart';
|
||||
import '../../models/game_board.dart';
|
||||
import '../../core/theme_manager.dart';
|
||||
import '../../core/app_colors.dart'; // L'import mancante!
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../services/multiplayer_service.dart';
|
||||
import '../../services/storage_service.dart';
|
||||
import '../game/game_screen.dart';
|
||||
import '../../widgets/painters.dart';
|
||||
import 'lobby_widgets.dart'; // Importa i widget separati
|
||||
import '../../widgets/cyber_border.dart'; // <--- ECCO L'IMPORT MANCANTE!
|
||||
import 'lobby_widgets.dart';
|
||||
|
||||
class LobbyScreen extends StatefulWidget {
|
||||
final String? initialRoomCode;
|
||||
|
|
@ -251,7 +252,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
],
|
||||
);
|
||||
|
||||
if (themeType == AppThemeType.cyberpunk) {
|
||||
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) {
|
||||
dialogContent = AnimatedCyberBorder(child: dialogContent);
|
||||
} else {
|
||||
dialogContent = Container(
|
||||
|
|
@ -320,13 +321,14 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
final theme = themeManager.currentColors;
|
||||
|
||||
String? bgImage;
|
||||
if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg';
|
||||
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
|
||||
if (themeType == AppThemeType.arcade) bgImage = 'assets/images/arcade.jpg';
|
||||
if (themeType == AppThemeType.grimorio) bgImage = 'assets/images/grimorio.jpg';
|
||||
|
||||
bool isChaosUnlocked = true;
|
||||
bool isChaosUnlocked = StorageService.instance.playerLevel >= 7;
|
||||
|
||||
// --- PANNELLO IMPOSTAZIONI STANZA ---
|
||||
Widget hostPanel = Transform.rotate(
|
||||
angle: themeType == AppThemeType.doodle ? 0.01 : 0,
|
||||
child: Container(
|
||||
|
|
@ -407,7 +409,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
);
|
||||
|
||||
if (themeType == AppThemeType.cyberpunk) {
|
||||
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music) {
|
||||
hostPanel = AnimatedCyberBorder(child: hostPanel);
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +429,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
Expanded(
|
||||
child: Text("MULTIPLAYER", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))),
|
||||
),
|
||||
const SizedBox(width: 48), // Bilanciamento
|
||||
const SizedBox(width: 48),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@
|
|||
// ===========================================================================
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import '../../models/game_board.dart';
|
||||
import '../../core/theme_manager.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
|
||||
|
|
@ -84,7 +82,7 @@ class NeonShapeButton extends StatelessWidget {
|
|||
children: [
|
||||
Icon(isLocked ? Icons.lock : icon, color: isSelected ? Colors.white : doodleColor, size: 20),
|
||||
const SizedBox(height: 2),
|
||||
FittedBox(fit: BoxFit.scaleDown, child: Text(isLocked ? "Liv. 10" : label, style: getLobbyTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 9, fontWeight: FontWeight.w900, letterSpacing: 0.2)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, child: Text(isLocked ? "Liv. 7" : label, style: getLobbyTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 9, fontWeight: FontWeight.w900, letterSpacing: 0.2)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -127,7 +125,7 @@ class NeonShapeButton extends StatelessWidget {
|
|||
children: [
|
||||
Icon(isLocked ? Icons.lock : icon, color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), size: 20),
|
||||
const SizedBox(height: 4),
|
||||
FittedBox(fit: BoxFit.scaleDown, child: Text(isLocked ? "Liv. 10" : label, style: getLobbyTextStyle(themeType, TextStyle(color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), fontSize: 9, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, child: Text(isLocked ? "Liv. 7" : label, style: getLobbyTextStyle(themeType, TextStyle(color: isLocked ? Colors.grey.withOpacity(0.5) : (isSelected ? Colors.white : theme.text.withOpacity(0.6)), fontSize: 9, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -390,7 +388,7 @@ class NeonPrivacySwitch extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isPublic ? 'STANZA PUBBLICA' : 'STANZA PRIVATA', style: getLobbyTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 10, letterSpacing: 1.0)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isPublic ? 'In bacheca' : 'Invita con codice', style: getLobbyTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isPublic ? 'Tutti ti vedono' : 'Solo con Codice', style: getLobbyTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -558,59 +556,3 @@ class NeonActionButton extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedCyberBorder extends StatefulWidget {
|
||||
final Widget child;
|
||||
const AnimatedCyberBorder({super.key, required this.child});
|
||||
@override
|
||||
State<AnimatedCyberBorder> createState() => _AnimatedCyberBorderState();
|
||||
}
|
||||
|
||||
class _AnimatedCyberBorderState extends State<AnimatedCyberBorder> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
@override
|
||||
void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); }
|
||||
@override
|
||||
void dispose() { _controller.dispose(); super.dispose(); }
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<ThemeManager>().currentColors;
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: _CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 20, spreadRadius: 2)]),
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CyberBorderPainter extends CustomPainter {
|
||||
final double animationValue;
|
||||
final Color color1;
|
||||
final Color color2;
|
||||
|
||||
_CyberBorderPainter({required this.animationValue, required this.color1, required this.color2});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final rect = Offset.zero & size;
|
||||
final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(20));
|
||||
final Paint paint = Paint()
|
||||
..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.0
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 3);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue;
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
|||
import '../../core/theme_manager.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../services/storage_service.dart';
|
||||
import '../../widgets/painters.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
|
@ -20,72 +21,104 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final themeManager = context.watch<ThemeManager>();
|
||||
final theme = themeManager.currentColors;
|
||||
final themeType = themeManager.currentThemeType;
|
||||
|
||||
int playerLevel = StorageService.instance.playerLevel;
|
||||
|
||||
final double screenHeight = MediaQuery.of(context).size.height;
|
||||
final double vScale = (screenHeight / 920.0).clamp(0.7, 1.2);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: theme.background,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
title: Text("SELEZIONA TEMA", style: TextStyle(fontWeight: FontWeight.bold, color: theme.text)),
|
||||
toolbarHeight: 80 * vScale,
|
||||
title: Text(
|
||||
"SELEZIONA TEMA",
|
||||
style: getSharedTextStyle(themeType, TextStyle(
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
letterSpacing: 2.0,
|
||||
fontSize: 20 * vScale,
|
||||
shadows: [Shadow(color: Colors.black.withOpacity(0.8), blurRadius: 5, offset: const Offset(2, 2))]
|
||||
))
|
||||
),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: theme.text),
|
||||
iconTheme: IconThemeData(color: Colors.white, size: 28 * vScale),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
body: Stack(
|
||||
children: [
|
||||
_ThemeCard(
|
||||
title: "Quaderno (Doodle)",
|
||||
subtitle: "Sfondo a quadretti, tratto a penna",
|
||||
type: AppThemeType.doodle,
|
||||
previewColors: AppColors.doodle,
|
||||
requiredLevel: 1,
|
||||
currentLevel: playerLevel,
|
||||
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
|
||||
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: const AssetImage('assets/images/sfondo_temi.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.6), BlendMode.darken),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_ThemeCard(
|
||||
title: "Legno & Fiammiferi",
|
||||
subtitle: "Tavolo di legno, linee come fiammiferi",
|
||||
type: AppThemeType.wood,
|
||||
previewColors: AppColors.wood,
|
||||
requiredLevel: 3,
|
||||
currentLevel: playerLevel,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_ThemeCard(
|
||||
title: "Cyberpunk",
|
||||
subtitle: "Nero profondo, luci al neon",
|
||||
type: AppThemeType.cyberpunk,
|
||||
previewColors: AppColors.cyberpunk,
|
||||
requiredLevel: 7,
|
||||
currentLevel: playerLevel,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_ThemeCard(
|
||||
title: "8-Bit Arcade",
|
||||
subtitle: "Sale giochi, fosfori verdi e pixel",
|
||||
type: AppThemeType.arcade,
|
||||
previewColors: AppColors.arcade,
|
||||
requiredLevel: 10,
|
||||
currentLevel: playerLevel,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_ThemeCard(
|
||||
title: "Grimorio",
|
||||
subtitle: "Incantesimi antichi, rune magiche",
|
||||
type: AppThemeType.grimorio,
|
||||
previewColors: AppColors.grimorio,
|
||||
requiredLevel: 15,
|
||||
currentLevel: playerLevel,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_ThemeCard(
|
||||
title: "Musica",
|
||||
subtitle: "Vinili, cassette e vibrazioni sonore",
|
||||
type: AppThemeType.music,
|
||||
previewColors: AppColors.music,
|
||||
requiredLevel: 20, // Tema Esclusivo di Livello 20!
|
||||
currentLevel: playerLevel,
|
||||
|
||||
ListView(
|
||||
padding: EdgeInsets.only(top: 120 * vScale, left: 20 * vScale, right: 20 * vScale, bottom: 40 * vScale),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [
|
||||
_ThemeCard(
|
||||
title: "Quaderno",
|
||||
subtitle: "Sfondo a quadretti, tratto a penna",
|
||||
type: AppThemeType.doodle,
|
||||
previewColors: AppColors.doodle,
|
||||
requiredLevel: 1,
|
||||
currentLevel: playerLevel,
|
||||
vScale: vScale,
|
||||
),
|
||||
SizedBox(height: 25 * vScale),
|
||||
_ThemeCard(
|
||||
title: "Cyberpunk",
|
||||
subtitle: "Nero profondo, luci al neon",
|
||||
type: AppThemeType.cyberpunk,
|
||||
previewColors: AppColors.cyberpunk,
|
||||
requiredLevel: 3,
|
||||
currentLevel: playerLevel,
|
||||
vScale: vScale,
|
||||
),
|
||||
SizedBox(height: 25 * vScale),
|
||||
_ThemeCard(
|
||||
title: "8-Bit Arcade",
|
||||
subtitle: "Sale giochi, fosfori verdi e pixel",
|
||||
type: AppThemeType.arcade,
|
||||
previewColors: AppColors.arcade,
|
||||
requiredLevel: 7,
|
||||
currentLevel: playerLevel,
|
||||
vScale: vScale,
|
||||
),
|
||||
SizedBox(height: 25 * vScale),
|
||||
_ThemeCard(
|
||||
title: "Grimorio",
|
||||
subtitle: "Incantesimi antichi, rune magiche",
|
||||
type: AppThemeType.grimorio,
|
||||
previewColors: AppColors.grimorio,
|
||||
requiredLevel: 10,
|
||||
currentLevel: playerLevel,
|
||||
vScale: vScale,
|
||||
),
|
||||
SizedBox(height: 25 * vScale),
|
||||
_ThemeCard(
|
||||
title: "Musica",
|
||||
subtitle: "Vinili, cassette e vibrazioni sonore",
|
||||
type: AppThemeType.music,
|
||||
previewColors: AppColors.music,
|
||||
requiredLevel: 15,
|
||||
currentLevel: playerLevel,
|
||||
vScale: vScale,
|
||||
),
|
||||
SizedBox(height: 40 * vScale),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -100,6 +133,7 @@ class _ThemeCard extends StatelessWidget {
|
|||
final ThemeColors previewColors;
|
||||
final int requiredLevel;
|
||||
final int currentLevel;
|
||||
final double vScale;
|
||||
|
||||
const _ThemeCard({
|
||||
required this.title,
|
||||
|
|
@ -108,6 +142,7 @@ class _ThemeCard extends StatelessWidget {
|
|||
required this.previewColors,
|
||||
required this.requiredLevel,
|
||||
required this.currentLevel,
|
||||
required this.vScale,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -116,6 +151,33 @@ class _ThemeCard extends StatelessWidget {
|
|||
bool isSelected = themeManager.currentThemeType == type;
|
||||
bool isLocked = currentLevel < requiredLevel;
|
||||
|
||||
String? bgImage;
|
||||
if (type == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||
if (type == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||
if (type == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
|
||||
if (type == AppThemeType.arcade) bgImage = 'assets/images/arcade.jpg';
|
||||
if (type == AppThemeType.grimorio) bgImage = 'assets/images/grimorio.jpg';
|
||||
|
||||
Border border;
|
||||
// OMBRA MARCATA PER DARE SPESSORE AL TASTO (Offset 0, 10)
|
||||
List<BoxShadow> shadows = [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.8), offset: const Offset(0, 10), blurRadius: 15)
|
||||
];
|
||||
|
||||
if (type == AppThemeType.doodle) {
|
||||
border = Border.all(color: isSelected ? previewColors.playerBlue : const Color(0xFF111122).withOpacity(0.8), width: isSelected ? 4 : 2);
|
||||
if (isSelected) shadows.add(const BoxShadow(color: Color(0xFF111122), offset: Offset(4, 5)));
|
||||
} else if (type == AppThemeType.cyberpunk || type == AppThemeType.music) {
|
||||
border = Border.all(color: isSelected ? previewColors.playerBlue : previewColors.gridLine.withOpacity(0.8), width: isSelected ? 3 : 1.5);
|
||||
if (isSelected) shadows.add(BoxShadow(color: previewColors.playerBlue.withOpacity(0.8), blurRadius: 25, spreadRadius: 3));
|
||||
} else if (type == AppThemeType.arcade) {
|
||||
border = Border.all(color: isSelected ? previewColors.gridLine : Colors.white54, width: isSelected ? 4 : 2);
|
||||
if (isSelected) shadows.add(BoxShadow(color: previewColors.gridLine.withOpacity(0.5), offset: const Offset(4, 4)));
|
||||
} else {
|
||||
border = Border.all(color: isSelected ? Colors.amber : previewColors.gridLine.withOpacity(0.8), width: isSelected ? 3 : 1.5);
|
||||
if (isSelected) shadows.add(BoxShadow(color: Colors.amber.withOpacity(0.6), blurRadius: 20));
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (isLocked) {
|
||||
|
|
@ -130,63 +192,128 @@ class _ThemeCard extends StatelessWidget {
|
|||
);
|
||||
return;
|
||||
}
|
||||
|
||||
themeManager.setTheme(type);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
padding: const EdgeInsets.all(20),
|
||||
height: 140 * vScale, // Leggermente più alto per ospitare la cornice del testo
|
||||
padding: EdgeInsets.symmetric(horizontal: 20 * vScale, vertical: 15 * vScale),
|
||||
decoration: BoxDecoration(
|
||||
color: isLocked ? previewColors.background.withOpacity(0.4) : previewColors.background,
|
||||
color: isLocked ? Colors.black87 : previewColors.background,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? previewColors.playerBlue
|
||||
: (isLocked ? Colors.grey.withOpacity(0.3) : previewColors.gridLine.withOpacity(0.5)),
|
||||
width: isSelected ? 4 : 2,
|
||||
),
|
||||
boxShadow: isSelected ? [BoxShadow(color: previewColors.playerBlue.withOpacity(0.4), blurRadius: 10, spreadRadius: 2)] : [],
|
||||
border: border,
|
||||
boxShadow: shadows, // L'ombra per lo spessore viene applicata qui
|
||||
image: bgImage != null ? DecorationImage(
|
||||
image: AssetImage(bgImage!),
|
||||
fit: BoxFit.cover,
|
||||
// Scuriamo leggermente di più le card per far risaltare la targa chiara
|
||||
colorFilter: type == AppThemeType.doodle
|
||||
? ColorFilter.mode(Colors.white.withOpacity(isLocked ? 0.9 : 0.4), BlendMode.lighten)
|
||||
: ColorFilter.mode(Colors.black.withOpacity(isLocked ? 0.85 : 0.5), BlendMode.darken),
|
||||
) : null,
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: isLocked ? 0.25 : 1.0,
|
||||
opacity: isLocked ? 0.3 : 1.0,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: previewColors.text)),
|
||||
Text(subtitle, style: TextStyle(fontSize: 14, color: previewColors.text.withOpacity(0.7))),
|
||||
],
|
||||
// --- LA NUOVA CORNICE CHIARA PER IL TESTO ---
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 15 * vScale, vertical: 10 * vScale),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.9), // Sfondo chiaro semitrasparente
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 8, offset: const Offset(2, 4))
|
||||
]
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
title,
|
||||
style: getSharedTextStyle(
|
||||
type,
|
||||
TextStyle(
|
||||
fontSize: (type == AppThemeType.arcade ? 15 : 22) * vScale,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: const Color(0xFF111122), // Testo scuro per contrastare lo sfondo chiaro
|
||||
letterSpacing: type == AppThemeType.music ? 1.5 : 0.5,
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4 * vScale),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
subtitle,
|
||||
style: getSharedTextStyle(
|
||||
type,
|
||||
TextStyle(
|
||||
fontSize: (type == AppThemeType.arcade ? 8 : 12) * vScale,
|
||||
color: const Color(0xFF333344), // Grigio scuro per il sottotitolo
|
||||
fontWeight: FontWeight.bold,
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerRed, shape: BoxShape.circle, boxShadow: [BoxShadow(color: previewColors.playerRed.withOpacity(0.5), blurRadius: 4)])),
|
||||
const SizedBox(width: 10),
|
||||
Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerBlue, shape: BoxShape.circle, boxShadow: [BoxShadow(color: previewColors.playerBlue.withOpacity(0.5), blurRadius: 4)])),
|
||||
SizedBox(width: 15 * vScale),
|
||||
Container(
|
||||
width: 28 * vScale, height: 28 * vScale,
|
||||
decoration: BoxDecoration(
|
||||
color: previewColors.playerRed,
|
||||
shape: type == AppThemeType.arcade ? BoxShape.rectangle : BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2), // Bordino bianco per far risaltare il colore
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 5, offset: const Offset(1, 2))]
|
||||
)
|
||||
),
|
||||
SizedBox(width: 12 * vScale),
|
||||
Container(
|
||||
width: 28 * vScale, height: 28 * vScale,
|
||||
decoration: BoxDecoration(
|
||||
color: previewColors.playerBlue,
|
||||
shape: type == AppThemeType.arcade ? BoxShape.rectangle : BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2), // Bordino bianco per far risaltare il colore
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 5, offset: const Offset(1, 2))]
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isLocked)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: EdgeInsets.symmetric(horizontal: 16 * vScale, vertical: 10 * vScale),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.85),
|
||||
color: Colors.black.withOpacity(0.95),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.2), width: 1.5),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.5), blurRadius: 10, offset: const Offset(0, 4))],
|
||||
border: Border.all(color: previewColors.playerRed.withOpacity(0.8), width: 2),
|
||||
boxShadow: [BoxShadow(color: previewColors.playerRed.withOpacity(0.5), blurRadius: 20)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.lock_rounded, color: Colors.white, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Icon(Icons.lock_rounded, color: Colors.white, size: 20 * vScale),
|
||||
SizedBox(width: 8 * vScale),
|
||||
Text(
|
||||
"LIV. $requiredLevel",
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 16, letterSpacing: 2)
|
||||
style: getSharedTextStyle(type, TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 16 * vScale, letterSpacing: 2))
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
312
public/report.html
Normal file
312
public/report.html
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Report Statistiche TetraQ</title>
|
||||
|
||||
<script defer src="/__/firebase/10.8.0/firebase-app-compat.js"></script>
|
||||
<script defer src="/__/firebase/10.8.0/firebase-firestore-compat.js"></script>
|
||||
<script defer src="/__/firebase/init.js"></script>
|
||||
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f7f6; color: #333; padding: 20px; margin: 0; box-sizing: border-box;}
|
||||
|
||||
/* STILI LOGIN */
|
||||
#login-view { background-color: #34495e; position: fixed; top: 0; left: 0; width: 100%; height: 100vh; display: flex; justify-content: center; align-items: center; z-index: 1000; padding: 20px; box-sizing: border-box; }
|
||||
.login-box { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); text-align: center; width: 100%; max-width: 350px; box-sizing: border-box; }
|
||||
.login-box h2 { color: #2c3e50; margin-top: 0; }
|
||||
.login-box input[type="text"], .login-box input[type="password"] { width: 100%; padding: 12px; margin: 10px 0 20px 0; border: 1px solid #bdc3c7; border-radius: 5px; box-sizing: border-box; font-size: 16px; }
|
||||
.login-box button { width: 100%; background-color: #3498db; color: white; border: none; padding: 12px; font-size: 16px; border-radius: 5px; cursor: pointer; font-weight: bold; transition: 0.2s; }
|
||||
.login-box button:hover { background-color: #2980b9; }
|
||||
.error { color: #e74c3c; font-weight: bold; font-size: 14px; margin-top: -5px; margin-bottom: 15px; }
|
||||
|
||||
/* STILI DASHBOARD */
|
||||
#dashboard-view { display: none; }
|
||||
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); box-sizing: border-box;}
|
||||
.header-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
h1 { color: #2c3e50; margin: 0; font-size: 24px;}
|
||||
.btn-logout { background-color: #e74c3c; color: white; padding: 8px 15px; border-radius: 5px; text-decoration: none; font-weight: bold; font-size: 14px; white-space: nowrap; border: none; cursor: pointer;}
|
||||
.btn-logout:hover { background-color: #c0392b; }
|
||||
|
||||
.filter-section { background: #eef2f5; padding: 20px; border-radius: 8px; margin-bottom: 30px; display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-end; }
|
||||
.form-group { display: flex; flex-direction: column; flex: 1; min-width: 150px; }
|
||||
.form-group label { font-size: 13px; font-weight: bold; color: #34495e; margin-bottom: 5px; }
|
||||
.form-group input, .form-group select { padding: 10px; border: 1px solid #bdc3c7; border-radius: 5px; font-size: 14px; outline: none; box-sizing: border-box; width: 100%;}
|
||||
.form-group input:focus, .form-group select:focus { border-color: #3498db; }
|
||||
.btn-filtra { background-color: #3498db; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 14px; transition: 0.2s; height: 40px; }
|
||||
.btn-filtra:hover { background-color: #2980b9; }
|
||||
.btn-reset { background-color: #95a5a6; color: white; padding: 10px 15px; border: none; border-radius: 5px; cursor: pointer; text-decoration: none; font-size: 14px; height: 40px; line-height: 20px; box-sizing: border-box; display: inline-block; text-align: center;}
|
||||
|
||||
.dashboard { display: flex; justify-content: space-between; margin-bottom: 30px; gap: 20px; text-align: center; flex-wrap: wrap; }
|
||||
.card { flex: 1; min-width: 150px; background: #ecf0f1; padding: 20px; border-radius: 8px; border-left: 5px solid #3498db; box-sizing: border-box;}
|
||||
.card.ios { border-left-color: #e74c3c; }
|
||||
.card.android { border-left-color: #2ecc71; }
|
||||
.card h3 { margin: 0 0 10px 0; font-size: 14px; color: #7f8c8d; text-transform: uppercase; }
|
||||
.card p { margin: 0; font-size: 28px; font-weight: bold; color: #2c3e50; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 14px; table-layout: fixed; }
|
||||
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #ddd; word-wrap: break-word;}
|
||||
th { background-color: #34495e; color: white; }
|
||||
tr:hover { background-color: #f1f1f1; }
|
||||
|
||||
/* Larghezze specifiche per evitare colonne deformate */
|
||||
th:nth-child(1) { width: 15%; }
|
||||
th:nth-child(2) { width: 25%; }
|
||||
th:nth-child(3) { width: 15%; }
|
||||
th:nth-child(4) { width: 20%; }
|
||||
th:nth-child(5) { width: 25%; }
|
||||
|
||||
.badge { padding: 4px 8px; border-radius: 4px; color: white; font-weight: bold; font-size: 12px; }
|
||||
.badge-ios { background-color: #e74c3c; }
|
||||
.badge-android { background-color: #2ecc71; }
|
||||
.badge-desktop { background-color: #95a5a6; }
|
||||
.empty { text-align: center; padding: 30px; color: #7f8c8d; font-style: italic; background: #f9f9f9; border-radius: 5px;}
|
||||
|
||||
/* MOBILE */
|
||||
@media (max-width: 768px) {
|
||||
body { padding: 10px; }
|
||||
.container { padding: 15px; }
|
||||
.header-top { flex-direction: column; text-align: center; gap: 15px; }
|
||||
.filter-section { flex-direction: column; align-items: stretch; gap: 10px; padding: 15px;}
|
||||
.form-group { min-width: 100%; }
|
||||
.btn-filtra, .btn-reset { width: 100%; margin-top: 5px; height: auto; padding: 12px;}
|
||||
.dashboard { flex-direction: column; gap: 15px; }
|
||||
.card { min-width: 100%; }
|
||||
table, thead, tbody, th, td, tr { display: block; width: 100%; box-sizing: border-box; }
|
||||
thead { display: none; }
|
||||
tr { margin-bottom: 15px; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; background: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
||||
td { display: flex; flex-direction: column; text-align: left; border-bottom: 1px solid #eee; padding: 12px 15px; position: relative; width: 100%; }
|
||||
td::before { content: attr(data-label); font-weight: bold; margin-bottom: 5px; color: #34495e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
td:last-child { border-bottom: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="login-view">
|
||||
<div class="login-box">
|
||||
<h2>Area Riservata</h2>
|
||||
<div id="login-error" class="error"></div>
|
||||
<form id="login-form">
|
||||
<input type="text" id="username" name="username" placeholder="Nome Utente (io)" autocomplete="username" required>
|
||||
<input type="password" id="password" name="password" placeholder="Inserisci la password" autocomplete="current-password" required>
|
||||
<button type="submit">Accedi</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dashboard-view">
|
||||
<div class="container">
|
||||
<div class="header-top">
|
||||
<h1>📊 Report Statistiche TetraQ</h1>
|
||||
<button class="btn-logout" onclick="logout()">Esci 🚪</button>
|
||||
</div>
|
||||
|
||||
<form class="filter-section" id="filter-form">
|
||||
<div class="form-group">
|
||||
<label for="data_da">Data Da:</label>
|
||||
<input type="date" id="data_da">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="data_a">Data A:</label>
|
||||
<input type="date" id="data_a">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="os">Sistema Operativo:</label>
|
||||
<select id="os">
|
||||
<option value="">Tutti i sistemi</option>
|
||||
<option value="iOS">Apple iOS</option>
|
||||
<option value="Android">Google Android</option>
|
||||
<option value="Desktop">Desktop (Mac/Win)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="loc">Località (es. Roma):</label>
|
||||
<input type="text" id="loc" placeholder="Cerca città...">
|
||||
</div>
|
||||
<button type="submit" class="btn-filtra">🔍 Applica Filtri</button>
|
||||
<button type="button" class="btn-reset" onclick="resetFilters()">✖ Reset</button>
|
||||
</form>
|
||||
|
||||
<div class="dashboard">
|
||||
<div class="card">
|
||||
<h3>Giocatori Trovati</h3>
|
||||
<p id="totale-text">0</p>
|
||||
</div>
|
||||
<div class="card ios">
|
||||
<h3>Apple iOS</h3>
|
||||
<p id="ios-text">0</p>
|
||||
</div>
|
||||
<div class="card android">
|
||||
<h3>Google Android</h3>
|
||||
<p id="android-text">0</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Desktop / Altro</h3>
|
||||
<p id="desktop-text">0</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Dettaglio Giocatori</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ultimo Accesso</th>
|
||||
<th>Giocatore (Livello)</th>
|
||||
<th>Sistema</th>
|
||||
<th>Località (IP)</th>
|
||||
<th>Dispositivo Hardware</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="table-body">
|
||||
<tr><td colspan="5" class="empty">Caricamento dati dal database...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const PASSWORD_SEGRETA = "!!TetraQ!!"; // La tua password sicura
|
||||
const UTENTE_SEGRETO = "io"; // Il nome utente richiesto
|
||||
let allData = [];
|
||||
|
||||
// GESTIONE LOGIN
|
||||
document.getElementById('login-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const user = document.getElementById('username').value.trim().toLowerCase();
|
||||
const pwd = document.getElementById('password').value;
|
||||
|
||||
if (user === UTENTE_SEGRETO && pwd === PASSWORD_SEGRETA) {
|
||||
document.getElementById('login-view').style.display = 'none';
|
||||
document.getElementById('dashboard-view').style.display = 'block';
|
||||
loadFirebaseData();
|
||||
} else {
|
||||
document.getElementById('login-error').innerText = "Credenziali errate! Riprova.";
|
||||
}
|
||||
});
|
||||
|
||||
function logout() {
|
||||
document.getElementById('password').value = '';
|
||||
document.getElementById('login-error').innerText = '';
|
||||
document.getElementById('dashboard-view').style.display = 'none';
|
||||
document.getElementById('login-view').style.display = 'flex';
|
||||
document.getElementById('table-body').innerHTML = '<tr><td colspan="5" class="empty">Caricamento dati dal database...</td></tr>';
|
||||
allData = [];
|
||||
}
|
||||
|
||||
// CONNESSIONE A FIREBASE E RECUPERO DATI
|
||||
function loadFirebaseData() {
|
||||
const db = firebase.firestore();
|
||||
db.collection('leaderboard').orderBy('lastActive', 'desc').get().then((snapshot) => {
|
||||
allData = [];
|
||||
snapshot.forEach(doc => {
|
||||
let data = doc.data();
|
||||
if (data.lastActive) {
|
||||
data.dateObj = data.lastActive.toDate();
|
||||
data.dateStr = data.dateObj.toISOString().substring(0, 10);
|
||||
} else {
|
||||
data.dateStr = "2000-01-01";
|
||||
}
|
||||
allData.push(data);
|
||||
});
|
||||
applyFilters();
|
||||
}).catch(error => {
|
||||
console.error("Errore lettura database:", error);
|
||||
document.getElementById('table-body').innerHTML = '<tr><td colspan="5" class="empty" style="color:red;">Errore di connessione a Firebase.</td></tr>';
|
||||
});
|
||||
}
|
||||
|
||||
// GESTIONE FILTRI
|
||||
document.getElementById('filter-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
function resetFilters() {
|
||||
document.getElementById('data_da').value = '';
|
||||
document.getElementById('data_a').value = '';
|
||||
document.getElementById('os').value = '';
|
||||
document.getElementById('loc').value = '';
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
const fDa = document.getElementById('data_da').value;
|
||||
const fA = document.getElementById('data_a').value;
|
||||
const fOs = document.getElementById('os').value;
|
||||
const fLoc = document.getElementById('loc').value.toLowerCase();
|
||||
|
||||
let tot = 0, ios = 0, android = 0, desktop = 0;
|
||||
let html = '';
|
||||
|
||||
allData.forEach(row => {
|
||||
let mostra = true;
|
||||
|
||||
let platform = row.platform || 'Sconosciuta';
|
||||
let city = (row.city || '').toLowerCase();
|
||||
let ip = row.ip || 'N/D';
|
||||
let name = row.name || 'Sconosciuto';
|
||||
let level = row.level || 1;
|
||||
let device = row.deviceModel || 'N/D';
|
||||
let appVersion = row.appVersion || 'N/D';
|
||||
|
||||
// Formattazione data precisa e sicura
|
||||
let dateDisplay = row.dateObj ? row.dateObj.toLocaleString('it-IT', {
|
||||
year: 'numeric', month: 'short', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', hour12: false
|
||||
}) : 'N/D';
|
||||
|
||||
// Filtro Data Da
|
||||
if (fDa !== '' && row.dateStr < fDa) mostra = false;
|
||||
// Filtro Data A
|
||||
if (fA !== '' && row.dateStr > fA) mostra = false;
|
||||
// Filtro Sistema Operativo
|
||||
if (fOs !== '') {
|
||||
if (fOs === 'Desktop' && (platform === 'iOS' || platform === 'Android')) mostra = false;
|
||||
if (fOs !== 'Desktop' && platform !== fOs) mostra = false;
|
||||
}
|
||||
// Filtro Località
|
||||
if (fLoc !== '' && !city.includes(fLoc)) mostra = false;
|
||||
|
||||
if (mostra) {
|
||||
tot++;
|
||||
let badgeClass = 'badge-desktop';
|
||||
let platformDisplay = platform;
|
||||
|
||||
if (platform === 'iOS') { ios++; badgeClass = 'badge-ios'; }
|
||||
else if (platform === 'Android') { android++; badgeClass = 'badge-android'; }
|
||||
else { desktop++; platformDisplay = 'Desktop'; }
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td data-label="Ultimo Accesso">${dateDisplay}</td>
|
||||
<td data-label="Giocatore">
|
||||
<strong>${name}</strong> (Liv. ${level})<br>
|
||||
<small style="color:#7f8c8d;">App v. ${appVersion}</small>
|
||||
</td>
|
||||
<td data-label="Sistema"><span class="badge ${badgeClass}">${platformDisplay}</span></td>
|
||||
<td data-label="Località">
|
||||
${row.city || 'N/D'}<br>
|
||||
<small style="color:#7f8c8d;">IP: ${ip}</small>
|
||||
</td>
|
||||
<td data-label="Dispositivo Hardware" style="font-size: 12px; color: #7f8c8d;">${device}</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
if (tot === 0) {
|
||||
html = '<tr><td colspan="5" class="empty">Nessun giocatore corrisponde ai filtri selezionati.</td></tr>';
|
||||
}
|
||||
|
||||
// Aggiorna la vista
|
||||
document.getElementById('table-body').innerHTML = html;
|
||||
document.getElementById('totale-text').innerText = tot;
|
||||
document.getElementById('ios-text').innerText = ios;
|
||||
document.getElementById('android-text').innerText = android;
|
||||
document.getElementById('desktop-text').innerText = desktop;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
name: tetraq
|
||||
description: A new Flutter project.
|
||||
publish_to: 'none'
|
||||
version: 1.1.4+6
|
||||
version: 1.1.5+7
|
||||
environment:
|
||||
sdk: ^3.10.7
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue