Auto-sync: 20260315_160000

This commit is contained in:
Paolo 2026-03-15 16:00:01 +01:00
parent 14c01e984c
commit 8caecd401e
12 changed files with 336 additions and 216 deletions

View file

@ -1,2 +1,2 @@
index.html,1773344753424,0d5d4b835a7d632ad11d249230a15561286f2bfcd1da8305c6fb294d37e5da09
404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3
index.html,1773586765860,5737ce966fa8786becaf7f36a32992cf44102fb3a217c226c30576c993b33e63

View file

@ -9,7 +9,8 @@ import 'package:cloud_firestore/cloud_firestore.dart';
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'; // <-- NUOVO IMPORT
import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart'; // <-- NUOVO IMPORT
class StorageService {
static final StorageService instance = StorageService._internal();
@ -89,19 +90,39 @@ class StorageService {
// IDENTIFICA IL SISTEMA OPERATIVO E LA VERSIONE APP
String currentPlatform = "Sconosciuta";
String appVersion = "N/D";
String deviceModel = "Sconosciuto"; // <-- NUOVO: MODELLO HARDWARE
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}";
} catch(e) {
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
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
deviceModel = iosInfo.utsname.machine; // Es. iPhone13,2
} else if (Platform.isMacOS) {
MacOsDeviceInfo macInfo = await deviceInfo.macOsInfo;
deviceModel = macInfo.model; // Es. MacBookPro16,1
}
} catch(e) {
debugPrint("Errore lettura hardware: $e");
}
}
// AGGIORNA IL TEMPO DI UTILIZZO
@ -109,7 +130,7 @@ class StorageService {
int now = DateTime.now().millisecondsSinceEpoch;
int sessionSeconds = (now - _sessionStart) ~/ 1000;
await _prefs.setInt('totalPlaytime', (_prefs.getInt('totalPlaytime') ?? 0) + sessionSeconds);
_sessionStart = now; // resetta per la prossima misurazione
_sessionStart = now;
}
int totalPlaytime = _prefs.getInt('totalPlaytime') ?? 0;
@ -123,7 +144,8 @@ class StorageService {
'ip': lastIp,
'city': lastCity,
'playtime': totalPlaytime,
'appVersion': appVersion, // <-- NUOVO: Salva la versione
'appVersion': appVersion,
'deviceModel': deviceModel, // Salva il modello hardware
}, SetOptions(merge: true));
}
} catch(e) {

View file

@ -33,7 +33,6 @@ class AdminScreen extends StatelessWidget {
return Center(child: Text("Nessun giocatore trovato nel database.", style: TextStyle(color: theme.text)));
}
// Ripristinata la lista completa senza nascondere PAOLO
final docs = snapshot.data!.docs;
return ListView.builder(
@ -50,8 +49,9 @@ class AdminScreen extends StatelessWidget {
String platform = data['platform'] ?? 'Sconosciuta';
String ip = data['ip'] ?? 'N/D';
String city = data['city'] ?? 'N/D';
String appVersion = data['appVersion'] ?? 'N/D';
String deviceModel = data['deviceModel'] ?? 'N/D';
// --- CALCOLO TEMPO UTILIZZO (HH:MM) ---
int playtimeSec = data['playtime'] ?? 0;
int hours = playtimeSec ~/ 3600;
int minutes = (playtimeSec % 3600) ~/ 60;
@ -109,6 +109,8 @@ class AdminScreen extends StatelessWidget {
Text("📍 Città: $city", style: TextStyle(color: theme.text, fontSize: 16)),
const SizedBox(height: 10),
Text("📱 OS: $platform", style: TextStyle(color: theme.text, fontSize: 16)),
const SizedBox(height: 10),
Text("💻 Hardware: $deviceModel", style: TextStyle(color: theme.text, fontSize: 16)),
],
),
actions: [
@ -140,7 +142,6 @@ class AdminScreen extends StatelessWidget {
const SizedBox(width: 10),
Text("Vittorie: $wins", style: TextStyle(color: Colors.amber.shade700, fontWeight: FontWeight.bold, fontSize: 12)),
const Spacer(),
// --- TEMPO DI GIOCO ---
Icon(Icons.timer, color: theme.text.withOpacity(0.6), size: 16),
const SizedBox(width: 4),
Text(playtimeStr, style: TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 14)),
@ -150,22 +151,36 @@ class AdminScreen extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Divider(),
),
// QUI È DOVE AVVENIVA IL CRASH! Ora usiamo Expanded e FittedBox
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Registrato il:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10)),
Text(createdStr, style: TextStyle(color: theme.text, fontSize: 12, fontWeight: FontWeight.bold)),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FittedBox(fit: BoxFit.scaleDown, child: Text("Registrato il:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10))),
FittedBox(fit: BoxFit.scaleDown, child: Text(createdStr, style: TextStyle(color: theme.text, fontSize: 12, fontWeight: FontWeight.bold))),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text("Ultimo Accesso:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10)),
Text(lastActiveStr, style: TextStyle(color: Colors.green, fontSize: 12, fontWeight: FontWeight.bold)),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FittedBox(fit: BoxFit.scaleDown, child: Text("Versione App:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10))),
FittedBox(fit: BoxFit.scaleDown, child: Text("v. $appVersion", style: TextStyle(color: theme.playerBlue, fontSize: 12, fontWeight: FontWeight.bold))),
],
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FittedBox(fit: BoxFit.scaleDown, child: Text("Ultimo Accesso:", style: TextStyle(color: theme.text.withOpacity(0.5), fontSize: 10))),
FittedBox(fit: BoxFit.scaleDown, child: Text(lastActiveStr, style: TextStyle(color: Colors.green, fontSize: 12, fontWeight: FontWeight.bold))),
],
),
),
],
)

