Auto-sync: 20260313_220000
This commit is contained in:
parent
da52ad9d80
commit
518cb22ec4
9 changed files with 206 additions and 186 deletions
BIN
ios/.DS_Store
vendored
BIN
ios/.DS_Store
vendored
Binary file not shown.
|
|
@ -5,7 +5,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
|
||||
enum AppThemeType { minimal, doodle, cyberpunk, wood, arcade, grimorio }
|
||||
enum AppThemeType { doodle, wood, cyberpunk, arcade, grimorio }
|
||||
|
||||
class ThemeColors {
|
||||
final Color background;
|
||||
|
|
@ -24,11 +24,6 @@ class ThemeColors {
|
|||
}
|
||||
|
||||
class AppColors {
|
||||
static const ThemeColors minimal = ThemeColors(
|
||||
background: Color(0xFFF5F7FA), gridLine: Color(0xFFCFD8DC),
|
||||
playerRed: Color(0xFFE53935), playerBlue: Color(0xFF1E88E5), text: Color(0xFF263238),
|
||||
);
|
||||
|
||||
static const ThemeColors doodle = ThemeColors(
|
||||
background: Color(0xFFFFF9E6), gridLine: Color(0xFFB0BEC5),
|
||||
playerRed: Color(0xFFD32F2F), playerBlue: Color(0xFF1565C0), text: Color(0xFF37474F),
|
||||
|
|
@ -56,10 +51,9 @@ class AppColors {
|
|||
|
||||
static ThemeColors getTheme(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.minimal: return minimal;
|
||||
case AppThemeType.doodle: return doodle;
|
||||
case AppThemeType.cyberpunk: return cyberpunk;
|
||||
case AppThemeType.wood: return wood;
|
||||
case AppThemeType.cyberpunk: return cyberpunk;
|
||||
case AppThemeType.arcade: return arcade;
|
||||
case AppThemeType.grimorio: return grimorio;
|
||||
}
|
||||
|
|
@ -69,7 +63,6 @@ class AppColors {
|
|||
class ThemeIcons {
|
||||
static IconData gold(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.minimal: return Icons.star_rounded;
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.star;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.gem;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.microchip;
|
||||
|
|
@ -80,7 +73,6 @@ class ThemeIcons {
|
|||
|
||||
static IconData bomb(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.minimal: return Icons.mood_bad_rounded;
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.virus;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.fire;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.bug;
|
||||
|
|
@ -91,7 +83,6 @@ class ThemeIcons {
|
|||
|
||||
static IconData swap(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.minimal: return Icons.sync_rounded;
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.arrowsRotate;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.rightLeft;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.networkWired;
|
||||
|
|
@ -102,7 +93,6 @@ class ThemeIcons {
|
|||
|
||||
static IconData joker(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.minimal: return Icons.sentiment_satisfied_alt;
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.faceSmileBeam;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.key;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.robot;
|
||||
|
|
@ -113,7 +103,6 @@ class ThemeIcons {
|
|||
|
||||
static IconData block(AppThemeType type) {
|
||||
switch (type) {
|
||||
case AppThemeType.minimal: return Icons.block;
|
||||
case AppThemeType.doodle: return FontAwesomeIcons.squareXmark;
|
||||
case AppThemeType.wood: return FontAwesomeIcons.ban;
|
||||
case AppThemeType.cyberpunk: return FontAwesomeIcons.shieldHalved;
|
||||
|
|
@ -122,7 +111,6 @@ class ThemeIcons {
|
|||
}
|
||||
}
|
||||
|
||||
// --- NUOVE ICONE ---
|
||||
static IconData ice(AppThemeType type) {
|
||||
return FontAwesomeIcons.snowflake;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ import '../services/storage_service.dart';
|
|||
import '../services/multiplayer_service.dart';
|
||||
import '../core/app_colors.dart';
|
||||
|
||||
class CpuMatchSetup {
|
||||
final int radius;
|
||||
final ArenaShape shape;
|
||||
CpuMatchSetup(this.radius, this.shape);
|
||||
}
|
||||
|
||||
class GameController extends ChangeNotifier {
|
||||
late GameBoard board;
|
||||
bool isVsCPU = false;
|
||||
|
|
@ -50,11 +56,9 @@ class GameController extends ChangeNotifier {
|
|||
bool opponentWantsRematch = false;
|
||||
int lastMatchXP = 0;
|
||||
|
||||
// --- VARIABILI PER IL LEVEL UP ---
|
||||
bool hasLeveledUp = false;
|
||||
int newlyReachedLevel = 1;
|
||||
List<String> unlockedFeatures = [];
|
||||
// ---------------------------------
|
||||
|
||||
bool isSetupPhase = true;
|
||||
bool myJokerPlaced = false;
|
||||
|
|
@ -67,7 +71,7 @@ class GameController extends ChangeNotifier {
|
|||
int cpuLevel = 1;
|
||||
int currentMatchLevel = 1;
|
||||
int? currentSeed;
|
||||
AppThemeType _activeTheme = AppThemeType.cyberpunk;
|
||||
AppThemeType _activeTheme = AppThemeType.doodle;
|
||||
|
||||
String onlineHostName = "ROSSO";
|
||||
String onlineGuestName = "BLU";
|
||||
|
|
@ -78,6 +82,25 @@ class GameController extends ChangeNotifier {
|
|||
startNewGame(radius);
|
||||
}
|
||||
|
||||
CpuMatchSetup _getSetupForCpuLevel(int level) {
|
||||
final rand = Random();
|
||||
if (level == 1) return CpuMatchSetup(3, ArenaShape.classic);
|
||||
if (level == 2) return CpuMatchSetup(4, ArenaShape.classic);
|
||||
if (level == 3) return CpuMatchSetup(4, ArenaShape.cross);
|
||||
if (level == 4) return CpuMatchSetup(4, ArenaShape.donut);
|
||||
if (level == 5) return CpuMatchSetup(5, ArenaShape.classic);
|
||||
if (level == 6) return CpuMatchSetup(4, ArenaShape.hourglass);
|
||||
if (level == 7) return CpuMatchSetup(5, ArenaShape.cross);
|
||||
if (level == 8) return CpuMatchSetup(5, ArenaShape.donut);
|
||||
if (level == 9) return CpuMatchSetup(5, ArenaShape.hourglass);
|
||||
|
||||
List<ArenaShape> hardShapes = [ArenaShape.classic, ArenaShape.cross, ArenaShape.donut, ArenaShape.hourglass, ArenaShape.chaos];
|
||||
ArenaShape chosenShape = hardShapes[rand.nextInt(hardShapes.length)];
|
||||
|
||||
int chosenRadius = (chosenShape == ArenaShape.chaos) ? (rand.nextInt(2) + 4) : (rand.nextInt(2) + 5);
|
||||
return CpuMatchSetup(chosenRadius, chosenShape);
|
||||
}
|
||||
|
||||
void startNewGame(int radius, {bool vsCPU = false, bool isOnline = false, String? roomCode, bool isHost = false, ArenaShape shape = ArenaShape.classic, bool timeMode = true}) {
|
||||
_onlineSubscription?.cancel();
|
||||
_onlineSubscription = null;
|
||||
|
|
@ -87,7 +110,6 @@ class GameController extends ChangeNotifier {
|
|||
_hasSavedResult = false;
|
||||
lastMatchXP = 0;
|
||||
|
||||
// Reset Level Up vars
|
||||
hasLeveledUp = false;
|
||||
unlockedFeatures.clear();
|
||||
|
||||
|
|
@ -108,10 +130,19 @@ class GameController extends ChangeNotifier {
|
|||
this.isHost = isHost;
|
||||
this.isTimeMode = timeMode;
|
||||
|
||||
onlineShape = shape;
|
||||
int finalRadius = radius;
|
||||
ArenaShape finalShape = shape;
|
||||
|
||||
if (this.isVsCPU) {
|
||||
CpuMatchSetup setup = _getSetupForCpuLevel(cpuLevel);
|
||||
finalRadius = setup.radius;
|
||||
finalShape = setup.shape;
|
||||
}
|
||||
|
||||
onlineShape = finalShape;
|
||||
int levelToUse = isOnline ? (currentMatchLevel == 1 ? 2 : currentMatchLevel) : cpuLevel;
|
||||
|
||||
board = GameBoard(radius: radius, level: levelToUse, seed: currentSeed, shape: onlineShape);
|
||||
board = GameBoard(radius: finalRadius, level: levelToUse, seed: currentSeed, shape: finalShape);
|
||||
board.currentPlayer = Player.red;
|
||||
|
||||
isCPUThinking = false;
|
||||
|
|
@ -513,12 +544,10 @@ class GameController extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
// --- LOGICA LEVEL UP E SBLOCCHI ---
|
||||
List<String> _getUnlocks(int oldLevel, int newLevel) {
|
||||
List<String> unlocks = [];
|
||||
for(int i = oldLevel + 1; i <= newLevel; i++) {
|
||||
if (i == 3) unlocks.add("Tema: Legno & Fiammiferi");
|
||||
if (i == 5) unlocks.add("Tema: Quaderno (Doodle)");
|
||||
if (i == 7) unlocks.add("Tema: Cyberpunk");
|
||||
if (i == 10) {
|
||||
unlocks.add("Tema: 8-Bit Arcade");
|
||||
|
|
@ -538,7 +567,7 @@ class GameController extends ChangeNotifier {
|
|||
String myRealName = StorageService.instance.playerName;
|
||||
if (myRealName.isEmpty) myRealName = "IO";
|
||||
|
||||
int oldLevel = StorageService.instance.playerLevel; // Salviamo il vecchio livello
|
||||
int oldLevel = StorageService.instance.playerLevel;
|
||||
|
||||
if (isOnline) {
|
||||
bool isWin = isHost ? board.scoreRed > board.scoreBlue : board.scoreBlue > board.scoreRed;
|
||||
|
|
@ -574,7 +603,6 @@ class GameController extends ChangeNotifier {
|
|||
lastMatchXP = calculatedXP;
|
||||
StorageService.instance.addXP(calculatedXP);
|
||||
|
||||
// --- CONTROLLO LEVEL UP DOPO AVER DATO GLI XP ---
|
||||
int newLevel = StorageService.instance.playerLevel;
|
||||
if (newLevel > oldLevel) {
|
||||
hasLeveledUp = true;
|
||||
|
|
|
|||
|
|
@ -15,46 +15,35 @@ class AudioService extends ChangeNotifier {
|
|||
final AudioPlayer _sfxPlayer = AudioPlayer();
|
||||
final AudioPlayer _bgmPlayer = AudioPlayer();
|
||||
|
||||
// Teniamo traccia del tema attuale per gestire bene quando togliamo il muto
|
||||
AppThemeType _currentTheme = AppThemeType.cyberpunk;
|
||||
AppThemeType _currentTheme = AppThemeType.doodle;
|
||||
|
||||
Future<void> init() async {
|
||||
// 1. Carica la preferenza salvata
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
isMuted = prefs.getBool('isMuted') ?? false;
|
||||
|
||||
// 2. Imposta la musica in loop infinito
|
||||
await _bgmPlayer.setReleaseMode(ReleaseMode.loop);
|
||||
}
|
||||
|
||||
void toggleMute() async {
|
||||
isMuted = !isMuted;
|
||||
|
||||
// Salva la scelta per il prossimo riavvio
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('isMuted', isMuted);
|
||||
|
||||
if (isMuted) {
|
||||
await _bgmPlayer.pause();
|
||||
} else {
|
||||
// Se togliamo il muto, facciamo ripartire la canzone del tema attuale
|
||||
playBgm(_currentTheme);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// --- BGM (Musica di sottofondo) ---
|
||||
Future<void> playBgm(AppThemeType theme) async {
|
||||
_currentTheme = theme; // Aggiorna sempre la memoria del tema
|
||||
|
||||
// FERMA SEMPRE LA TRACCIA PRECEDENTE prima di far partire la nuova
|
||||
_currentTheme = theme;
|
||||
await _bgmPlayer.stop();
|
||||
|
||||
if (isMuted) return;
|
||||
|
||||
String audioPath = '';
|
||||
|
||||
// Assegna a ogni tema la sua colonna sonora
|
||||
switch (theme) {
|
||||
case AppThemeType.cyberpunk:
|
||||
audioPath = 'audio/bgm/Cyber_Dystopia.mp3';
|
||||
|
|
@ -71,15 +60,10 @@ class AudioService extends ChangeNotifier {
|
|||
case AppThemeType.grimorio:
|
||||
audioPath = 'audio/bgm/Grimorio_Astral.mp3';
|
||||
break;
|
||||
case AppThemeType.minimal:
|
||||
// Il tema minimal non ha musica (silenzio/focus)
|
||||
audioPath = '';
|
||||
break;
|
||||
}
|
||||
|
||||
if (audioPath.isNotEmpty) {
|
||||
try {
|
||||
// IL VOLUME VA PASSATO QUI (0.15 = 15%), sennò viene ignorato!
|
||||
await _bgmPlayer.play(AssetSource(audioPath), volume: 0.15);
|
||||
} catch (e) {
|
||||
debugPrint("Errore riproduzione BGM: $e");
|
||||
|
|
@ -91,12 +75,10 @@ class AudioService extends ChangeNotifier {
|
|||
await _bgmPlayer.stop();
|
||||
}
|
||||
|
||||
// --- SFX (Effetti sonori) ---
|
||||
void playLineSfx(AppThemeType theme) async {
|
||||
if (isMuted) return;
|
||||
String file = '';
|
||||
switch (theme) {
|
||||
case AppThemeType.minimal:
|
||||
case AppThemeType.arcade:
|
||||
file = 'minimal_line.wav'; break;
|
||||
case AppThemeType.doodle:
|
||||
|
|
@ -109,10 +91,9 @@ class AudioService extends ChangeNotifier {
|
|||
|
||||
if (file.isNotEmpty) {
|
||||
try {
|
||||
// Effetti sonori forzati al 100%
|
||||
await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0);
|
||||
} catch (e) {
|
||||
debugPrint("Errore SFX Linea non trovato: $file");
|
||||
debugPrint("Errore SFX Linea: $file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +102,6 @@ class AudioService extends ChangeNotifier {
|
|||
if (isMuted) return;
|
||||
String file = '';
|
||||
switch (theme) {
|
||||
case AppThemeType.minimal:
|
||||
case AppThemeType.arcade:
|
||||
file = 'minimal_box.wav'; break;
|
||||
case AppThemeType.doodle:
|
||||
|
|
@ -136,7 +116,7 @@ class AudioService extends ChangeNotifier {
|
|||
try {
|
||||
await _sfxPlayer.play(AssetSource('audio/sfx/$file'), volume: 1.0);
|
||||
} catch (e) {
|
||||
debugPrint("Errore SFX Box non trovato: $file");
|
||||
debugPrint("Errore SFX Box: $file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,11 @@ class StorageService {
|
|||
|
||||
Future<void> init() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
_checkDailyQuests(); // All'avvio controlliamo se ci sono nuove sfide
|
||||
_checkDailyQuests();
|
||||
}
|
||||
|
||||
int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.minimal.index;
|
||||
// Doodle è il nuovo tema di partenza (index 0)
|
||||
int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.doodle.index;
|
||||
Future<void> saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index);
|
||||
|
||||
int get savedRadius => _prefs.getInt('radius') ?? 2;
|
||||
|
|
@ -31,7 +32,6 @@ class StorageService {
|
|||
|
||||
int get totalXP => _prefs.getInt('totalXP') ?? 0;
|
||||
|
||||
// Modificato per sincronizzare automaticamente la classifica su Firebase
|
||||
Future<void> addXP(int xp) async {
|
||||
await _prefs.setInt('totalXP', totalXP + xp);
|
||||
syncLeaderboard();
|
||||
|
|
@ -54,20 +54,17 @@ class StorageService {
|
|||
String get playerName => _prefs.getString('playerName') ?? '';
|
||||
Future<void> savePlayerName(String name) async {
|
||||
await _prefs.setString('playerName', name);
|
||||
syncLeaderboard(); // Aggiorna il nome in classifica
|
||||
syncLeaderboard();
|
||||
}
|
||||
|
||||
// --- NUOVO: SINCRONIZZAZIONE CLASSIFICA ONLINE ---
|
||||
Future<void> syncLeaderboard() async {
|
||||
if (playerName.isNotEmpty) {
|
||||
try {
|
||||
// Recuperiamo il nostro ID segreto e univoco appena creato
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
|
||||
if (user != null) {
|
||||
// Usiamo user.uid come nome del documento, NON più il playerName!
|
||||
await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({
|
||||
'name': playerName, // Il nome rimane dentro per mostrarlo nella lista
|
||||
'name': playerName,
|
||||
'xp': totalXP,
|
||||
'level': playerLevel,
|
||||
'wins': wins,
|
||||
|
|
@ -75,32 +72,26 @@ class StorageService {
|
|||
}, SetOptions(merge: true));
|
||||
}
|
||||
} catch(e) {
|
||||
// Ignoriamo gli errori se manca la rete, si sincronizzerà dopo
|
||||
debugPrint("Errore sinc. classifica: $e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- NUOVO: GESTIONE SFIDE GIORNALIERE ---
|
||||
void _checkDailyQuests() {
|
||||
String today = DateTime.now().toIso8601String().substring(0, 10);
|
||||
String lastDate = _prefs.getString('quest_date') ?? '';
|
||||
|
||||
if (today != lastDate) {
|
||||
// Nuovo giorno, nuove sfide!
|
||||
_prefs.setString('quest_date', today);
|
||||
|
||||
// Sfida 1: Gioca partite online
|
||||
_prefs.setInt('q1_type', 0);
|
||||
_prefs.setInt('q1_prog', 0);
|
||||
_prefs.setInt('q1_target', 3);
|
||||
|
||||
// Sfida 2: Vinci contro la CPU
|
||||
_prefs.setInt('q2_type', 1);
|
||||
_prefs.setInt('q2_prog', 0);
|
||||
_prefs.setInt('q2_target', 2);
|
||||
|
||||
// Sfida 3: Partite con forme speciali (Croce, Caos, ecc)
|
||||
_prefs.setInt('q3_type', 2);
|
||||
_prefs.setInt('q3_prog', 0);
|
||||
_prefs.setInt('q3_target', 2);
|
||||
|
|
@ -119,7 +110,6 @@ class StorageService {
|
|||
}
|
||||
}
|
||||
|
||||
// --- STORICO PARTITE ---
|
||||
List<Map<String, dynamic>> get matchHistory {
|
||||
List<String> history = _prefs.getStringList('matchHistory') ?? [];
|
||||
return history.map((e) => jsonDecode(e) as Map<String, dynamic>).toList();
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ class BoardPainter extends CustomPainter {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Sfondo azzurrino se è di ghiaccio (anche prima di chiuderla)
|
||||
if (box.type == BoxType.ice && box.owner == Player.none) {
|
||||
canvas.drawRect(rect.deflate(2.0), Paint()..color = Colors.cyanAccent.withOpacity(0.05)..style=PaintingStyle.fill);
|
||||
}
|
||||
|
|
@ -127,10 +126,9 @@ class BoardPainter extends CustomPainter {
|
|||
Offset p1 = getScreenPos(line.p1.x, line.p1.y);
|
||||
Offset p2 = getScreenPos(line.p2.x, line.p2.y);
|
||||
|
||||
// --- 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);
|
||||
|
|
@ -220,7 +218,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);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
bool _gameOverDialogShown = false;
|
||||
bool _opponentLeftDialogShown = false;
|
||||
|
||||
// Variabili per coprire il posizionamento del Jolly in Locale
|
||||
bool _hideJokerMessage = false;
|
||||
bool _wasSetupPhase = false;
|
||||
Player _lastJokerTurn = Player.red;
|
||||
|
|
@ -100,7 +99,6 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
decoration: BoxDecoration(color: theme.text.withOpacity(0.05), borderRadius: BorderRadius.circular(15)),
|
||||
// AGGIUNTO FITTEDBOX QUI
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Row(
|
||||
|
|
@ -196,8 +194,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
titleText = "Nascondi il tuo Jolly!";
|
||||
subtitleText = "(Tocca qui per nascondere)";
|
||||
} else {
|
||||
// --- TESTI MODALITÀ LOCALE ---
|
||||
String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? "VERDE" : "BLU");
|
||||
String pName = gameController.jokerTurn == Player.red ? "ROSSO" : (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? "VERDE" : "BLU");
|
||||
titleText = "TURNO GIOCATORE $pName";
|
||||
subtitleText = "Passa il dispositivo.\nL'avversario NON deve guardare!\n\n(Tocca qui quando sei pronto)";
|
||||
}
|
||||
|
|
@ -207,7 +204,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.yellowAccent : theme.playerBlue, size: 50),
|
||||
Icon(ThemeIcons.joker(themeType), color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.yellowAccent : theme.playerBlue, size: 50),
|
||||
const SizedBox(height: 15),
|
||||
Text(
|
||||
titleText,
|
||||
|
|
@ -243,7 +240,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
} else if (themeType == AppThemeType.grimorio) {
|
||||
return Container(decoration: BoxDecoration(color: const Color(0xFF2C1E3D), borderRadius: BorderRadius.circular(30), border: Border.all(color: const Color(0xFFBCAAA4), width: 3), boxShadow: [BoxShadow(color: Colors.deepPurpleAccent.withOpacity(0.5), blurRadius: 20, spreadRadius: 5)]), child: content);
|
||||
} else {
|
||||
return Container(decoration: BoxDecoration(color: theme.background, borderRadius: BorderRadius.circular(20), border: Border.all(color: theme.gridLine, width: 2), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.15), blurRadius: 20, offset: const Offset(0, 10))]), child: content);
|
||||
return Container(decoration: BoxDecoration(color: theme.background.withOpacity(0.95), borderRadius: BorderRadius.circular(20), border: Border.all(color: theme.gridLine.withOpacity(0.5), width: 2), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 10))]), child: content);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -254,13 +251,10 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
final theme = themeManager.currentColors;
|
||||
final gameController = context.watch<GameController>();
|
||||
|
||||
// --- LOGICA CAMBIO TURNO E SCHERMATA JOLLY ---
|
||||
if (gameController.isSetupPhase && !_wasSetupPhase) {
|
||||
// È appena iniziata una nuova partita
|
||||
_hideJokerMessage = false;
|
||||
_lastJokerTurn = Player.red;
|
||||
} else if (gameController.isSetupPhase && gameController.jokerTurn != _lastJokerTurn) {
|
||||
// È cambiato il turno durante il setup (in modalità locale), rifacciamo apparire la copertura
|
||||
_hideJokerMessage = false;
|
||||
_lastJokerTurn = gameController.jokerTurn;
|
||||
}
|
||||
|
|
@ -296,7 +290,7 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
if (themeType == AppThemeType.wood) bgImage = 'assets/images/wood_bg.jpg';
|
||||
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||
|
||||
Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.white : Colors.black;
|
||||
Color indicatorColor = themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : Colors.black;
|
||||
|
||||
Widget emojiBar = const SizedBox();
|
||||
if (gameController.isOnline && !gameController.isGameOver) {
|
||||
|
|
@ -304,9 +298,9 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
emojiBar = Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8),
|
||||
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.black.withOpacity(0.6) : Colors.white.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
border: Border.all(color: themeType == AppThemeType.cyberpunk ? theme.playerBlue.withOpacity(0.3) : Colors.black12, width: 2),
|
||||
border: Border.all(color: themeType == AppThemeType.cyberpunk ? theme.playerBlue.withOpacity(0.3) : Colors.white24, width: 2),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -388,10 +382,10 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
Container(
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.4), offset: const Offset(0, 4), blurRadius: 5)]),
|
||||
child: TextButton.icon(
|
||||
style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))),
|
||||
icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, size: 20),
|
||||
style: TextButton.styleFrom(backgroundColor: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.black87 : theme.background, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.white.withOpacity(0.1), width: 1))),
|
||||
icon: Icon(Icons.exit_to_app, color: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : theme.text, size: 20),
|
||||
onPressed: () { gameController.disconnectOnlineGame(); Navigator.pop(context); },
|
||||
label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))),
|
||||
label: Text("ESCI", style: _getTextStyle(themeType, TextStyle(color: bgImage != null || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora ? Colors.white : theme.text, fontWeight: FontWeight.bold, fontSize: 12))),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -412,11 +406,21 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
canPop: true,
|
||||
onPopInvoked: (didPop) { gameController.disconnectOnlineGame(); },
|
||||
child: Scaffold(
|
||||
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
|
||||
body: CustomPaint(
|
||||
painter: themeType == AppThemeType.minimal ? FullScreenGridPainter(Colors.black.withOpacity(0.06)) : null,
|
||||
child: Container(
|
||||
decoration: bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover, colorFilter: themeType == AppThemeType.doodle ? ColorFilter.mode(Colors.white.withOpacity(0.7), BlendMode.lighten) : null)) : null,
|
||||
// L'impostazione dello sfondo dipende dal tema
|
||||
backgroundColor: themeType == AppThemeType.aurora ? Colors.transparent : (bgImage != null ? Colors.transparent : theme.background),
|
||||
body: Container(
|
||||
// GRADIENTE AURORA IN BACKGROUND!
|
||||
decoration: themeType == AppThemeType.aurora
|
||||
? const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFF2A0845), Color(0xFFDD2476), Color(0xFFFF512F)],
|
||||
),
|
||||
)
|
||||
: (bgImage != null ? BoxDecoration(image: DecorationImage(image: AssetImage(bgImage), fit: BoxFit.cover, colorFilter: themeType == AppThemeType.doodle ? ColorFilter.mode(Colors.white.withOpacity(0.7), BlendMode.lighten) : null)) : null),
|
||||
child: CustomPaint(
|
||||
// painter rimosso per supportare il nuovo sfondo a gradiente
|
||||
child: Stack(
|
||||
children: [
|
||||
if (gameController.isTimeMode && !gameController.isCPUThinking && !gameController.isGameOver && gameController.timeLeft > 0 && gameController.timeLeft <= 5 && !gameController.isSetupPhase)
|
||||
|
|
@ -427,13 +431,11 @@ class _GameScreenState extends State<GameScreen> with TickerProviderStateMixin {
|
|||
|
||||
Positioned.fill(child: gameContent),
|
||||
|
||||
// --- SCHERMATA COPRENTE PER IL PASSAGGIO DEL TELEFONO IN LOCALE ---
|
||||
if (gameController.isSetupPhase && !_hideJokerMessage)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
// Il colore di sfondo riempie tutto lo schermo per non far sbirciare la griglia
|
||||
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade
|
||||
? Colors.black
|
||||
color: themeType == AppThemeType.cyberpunk || themeType == AppThemeType.arcade || themeType == AppThemeType.aurora
|
||||
? Colors.black.withOpacity(0.98)
|
||||
: theme.background.withOpacity(0.98),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
|
|
@ -518,7 +520,7 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
|
|||
}
|
||||
|
||||
void _initParticles(Size screenSize) {
|
||||
int particleCount = widget.themeType == AppThemeType.cyberpunk ? 150 : 100;
|
||||
int particleCount = widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.aurora ? 150 : 100;
|
||||
if (widget.themeType == AppThemeType.arcade) particleCount = 80;
|
||||
if (widget.themeType == AppThemeType.grimorio) particleCount = 120;
|
||||
|
||||
|
|
@ -528,6 +530,7 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
|
|||
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.aurora) { palette = [widget.winnerColor, Colors.white, Colors.orangeAccent, Colors.pinkAccent]; }
|
||||
|
||||
for (int i = 0; i < particleCount; i++) {
|
||||
double speed = _rand.nextDouble() * 20 + 5;
|
||||
|
|
@ -540,7 +543,7 @@ class _WinnerVFXOverlayState extends State<WinnerVFXOverlay> with SingleTickerPr
|
|||
setState(() {
|
||||
for (var p in _particles) {
|
||||
p.x += p.vx; p.y += p.vy;
|
||||
if (widget.themeType == AppThemeType.cyberpunk) { p.vy += 0.1; p.vx *= 0.98; p.vy *= 0.98; }
|
||||
if (widget.themeType == AppThemeType.cyberpunk || widget.themeType == AppThemeType.aurora) { 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; }
|
||||
|
|
@ -563,7 +566,7 @@ class _VFXPainter extends CustomPainter {
|
|||
for (var p in particles) {
|
||||
if (p.size < 0.5) continue;
|
||||
final paint = Paint()..color = p.color..style = PaintingStyle.fill;
|
||||
if (themeType == AppThemeType.cyberpunk) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); }
|
||||
if (themeType == AppThemeType.cyberpunk || themeType == AppThemeType.aurora) { paint.maskFilter = const MaskFilter.blur(BlurStyle.solid, 4.0); }
|
||||
canvas.save(); canvas.translate(p.x, p.y); canvas.rotate(p.angle);
|
||||
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
|
|
@ -600,6 +603,7 @@ class _BouncingEmojiState extends State<_BouncingEmoji> with SingleTickerProvide
|
|||
@override Widget build(BuildContext context) { return AnimatedBuilder(animation: _anim, builder: (ctx, child) => Transform.translate(offset: Offset(0, _anim.value), child: Container(padding: const EdgeInsets.all(8), decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle, boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 5)]), child: Text(widget.emoji, style: const TextStyle(fontSize: 32))))); }
|
||||
}
|
||||
|
||||
// Mantengo la definizione per evitare buchi, ma ora Aurora usa il LinearGradient!
|
||||
class FullScreenGridPainter extends CustomPainter {
|
||||
final Color gridColor; FullScreenGridPainter(this.gridColor);
|
||||
@override void paint(Canvas canvas, Size size) { final Paint paperGridPaint = Paint()..color = gridColor..strokeWidth = 1.0..style = PaintingStyle.stroke; double paperStep = 20.0; for (double i = 0; i <= size.width; i += paperStep) canvas.drawLine(Offset(i, 0), Offset(i, size.height), paperGridPaint); for (double i = 0; i <= size.height; i += paperStep) canvas.drawLine(Offset(0, i), Offset(size.width, i), paperGridPaint); }
|
||||
|
|
|
|||
|
|
@ -405,13 +405,13 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
StorageService.instance.syncLeaderboard();
|
||||
});
|
||||
_checkClipboardForInvite();
|
||||
_initDeepLinks(); // <--- AGGIUNGI QUESTO
|
||||
_initDeepLinks();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_linkSubscription?.cancel(); // <--- AGGIUNGI QUESTO
|
||||
_linkSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -541,13 +541,11 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
// --- LOGICA DEEP LINKS ---
|
||||
Future<void> _initDeepLinks() async {
|
||||
_appLinks = AppLinks();
|
||||
|
||||
// 1. Controlla se l'app è stata aperta DA CHIUSA tramite un link
|
||||
try {
|
||||
final initialUri = await _appLinks.getInitialLink(); // <--- ECCO LA PAROLA CORRETTA!
|
||||
final initialUri = await _appLinks.getInitialLink();
|
||||
if (initialUri != null) {
|
||||
_handleDeepLink(initialUri);
|
||||
}
|
||||
|
|
@ -555,7 +553,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
debugPrint("Errore lettura link iniziale: $e");
|
||||
}
|
||||
|
||||
// 2. Rimane in ascolto se l'app era IN BACKGROUND
|
||||
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
|
||||
_handleDeepLink(uri);
|
||||
}, onError: (err) {
|
||||
|
|
@ -568,7 +565,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
if (uri.scheme == 'tetraq' && uri.host == 'join') {
|
||||
String? code = uri.queryParameters['code'];
|
||||
if (code != null && code.length == 5) {
|
||||
// Usa un piccolo delay per assicurarsi che l'app sia pronta
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) {
|
||||
_promptJoinRoom(code.toUpperCase());
|
||||
|
|
@ -577,7 +573,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
}
|
||||
}
|
||||
}
|
||||
// -------------------------
|
||||
|
||||
Future<void> _checkClipboardForInvite() async {
|
||||
try {
|
||||
|
|
@ -662,41 +657,68 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isVsCPU ? loc.cpuTitle : loc.localTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2))),
|
||||
const SizedBox(height: 25),
|
||||
|
||||
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 15),
|
||||
Wrap(
|
||||
spacing: 12, runSpacing: 12, alignment: WrapAlignment.center,
|
||||
children: [
|
||||
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
|
||||
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
|
||||
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
|
||||
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
|
||||
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 25),
|
||||
Divider(color: inkColor.withOpacity(0.3), thickness: 2.5),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
|
||||
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
|
||||
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
|
||||
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
alignment: Alignment.centerLeft,
|
||||
icon: Icon(Icons.arrow_back_ios_new, color: inkColor, size: 26),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(isVsCPU ? loc.cpuTitle : loc.localTitle, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 26, fontWeight: FontWeight.w900, color: inkColor, letterSpacing: 2))),
|
||||
),
|
||||
const SizedBox(width: 40),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 25),
|
||||
Divider(color: inkColor.withOpacity(0.3), thickness: 2.5),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
if (isVsCPU) ...[
|
||||
Icon(Icons.smart_toy, size: 50, color: inkColor.withOpacity(0.6)),
|
||||
const SizedBox(height: 10),
|
||||
Text("MODALITÀ CAMPAGNA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: inkColor))),
|
||||
const SizedBox(height: 10),
|
||||
Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 13, color: inkColor.withOpacity(0.8), height: 1.4))),
|
||||
const SizedBox(height: 25),
|
||||
Divider(color: inkColor.withOpacity(0.3), thickness: 2.5),
|
||||
const SizedBox(height: 20),
|
||||
] else ...[
|
||||
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 15),
|
||||
Wrap(
|
||||
spacing: 12, runSpacing: 12, alignment: WrapAlignment.center,
|
||||
children: [
|
||||
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
|
||||
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
|
||||
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
|
||||
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
|
||||
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 25),
|
||||
Divider(color: inkColor.withOpacity(0.3), thickness: 2.5),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
|
||||
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
|
||||
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
|
||||
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 25),
|
||||
Divider(color: inkColor.withOpacity(0.3), thickness: 2.5),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
|
||||
Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: inkColor.withOpacity(0.6), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 10),
|
||||
|
|
@ -742,41 +764,68 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isVsCPU ? loc.cpuTitle : loc.localTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: 10, runSpacing: 10, alignment: WrapAlignment.center,
|
||||
children: [
|
||||
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
|
||||
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
|
||||
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
|
||||
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
|
||||
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
Divider(color: Colors.white.withOpacity(0.05), thickness: 2),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
|
||||
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
|
||||
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
|
||||
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)),
|
||||
SizedBox(
|
||||
width: 40,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
alignment: Alignment.centerLeft,
|
||||
icon: Icon(Icons.arrow_back_ios_new, color: theme.text, size: 26),
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(isVsCPU ? loc.cpuTitle : loc.localTitle, textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))),
|
||||
),
|
||||
const SizedBox(width: 40),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
Divider(color: Colors.white.withOpacity(0.05), thickness: 2),
|
||||
const SizedBox(height: 20),
|
||||
if (isVsCPU) ...[
|
||||
Icon(Icons.smart_toy, size: 50, color: theme.playerBlue),
|
||||
const SizedBox(height: 10),
|
||||
Text("MODALITÀ CAMPAGNA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
|
||||
const SizedBox(height: 10),
|
||||
Text("Livello CPU: ${StorageService.instance.cpuLevel}\nForma e dimensioni si adatteranno alla tua bravura!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 13, color: theme.text.withOpacity(0.7), height: 1.4))),
|
||||
const SizedBox(height: 20),
|
||||
Divider(color: Colors.white.withOpacity(0.05), thickness: 2),
|
||||
const SizedBox(height: 20),
|
||||
] else ...[
|
||||
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: 10, runSpacing: 10, alignment: WrapAlignment.center,
|
||||
children: [
|
||||
_NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: localShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.classic)),
|
||||
_NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: localShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.cross)),
|
||||
_NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: localShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.donut)),
|
||||
_NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: localShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localShape = ArenaShape.hourglass)),
|
||||
_NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: localShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setStateDialog(() => localShape = ArenaShape.chaos)),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
Divider(color: Colors.white.withOpacity(0.05), thickness: 2),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_NeonSizeButton(label: 'S', isSelected: localRadius == 3, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 3)),
|
||||
_NeonSizeButton(label: 'M', isSelected: localRadius == 4, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 4)),
|
||||
_NeonSizeButton(label: 'L', isSelected: localRadius == 5, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 5)),
|
||||
_NeonSizeButton(label: 'MAX', isSelected: localRadius == 6, theme: theme, themeType: themeType, onTap: () => setStateDialog(() => localRadius = 6)),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
Divider(color: Colors.white.withOpacity(0.05), thickness: 2),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
|
||||
Text("TEMPO", style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 10),
|
||||
|
|
@ -813,7 +862,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
// --- NUOVA FUNZIONE: MOSTRA LE SFIDE GIORNALIERE ---
|
||||
Future<void> _showDailyQuestsDialog() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
|
|
@ -846,7 +894,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
Text(loc.questsTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
|
||||
const SizedBox(height: 25),
|
||||
|
||||
// Generiamo dinamicamente le 3 missioni salvate in memoria
|
||||
...List.generate(3, (index) {
|
||||
int i = index + 1;
|
||||
int type = prefs.getInt('q${i}_type') ?? 0;
|
||||
|
|
@ -916,7 +963,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
// --- NUOVA FUNZIONE: MOSTRA LA CLASSIFICA GLOBALE ---
|
||||
void _showLeaderboardDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
|
@ -943,7 +989,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
Text(loc.leaderboardTitle, style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 1.5))),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Lista giocatori pescata da Firebase!
|
||||
SizedBox(
|
||||
height: 350,
|
||||
child: StreamBuilder<QuerySnapshot>(
|
||||
|
|
@ -959,7 +1004,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
final docs = snapshot.data!.docs;
|
||||
return ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: docs.length, // <--- ECCO LA RIGA MAGICA AGGIUNTA!
|
||||
itemCount: docs.length,
|
||||
itemBuilder: (context, index) {
|
||||
var data = docs[index].data() as Map<String, dynamic>;
|
||||
|
||||
|
|
@ -1320,7 +1365,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
|
||||
const Spacer(),
|
||||
|
||||
// --- NUOVA LISTA BOTTONI CON CLASSIFICHE E SFIDE ---
|
||||
// --- NUOVA LISTA BOTTONI ---
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
|
|
@ -1331,7 +1376,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
_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),
|
||||
|
||||
// NUOVI BOTTONI PER LA VERSIONE 2.0
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _buildCyberCard(_FeatureCard(title: loc.leaderboardTitle, subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: _showLeaderboardDialog, compact: true), themeType)),
|
||||
|
|
@ -1457,7 +1501,6 @@ class _FeatureCard extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// --- MAGIA QUI: FittedBox impedisce di andare a capo ---
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
|
|
@ -1522,7 +1565,6 @@ class _FeatureCard extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// --- MAGIA QUI: FittedBox impedisce di andare a capo ---
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
padding: const EdgeInsets.all(20),
|
||||
children: [
|
||||
_ThemeCard(
|
||||
title: "Minimal",
|
||||
subtitle: "Linee pulite, sfondo chiaro",
|
||||
type: AppThemeType.minimal,
|
||||
previewColors: AppColors.minimal,
|
||||
title: "Quaderno (Doodle)",
|
||||
subtitle: "Sfondo a quadretti, tratto a penna",
|
||||
type: AppThemeType.doodle,
|
||||
previewColors: AppColors.doodle,
|
||||
requiredLevel: 1,
|
||||
currentLevel: playerLevel,
|
||||
),
|
||||
|
|
@ -52,15 +52,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
currentLevel: playerLevel,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_ThemeCard(
|
||||
title: "Quaderno (Doodle)",
|
||||
subtitle: "Sfondo a quadretti, tratto a penna",
|
||||
type: AppThemeType.doodle,
|
||||
previewColors: AppColors.doodle,
|
||||
requiredLevel: 5,
|
||||
currentLevel: playerLevel,
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_ThemeCard(
|
||||
title: "Cyberpunk",
|
||||
subtitle: "Nero profondo, luci al neon",
|
||||
|
|
@ -164,9 +155,9 @@ class _ThemeCard extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerRed, shape: BoxShape.circle)),
|
||||
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)),
|
||||
Container(width: 20, height: 20, decoration: BoxDecoration(color: previewColors.playerBlue, shape: BoxShape.circle, boxShadow: [BoxShadow(color: previewColors.playerBlue.withOpacity(0.5), blurRadius: 4)])),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in a new issue