diff --git a/.DS_Store b/.DS_Store index 34014c8..95045bb 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.firebase/hosting.cHVibGlj.cache b/.firebase/hosting.cHVibGlj.cache index 448d542..78bb9a7 100644 --- a/.firebase/hosting.cHVibGlj.cache +++ b/.firebase/hosting.cHVibGlj.cache @@ -1,2 +1,3 @@ -404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3 index.html,1773586765860,5737ce966fa8786becaf7f36a32992cf44102fb3a217c226c30576c993b33e63 +404.html,1773344753356,05cbc6f94d7a69ce2e29646eab13be2c884e61ba93e3094df5028866876d18b3 +report.html,1773588057140,876c6baaa912c9abfb81ee70e9868d84476b1c204ebca4c99f458f300661a36b diff --git a/assets/.DS_Store b/assets/.DS_Store index cb7b5f2..99fc009 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/audio/.DS_Store b/assets/audio/.DS_Store index a9d9af3..f690802 100644 Binary files a/assets/audio/.DS_Store and b/assets/audio/.DS_Store differ diff --git a/assets/images/doodle_bg1.jpg b/assets/images/doodle_bg_riserva.jpg similarity index 100% rename from assets/images/doodle_bg1.jpg rename to assets/images/doodle_bg_riserva.jpg diff --git a/assets/images/sfondo_temi.jpg b/assets/images/sfondo_temi.jpg new file mode 100644 index 0000000..97776d0 Binary files /dev/null and b/assets/images/sfondo_temi.jpg differ diff --git a/assets/images/wood_bg.jpg b/assets/images/wood_bg.jpg deleted file mode 100644 index 704bb51..0000000 Binary files a/assets/images/wood_bg.jpg and /dev/null differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e08d7c7..3067147 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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 diff --git a/lib/core/app_colors.dart b/lib/core/app_colors.dart index 4ca3f6c..057bdd8 100644 --- a/lib/core/app_colors.dart +++ b/lib/core/app_colors.dart @@ -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; diff --git a/lib/core/theme_manager.dart b/lib/core/theme_manager.dart index 3a2b092..2ab1c56 100644 --- a/lib/core/theme_manager.dart +++ b/lib/core/theme_manager.dart @@ -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 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 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, + )); + } } \ No newline at end of file diff --git a/lib/logic/game_controller.dart b/lib/logic/game_controller.dart index 201c745..fd5d8b9 100644 --- a/lib/logic/game_controller.dart +++ b/lib/logic/game_controller.dart @@ -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 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 _getUnlocks(int oldLevel, int newLevel) { List 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; } diff --git a/lib/services/audio_service.dart b/lib/services/audio_service.dart index 6ce88b6..db890e2 100644 --- a/lib/services/audio_service.dart +++ b/lib/services/audio_service.dart @@ -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: diff --git a/lib/services/multiplayer_service.dart b/lib/services/multiplayer_service.dart index c69a93b..9e3a307 100644 --- a/lib/services/multiplayer_service.dart +++ b/lib/services/multiplayer_service.dart @@ -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); } diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 4c72d07..e7f4284 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -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 _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 saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index); + // --- METODI TEMA AGGIORNATI A STRINGHE --- + String getTheme() => _prefs.getString('theme') ?? AppThemeType.doodle.toString(); + Future saveTheme(String themeStr) async => await _prefs.setString('theme', themeStr); int get savedRadius => _prefs.getInt('radius') ?? 2; Future 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> get favorites { List favs = _prefs.getStringList('favorites') ?? []; return favs.map((e) => Map.from(jsonDecode(e))).toList(); diff --git a/lib/ui/game/board_painter.dart b/lib/ui/game/board_painter.dart index ac944ff..163df9a 100644 --- a/lib/ui/game/board_painter.dart +++ b/lib/ui/game/board_painter.dart @@ -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)); diff --git a/lib/ui/game/game_screen.dart b/lib/ui/game/game_screen.dart index a8ad080..a1b1958 100644 --- a/lib/ui/game/game_screen.dart +++ b/lib/ui/game/game_screen.dart @@ -236,8 +236,6 @@ class _GameScreenState extends State 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 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 with SingleTickerPr List 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 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) { diff --git a/lib/ui/home/dialog.dart b/lib/ui/home/dialog.dart index 68fa7f0..94583e7 100644 --- a/lib/ui/home/dialog.dart +++ b/lib/ui/home/dialog.dart @@ -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 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:"; } diff --git a/lib/ui/home/home_screen.dart b/lib/ui/home/home_screen.dart index 2fa6d2d..28c6edf 100644 --- a/lib/ui/home/home_screen.dart +++ b/lib/ui/home/home_screen.dart @@ -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 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().setTheme(AppThemeType.doodle); + } + } + @override void dispose() { WidgetsBinding.instance.removeObserver(this); @@ -330,7 +336,6 @@ class _HomeScreenState extends State 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 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 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 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 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 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 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 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 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 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 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 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), diff --git a/lib/ui/multiplayer/lobby_screen.dart b/lib/ui/multiplayer/lobby_screen.dart index 97cb467..524d6b0 100644 --- a/lib/ui/multiplayer/lobby_screen.dart +++ b/lib/ui/multiplayer/lobby_screen.dart @@ -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 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 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 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 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), diff --git a/lib/ui/multiplayer/lobby_widgets.dart b/lib/ui/multiplayer/lobby_widgets.dart index 102d217..e6337c8 100644 --- a/lib/ui/multiplayer/lobby_widgets.dart +++ b/lib/ui/multiplayer/lobby_widgets.dart @@ -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)))), ], ), ), @@ -557,60 +555,4 @@ class NeonActionButton extends StatelessWidget { ), ); } -} - -class AnimatedCyberBorder extends StatefulWidget { - final Widget child; - const AnimatedCyberBorder({super.key, required this.child}); - @override - State createState() => _AnimatedCyberBorderState(); -} - -class _AnimatedCyberBorderState extends State 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().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; } \ No newline at end of file diff --git a/lib/ui/settings/settings_screen.dart b/lib/ui/settings/settings_screen.dart index d0f007d..e20f6f0 100644 --- a/lib/ui/settings/settings_screen.dart +++ b/lib/ui/settings/settings_screen.dart @@ -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 { Widget build(BuildContext context) { final themeManager = context.watch(); 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 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)) ), ], ), diff --git a/public/report.html b/public/report.html new file mode 100644 index 0000000..98f1ddb --- /dev/null +++ b/public/report.html @@ -0,0 +1,312 @@ + + + + + + Report Statistiche TetraQ + + + + + + + + + +
+ +
+ +
+
+
+

📊 Report Statistiche TetraQ

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ +
+
+

Giocatori Trovati

+

0

+
+
+

Apple iOS

+

0

+
+
+

Google Android

+

0

+
+
+

Desktop / Altro

+

0

+
+
+ +

Dettaglio Giocatori

+ + + + + + + + + + + + + +
Ultimo AccessoGiocatore (Livello)SistemaLocalità (IP)Dispositivo Hardware
Caricamento dati dal database...
+
+
+ + + + \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 4d4b213..54d6790 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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