View file

@ -328,7 +328,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
Expanded(
child: Center(
child: Padding(
// PADDING RIDOTTO AL MINIMO: permette alla griglia di guadagnare pixel preziosi per allargarsi/alzarsi!
padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0),
child: LayoutBuilder(
builder: (context, constraints) {
@ -344,7 +343,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
width: actualWidth, height: actualHeight,
child: Stack(
children: [
// --- IL VERO SFONDO SFOCATO SAGOMATO (ORA PER TUTTI I TEMI) ---
Positioned.fill(
child: ClipPath(
clipper: _ArenaClipper(gameController.board),
@ -387,7 +385,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
),
Padding(
// PADDING RIDOTTO IN BASSO: 10 al posto di 20, per far esplodere la griglia verticalmente
padding: const EdgeInsets.only(bottom: 10.0, left: 20.0, right: 20.0, top: 5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -437,10 +434,8 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
backgroundColor: themeType == AppThemeType.doodle ? Colors.white : (bgImage != null ? Colors.transparent : theme.background),
body: Stack(
children: [
// 1. Sfondo base a tinta unita
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
// 2. Griglia a quadretti (Doodle Theme)
if (themeType == AppThemeType.doodle)
Positioned.fill(
child: CustomPaint(
@ -448,19 +443,21 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
),
),
// 3. Immagine di Sfondo
if (bgImage != null)
Positioned.fill(
child: Image.asset(
bgImage,
fit: BoxFit.cover,
alignment: Alignment.center,
color: themeType == AppThemeType.doodle ? Colors.white.withOpacity(0.5) : null,
colorBlendMode: themeType == AppThemeType.doodle ? BlendMode.lighten : null,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(bgImage!),
fit: BoxFit.cover,
colorFilter: themeType == AppThemeType.doodle
? ColorFilter.mode(Colors.white.withOpacity(0.5), BlendMode.lighten)
: null,
),
),
),
),
// 4. Patina scura (Cyberpunk, Music, Arcade e Grimorio)
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio))
Positioned.fill(
child: Container(
@ -473,18 +470,14 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
),
),
// 5. Effetto "Furia" o Timeout
if (gameController.isTimeMode && !gameController.isCPUThinking && !gameController.isGameOver && gameController.timeLeft > 0 && gameController.timeLeft <= 5 && !gameController.isSetupPhase)
Positioned.fill(child: BlitzBackgroundEffect(timeLeft: gameController.timeLeft, color: theme.playerRed, themeType: themeType)),
// 6. Testo degli Eventi
if (gameController.effectText.isNotEmpty)
Positioned.fill(child: SpecialEventBackgroundEffect(text: gameController.effectText, color: gameController.effectColor, themeType: themeType)),
// 7. Il Gioco Vero e Proprio
Positioned.fill(child: gameContent),
// 8. Schermata Passaggio Dispositivo
if (gameController.isSetupPhase && !_hideJokerMessage)
Positioned.fill(
child: Container(
@ -503,7 +496,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
),
),
// 9. Effetti di Vittoria
if (gameController.isGameOver && gameController.board.scoreRed != gameController.board.scoreBlue)
Positioned.fill(child: IgnorePointer(child: WinnerVFXOverlay(winnerColor: gameController.board.scoreRed > gameController.board.scoreBlue ? theme.playerRed : theme.playerBlue, themeType: themeType))),
],
@ -543,7 +535,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
}
// ===========================================================================
// CLIPPER MAGICO: Ritaglia l'effetto sfocatura sull'esatta forma dell'arena
// CLIPPER MAGICO E ALTRI WIDGETS
// ===========================================================================
class _ArenaClipper extends CustomClipper<Path> {
final GameBoard board;
@ -568,9 +560,7 @@ class _ArenaClipper extends CustomClipper<Path> {
}
return path;
}
@override
bool shouldReclip(covariant _ArenaClipper oldClipper) => true;
@override bool shouldReclip(covariant _ArenaClipper oldClipper) => true;
}
class _Particle {
@ -636,7 +626,6 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
}
});
}
@override void dispose() { _vfxController.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return CustomPaint(painter: _VFXPainter(particles: _particles, themeType: widget.themeType), child: Container()); }
}
@ -679,7 +668,6 @@ class _BouncingEmoji extends StatefulWidget {
final String emoji; const _BouncingEmoji({required this.emoji});
@override State<_BouncingEmoji> createState() => _BouncingEmojiState();
}
class _BouncingEmojiState extends State<_BouncingEmoji> with SingleTickerProviderStateMixin {
late AnimationController _ctrl; late Animation<double> _anim;
@override void initState() { super.initState(); _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 500))..repeat(reverse: true); _anim = Tween<double>(begin: -10, end: 10).animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut)); }

View file

@ -747,10 +747,19 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
BoxDecoration _glassBoxDecoration(ThemeColors theme, AppThemeType themeType) {
return BoxDecoration(
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
color: themeType == AppThemeType.doodle ? Colors.white : null,
// Sfumatura bianca inserita come richiesto!
gradient: themeType == AppThemeType.doodle ? null : LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white.withOpacity(0.25),
Colors.white.withOpacity(0.05),
],
),
borderRadius: BorderRadius.circular(25),
border: Border.all(
color: themeType == AppThemeType.doodle ? theme.text : Colors.white.withOpacity(0.2),
color: themeType == AppThemeType.doodle ? theme.text : Colors.white.withOpacity(0.3),
width: themeType == AppThemeType.doodle ? 2 : 1.5,
),
boxShadow: themeType == AppThemeType.doodle
@ -763,6 +772,7 @@ 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,
@ -815,6 +825,7 @@ 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,13 +879,16 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
if (playerName.isEmpty) playerName = "GUEST";
int playerLevel = StorageService.instance.playerLevel;
final double screenHeight = MediaQuery.of(context).size.height;
final double vScale = (screenHeight / 920.0).clamp(0.50, 1.0);
Widget uiContent = SafeArea(
child: Column(
children: [
// 1. TOP BAR (Sempre visibile in alto)
_buildTopBar(context, theme, themeType, playerName, playerLevel),
// 2. CONTENUTO SCORREVOLE (Logo + Bottoni)
// 2. CONTENUTO SCORREVOLE (Logo + Bottoni) con altezze dinamiche!
Expanded(
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
@ -883,22 +897,24 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 20),
SizedBox(height: 20 * vScale),
Center(
child: Transform.rotate(
angle: themeType == AppThemeType.doodle ? -0.04 : 0,
child: GestureDetector(
onTap: () {
_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: (_) => const AdminScreen()));
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: (_) => const AdminScreen()));
}
}
},
child: FittedBox(
@ -906,29 +922,31 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
child: Text(
loc.appTitle.toUpperCase(),
style: getSharedTextStyle(themeType, TextStyle(
fontSize: 65,
fontSize: 65 * vScale,
fontWeight: FontWeight.w900,
color: themeType == AppThemeType.doodle ? inkColor : theme.text,
letterSpacing: 10,
letterSpacing: 10 * vScale,
shadows: themeType == AppThemeType.doodle
? [const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 0), Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(2.5, 2.5), blurRadius: 1)]
: themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(3, 6), blurRadius: 8), BoxShadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20)]
// 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)]
))
),
),
),
),
),
const SizedBox(height: 40),
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())); }),
const SizedBox(height: 12),
SizedBox(height: 12 * vScale),
MusicCassetteCard(title: loc.cpuTitle, subtitle: loc.cpuSub, neonColor: Colors.purpleAccent, angle: 0.03, leftIcon: FontAwesomeIcons.desktop, rightIcon: FontAwesomeIcons.music, themeType: themeType, onTap: () => _showMatchSetupDialog(true)),
const SizedBox(height: 12),
SizedBox(height: 12 * vScale),
MusicCassetteCard(title: loc.localTitle, subtitle: loc.localSub, neonColor: Colors.deepPurpleAccent, angle: -0.02, leftIcon: FontAwesomeIcons.headphones, rightIcon: FontAwesomeIcons.headphones, themeType: themeType, onTap: () => _showMatchSetupDialog(false)),
const SizedBox(height: 30),
SizedBox(height: 30 * vScale),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -943,11 +961,11 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildCyberCard(FeatureCard(title: loc.onlineTitle, subtitle: loc.onlineSub, icon: Icons.public, color: Colors.lightBlue.shade200, theme: theme, themeType: themeType, isFeatured: true, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), themeType),
const SizedBox(height: 12),
SizedBox(height: 12 * vScale),
_buildCyberCard(FeatureCard(title: loc.cpuTitle, subtitle: loc.cpuSub, icon: Icons.smart_toy, color: Colors.purple.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), themeType),
const SizedBox(height: 12),
SizedBox(height: 12 * vScale),
_buildCyberCard(FeatureCard(title: loc.localTitle, subtitle: loc.localSub, icon: Icons.people_alt, color: Colors.red.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), themeType),
const SizedBox(height: 12),
SizedBox(height: 12 * vScale),
Row(
children: [
@ -957,7 +975,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
],
),
const SizedBox(height: 12),
SizedBox(height: 12 * vScale),
Row(
children: [
@ -969,7 +987,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
],
),
],
const SizedBox(height: 40), // Margine in fondo per assicurare che ci sia respiro
SizedBox(height: 40 * vScale),
],
),
),
@ -981,31 +999,30 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
return Scaffold(
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
extendBodyBehindAppBar: true, // Rimosso l'AppBar vuoto che "spingeva" giù il SafeArea!
extendBodyBehindAppBar: true,
body: Stack(
children: [
// 1. Sfondo base a tinta unita
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
// 2. Immagine di Sfondo per tutti i temi che la supportano
if (bgImage != null)
Positioned.fill(
child: Image.asset(
bgImage,
fit: BoxFit.cover,
alignment: Alignment.center,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(bgImage!),
fit: BoxFit.cover,
colorFilter: themeType == AppThemeType.doodle
? ColorFilter.mode(Colors.white.withOpacity(0.5), BlendMode.lighten)
: null,
),
),
),
),
// 3. Griglia a righe incrociate per il doodle
if (themeType == AppThemeType.doodle)
Positioned.fill(
child: CustomPaint(
painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)),
),
),
// 4. Patina scura (Cyberpunk, Music, Arcade e Grimorio)
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio))
Positioned.fill(
child: Container(
@ -1017,8 +1034,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
),
),
),
// 5. Cavi musicali (Tema Musica)
if (themeType == AppThemeType.music)
Positioned.fill(
child: IgnorePointer(
@ -1027,8 +1042,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
),
),
),
// 6. UI
Positioned.fill(child: uiContent),
],
),

View file

@ -631,31 +631,50 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
body: Stack(
children: [
Container(color: themeType == AppThemeType.doodle ? Colors.white : theme.background),
if (bgImage != null)
Positioned.fill(
child: Image.asset(
bgImage,
fit: BoxFit.cover,
alignment: Alignment.center,
),
),
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio))
Positioned.fill(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter, end: Alignment.bottomCenter,
colors: [Colors.black.withOpacity(0.6), Colors.black.withOpacity(0.9)]
)
),
),
),
if (themeType == AppThemeType.doodle)
Positioned.fill(
child: CustomPaint(
painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)),
),
),
if (bgImage != null)
Positioned.fill(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(bgImage!),
fit: BoxFit.cover,
colorFilter: themeType == AppThemeType.doodle
? ColorFilter.mode(Colors.white.withOpacity(0.5), BlendMode.lighten)
: null,
),
),
),
),
if (bgImage != null && (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.music || themeType == AppThemeType.arcade || themeType == AppThemeType.grimorio))
Positioned.fill(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter, end: Alignment.bottomCenter,
colors: [Colors.black.withOpacity(0.4), Colors.black.withOpacity(0.8)]
)
),
),
),
if (themeType == AppThemeType.music)
Positioned.fill(
child: IgnorePointer(
child: CustomPaint(
painter: AudioCablesPainter(),
),
),
),
Positioned.fill(child: uiContent),
],
),

View file

@ -16,53 +16,101 @@ class MusicCassetteCard extends StatelessWidget {
final VoidCallback onTap;
final AppThemeType themeType;
const MusicCassetteCard({super.key, required this.title, required this.subtitle, required this.neonColor, required this.angle, required this.leftIcon, required this.rightIcon, required this.onTap, required this.themeType});
const MusicCassetteCard({
super.key,
required this.title,
required this.subtitle,
required this.neonColor,
required this.angle,
required this.leftIcon,
required this.rightIcon,
required this.onTap,
required this.themeType
});
@override
Widget build(BuildContext context) {
// Calcoliamo la scala in base all'altezza dello schermo per strizzare la cassetta
final double screenHeight = MediaQuery.of(context).size.height;
final double vScale = (screenHeight / 850.0).clamp(0.65, 1.0);
return Transform.rotate(
angle: angle,
child: GestureDetector(
onTap: onTap,
child: Container(
// Aumentato leggermente l'altezza a 125 per evitare l'overflow
height: 125, margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), padding: const EdgeInsets.all(12),
height: 125 * vScale, // Altezza dinamica!
margin: EdgeInsets.symmetric(vertical: 8 * vScale, horizontal: 10),
padding: EdgeInsets.all(12 * vScale),
decoration: BoxDecoration(
color: const Color(0xFF22222A), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.black87, width: 2),
boxShadow: [ BoxShadow(color: neonColor.withOpacity(0.5), blurRadius: 25, spreadRadius: 2), const BoxShadow(color: Colors.black54, offset: Offset(5, 10), blurRadius: 15) ]
color: const Color(0xFF22222A),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.black87, width: 2),
boxShadow: [
BoxShadow(color: neonColor.withOpacity(0.5), blurRadius: 25, spreadRadius: 2),
const BoxShadow(color: Colors.black54, offset: Offset(5, 10), blurRadius: 15)
]
),
child: Column(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(color: neonColor.withOpacity(0.15), borderRadius: BorderRadius.circular(4), border: Border.all(color: neonColor.withOpacity(0.5), width: 1.5)),
decoration: BoxDecoration(
color: neonColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: neonColor.withOpacity(0.5), width: 1.5)
),
child: Row(
children: [
Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(leftIcon, color: neonColor, size: 28)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12 * vScale),
child: Icon(leftIcon, color: neonColor, size: 28 * vScale)
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
// Aggiunto minAxisSize per far stare il contenuto stretto
mainAxisSize: MainAxisSize.min,
children: [
Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w900, shadows: [Shadow(color: neonColor, blurRadius: 10)]))))),
Flexible(child: FittedBox(fit: BoxFit.scaleDown, child: Text(subtitle, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold))))),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white, fontSize: 20 * vScale, fontWeight: FontWeight.w900, shadows: [Shadow(color: neonColor, blurRadius: 10)])))
)
),
Flexible(
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(subtitle, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white70, fontSize: 11 * vScale, fontWeight: FontWeight.bold)))
)
),
],
),
),
Padding(padding: const EdgeInsets.symmetric(horizontal: 12), child: Icon(rightIcon, color: neonColor, size: 28)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12 * vScale),
child: Icon(rightIcon, color: neonColor, size: 28 * vScale)
),
],
),
),
),
const SizedBox(height: 10),
SizedBox(height: 10 * vScale),
Container(
height: 35, width: 180, decoration: BoxDecoration(color: const Color(0xFF0D0D12), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white24, width: 1)),
height: 35 * vScale,
width: 180 * vScale,
decoration: BoxDecoration(
color: const Color(0xFF0D0D12),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.white24, width: 1)
),
child: Stack(
alignment: Alignment.center,
children: [
Container(height: 2, width: 120, color: const Color(0xFF333333)),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _buildSpool(), _buildSpool() ]),
Container(height: 2, width: 120 * vScale, color: const Color(0xFF333333)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ _buildSpool(vScale), _buildSpool(vScale) ]
),
],
),
),
@ -73,10 +121,22 @@ class MusicCassetteCard extends StatelessWidget {
);
}
Widget _buildSpool() {
Widget _buildSpool(double vScale) {
return Container(
width: 26, height: 26, decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white70, border: Border.all(color: Colors.black87, width: 5)),
child: Center(child: Container(width: 6, height: 6, decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.black))),
width: 26 * vScale,
height: 26 * vScale,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white70,
border: Border.all(color: Colors.black87, width: 5 * vScale)
),
child: Center(
child: Container(
width: 6 * vScale,
height: 6 * vScale,
decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.black)
)
),
);
}
}
@ -88,40 +148,61 @@ class MusicKnobCard extends StatelessWidget {
final AppThemeType themeType;
final Color? iconColor;
const MusicKnobCard({super.key, required this.title, required this.icon, required this.onTap, required this.themeType, this.iconColor});
const MusicKnobCard({
super.key,
required this.title,
required this.icon,
required this.onTap,
required this.themeType,
this.iconColor
});
@override
Widget build(BuildContext context) {
// Adattiamo anche le manopole in base all'altezza dello schermo
final double screenHeight = MediaQuery.of(context).size.height;
final double vScale = (screenHeight / 850.0).clamp(0.65, 1.0);
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 65, height: 65,
width: 65 * vScale,
height: 65 * vScale,
decoration: BoxDecoration(
shape: BoxShape.circle, color: const Color(0xFF222222), border: Border.all(color: const Color(0xFF111111), width: 2),
boxShadow: const [BoxShadow(color: Colors.black87, blurRadius: 10, offset: Offset(2, 6)), BoxShadow(color: Colors.white12, blurRadius: 2, offset: Offset(-1, -1))],
shape: BoxShape.circle,
color: const Color(0xFF222222),
border: Border.all(color: const Color(0xFF111111), width: 2),
boxShadow: const [
BoxShadow(color: Colors.black87, blurRadius: 10, offset: Offset(2, 6)),
BoxShadow(color: Colors.white12, blurRadius: 2, offset: Offset(-1, -1))
],
),
child: Padding(
padding: const EdgeInsets.all(6.0),
padding: EdgeInsets.all(6.0 * vScale),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle, border: Border.all(color: Colors.black54, width: 1),
shape: BoxShape.circle,
border: Border.all(color: Colors.black54, width: 1),
gradient: const SweepGradient(colors: [Color(0xFF555555), Color(0xFFAAAAAA), Color(0xFF555555), Color(0xFF222222), Color(0xFF555555)]),
),
child: Padding(
padding: const EdgeInsets.all(4.0),
padding: EdgeInsets.all(4.0 * vScale),
child: Container(
decoration: const BoxDecoration(shape: BoxShape.circle, color: Color(0xFF1A1A1A)),
child: Center(child: Icon(icon, color: iconColor ?? Colors.white70, size: 20)),
child: Center(child: Icon(icon, color: iconColor ?? Colors.white70, size: 20 * vScale)),
),
),
),
),
),
const SizedBox(height: 10),
FittedBox(fit: BoxFit.scaleDown, child: Text(title, style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white70, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.0)))),
SizedBox(height: 10 * vScale),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(title, style: getSharedTextStyle(themeType, TextStyle(color: Colors.white70, fontSize: 11 * vScale, fontWeight: FontWeight.bold, letterSpacing: 1.0)))
),
],
),
);

View file

@ -8,6 +8,7 @@ import Foundation
import app_links
import audioplayers_darwin
import cloud_firestore
import device_info_plus
import firebase_app_check
import firebase_auth
import firebase_core
@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))

View file

@ -1207,6 +1207,8 @@ PODS:
- Firebase/Firestore (~> 12.9.0)
- firebase_core
- FlutterMacOS
- device_info_plus (0.0.1):
- FlutterMacOS
- Firebase/AppCheck (12.9.0):
- Firebase/CoreOnly
- FirebaseAppCheck (~> 12.9.0)
@ -1414,6 +1416,7 @@ DEPENDENCIES:
- app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`)
- audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`)
- cloud_firestore (from `Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos`)
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
- firebase_app_check (from `Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos`)
- firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
@ -1453,6 +1456,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos
cloud_firestore:
:path: Flutter/ephemeral/.symlinks/plugins/cloud_firestore/macos
device_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
firebase_app_check:
:path: Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos
firebase_auth:
@ -1475,6 +1480,7 @@ SPEC CHECKSUMS:
audioplayers_darwin: 761f2948df701d05b5db603220c384fb55720012
BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508
cloud_firestore: a2a9382e6cc4dd07345748b904b3b194ea46be44
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
Firebase: 065f2bb395062046623036d8e6dc857bc2521d56
firebase_app_check: 1ea404b52b0910bf632b1ea2e00ceb8d1730cb44
firebase_auth: 8db6796451d9aa44d4cc49b3e757865c65ce170f

View file

@ -1,89 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to Firebase Hosting</title>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- update the version number as needed -->
<script defer src="/__/firebase/12.10.0/firebase-app-compat.js"></script>
<!-- include only the Firebase features as you need -->
<script defer src="/__/firebase/12.10.0/firebase-auth-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-database-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-firestore-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-functions-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-messaging-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-storage-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-analytics-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-remote-config-compat.js"></script>
<script defer src="/__/firebase/12.10.0/firebase-performance-compat.js"></script>
<!--
initialize the SDK after all desired features are loaded, set useEmulator to false
to avoid connecting the SDK to running emulators.
-->
<script defer src="/__/firebase/init.js?useEmulator=true"></script>
<title>Gioca a TetraQ!</title>
<meta property="og:title" content="Gioca a TetraQ!">
<meta property="og:description" content="Sfida i tuoi amici nell'arena al neon. Unisciti alla partita!">
<style media="screen">
body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
#message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
#message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
#message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
#message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
#message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
#message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
#load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
@media (max-width: 600px) {
body, #message { margin-top: 0; background: white; box-shadow: none; }
body { border-top: 16px solid #ffa100; }
}
</style>
</head>
<body>
<div id="message">
<h2>Welcome</h2>
<h1>Firebase Hosting Setup Complete</h1>
<p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>
<a target="_blank" href="https://firebase.google.com/docs/hosting/">Open Hosting Documentation</a>
</div>
<p id="load">Firebase SDK Loading&hellip;</p>
<meta property="og:image" content="https://upload.wikimedia.org/wikipedia/commons/c/ca/1x1.png">
<script>
document.addEventListener('DOMContentLoaded', function() {
const loadEl = document.querySelector('#load');
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
// // The Firebase SDK is initialized and available here!
//
// firebase.auth().onAuthStateChanged(user => { });
// firebase.database().ref('/path/to/ref').on('value', snapshot => { });
// firebase.firestore().doc('/foo/bar').get().then(() => { });
// firebase.functions().httpsCallable('yourFunction')().then(() => { });
// firebase.messaging().requestPermission().then(() => { });
// firebase.storage().ref('/path/to/ref').getDownloadURL().then(() => { });
// firebase.analytics(); // call to activate
// firebase.analytics().logEvent('tutorial_completed');
// firebase.performance(); // call to activate
//
// // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
<script>
function redirect() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
try {
let app = firebase.app();
let features = [
'auth',
'database',
'firestore',
'functions',
'messaging',
'storage',
'analytics',
'remoteConfig',
'performance',
].filter(feature => typeof app[feature] === 'function');
loadEl.textContent = `Firebase SDK loaded with ${features.join(', ')}`;
} catch (e) {
console.error(e);
loadEl.textContent = 'Error loading the Firebase SDK, check the console.';
// Se è iOS
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
window.location.href = "https://apps.apple.com/it/app/tetraq/id6759522394";
return;
}
});
</script>
</body>
// Se è Android
if (/android/i.test(userAgent)) {
window.location.href = "https://play.google.com/store/apps/details?id=com.amastra.tetraq";
return;
}
// Se è da PC (o non riconosciuto), lo mandiamo alla tua pagina Google Sites
window.location.href = "https://sites.google.com/view/tetraq/home-page";
}
</script>
</head>
<body onload="redirect()" style="background-color: #0A001A; color: white;">
<h3 style="text-align: center; font-family: sans-serif; margin-top: 50px;">
Apertura in corso... 🚀
</h3>
</body>
</html>

View file

@ -233,6 +233,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.8"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c"
url: "https://pub.dev"
source: hosted
version: "12.3.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
url: "https://pub.dev"
source: hosted
version: "7.0.3"
fake_async:
dependency: transitive
description:
@ -890,6 +906,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.15.0"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
xdg_directories:
dependency: transitive
description:

View file

@ -26,6 +26,7 @@ dependencies:
font_awesome_flutter: ^10.12.0
firebase_app_check: ^0.4.1+5
package_info_plus: ^9.0.0
device_info_plus: ^12.3.0
dev_dependencies:
flutter_test: