Auto-sync: 20260315_150001
This commit is contained in:
parent
fe8d54b3e1
commit
14c01e984c
11 changed files with 1032 additions and 931 deletions
|
|
@ -1396,6 +1396,8 @@ PODS:
|
|||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
- RecaptchaInterop (101.0.0)
|
||||
- share_plus (0.0.1):
|
||||
|
|
@ -1412,6 +1414,7 @@ DEPENDENCIES:
|
|||
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
|
||||
|
|
@ -1455,6 +1458,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
|
|
@ -1488,6 +1493,7 @@ SPEC CHECKSUMS:
|
|||
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
|
||||
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
|
|
|
|||
|
|
@ -3,24 +3,47 @@
|
|||
// ===========================================================================
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io' show Platform, HttpClient;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import '../core/app_colors.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart'; // <-- NUOVO IMPORT
|
||||
|
||||
class StorageService {
|
||||
static final StorageService instance = StorageService._internal();
|
||||
StorageService._internal();
|
||||
|
||||
late SharedPreferences _prefs;
|
||||
int _sessionStart = 0;
|
||||
|
||||
Future<void> init() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
_checkDailyQuests();
|
||||
_fetchLocationData();
|
||||
_sessionStart = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
// Doodle è il nuovo tema di partenza (index 0)
|
||||
// --- RECUPERO IP E CITTÀ IN BACKGROUND ---
|
||||
Future<void> _fetchLocationData() async {
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
final request = await HttpClient().getUrl(Uri.parse('http://ip-api.com/json/'));
|
||||
final response = await request.close();
|
||||
final responseBody = await response.transform(utf8.decoder).join();
|
||||
final data = jsonDecode(responseBody);
|
||||
await _prefs.setString('last_ip', data['query'] ?? 'Sconosciuto');
|
||||
await _prefs.setString('last_city', data['city'] ?? 'Sconosciuta');
|
||||
} catch (e) {
|
||||
debugPrint("Errore recupero IP: $e");
|
||||
}
|
||||
}
|
||||
|
||||
String get lastIp => _prefs.getString('last_ip') ?? 'Sconosciuto';
|
||||
String get lastCity => _prefs.getString('last_city') ?? 'Sconosciuta';
|
||||
// ------------------------------------------------
|
||||
|
||||
int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.doodle.index;
|
||||
Future<void> saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index);
|
||||
|
||||
|
|
@ -63,12 +86,44 @@ 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";
|
||||
|
||||
if (!kIsWeb) {
|
||||
if (Platform.isAndroid) currentPlatform = "Android";
|
||||
else if (Platform.isIOS) currentPlatform = "iOS";
|
||||
else if (Platform.isMacOS) currentPlatform = "macOS";
|
||||
else if (Platform.isWindows) currentPlatform = "Windows";
|
||||
|
||||
try {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
appVersion = "${packageInfo.version}+${packageInfo.buildNumber}";
|
||||
} catch(e) {
|
||||
debugPrint("Errore lettura versione: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// AGGIORNA IL TEMPO DI UTILIZZO
|
||||
if (_sessionStart != 0) {
|
||||
int now = DateTime.now().millisecondsSinceEpoch;
|
||||
int sessionSeconds = (now - _sessionStart) ~/ 1000;
|
||||
await _prefs.setInt('totalPlaytime', (_prefs.getInt('totalPlaytime') ?? 0) + sessionSeconds);
|
||||
_sessionStart = now; // resetta per la prossima misurazione
|
||||
}
|
||||
int totalPlaytime = _prefs.getInt('totalPlaytime') ?? 0;
|
||||
|
||||
await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({
|
||||
'name': playerName,
|
||||
'xp': totalXP,
|
||||
'level': playerLevel,
|
||||
'wins': wins,
|
||||
'lastActive': FieldValue.serverTimestamp(),
|
||||
'platform': currentPlatform,
|
||||
'ip': lastIp,
|
||||
'city': lastCity,
|
||||
'playtime': totalPlaytime,
|
||||
'appVersion': appVersion, // <-- NUOVO: Salva la versione
|
||||
}, SetOptions(merge: true));
|
||||
}
|
||||
} catch(e) {
|
||||
|
|
@ -77,7 +132,7 @@ class StorageService {
|
|||
}
|
||||
}
|
||||
|
||||
// --- NUOVO: GESTIONE PREFERITI (RUBRICA LOCALE) ---
|
||||
// --- GESTIONE PREFERITI (RUBRICA LOCALE) ---
|
||||
List<Map<String, String>> get favorites {
|
||||
List<String> favs = _prefs.getStringList('favorites') ?? [];
|
||||
return favs.map((e) => Map<String, String>.from(jsonDecode(e))).toList();
|
||||
|
|
@ -86,9 +141,9 @@ class StorageService {
|
|||
Future<void> toggleFavorite(String uid, String name) async {
|
||||
var favs = favorites;
|
||||
if (favs.any((f) => f['uid'] == uid)) {
|
||||
favs.removeWhere((f) => f['uid'] == uid); // Rimuove se esiste
|
||||
favs.removeWhere((f) => f['uid'] == uid);
|
||||
} else {
|
||||
favs.add({'uid': uid, 'name': name}); // Aggiunge se non esiste
|
||||
favs.add({'uid': uid, 'name': name});
|
||||
}
|
||||
await _prefs.setStringList('favorites', favs.map((e) => jsonEncode(e)).toList());
|
||||
}
|
||||
|
|
@ -96,7 +151,6 @@ class StorageService {
|
|||
bool isFavorite(String uid) {
|
||||
return favorites.any((f) => f['uid'] == uid);
|
||||
}
|
||||
// ---------------------------------------------------
|
||||
|
||||
void _checkDailyQuests() {
|
||||
String today = DateTime.now().toIso8601String().substring(0, 10);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ class AdminScreen extends StatelessWidget {
|
|||
elevation: 0,
|
||||
),
|
||||
body: StreamBuilder<QuerySnapshot>(
|
||||
// Ordiniamo per Ultimo Accesso, così i giocatori attivi di recente sono in cima!
|
||||
stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('lastActive', descending: true).snapshots(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
|
|
@ -34,7 +33,8 @@ class AdminScreen extends StatelessWidget {
|
|||
return Center(child: Text("Nessun giocatore trovato nel database.", style: TextStyle(color: theme.text)));
|
||||
}
|
||||
|
||||
var docs = snapshot.data!.docs;
|
||||
// Ripristinata la lista completa senza nascondere PAOLO
|
||||
final docs = snapshot.data!.docs;
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
|
@ -46,9 +46,17 @@ class AdminScreen extends StatelessWidget {
|
|||
int level = data['level'] ?? 1;
|
||||
int xp = data['xp'] ?? 0;
|
||||
int wins = data['wins'] ?? 0;
|
||||
String platform = data['platform'] ?? 'Sconosciuta';
|
||||
|
||||
// Formattazione Date (Se esistono)
|
||||
String platform = data['platform'] ?? 'Sconosciuta';
|
||||
String ip = data['ip'] ?? 'N/D';
|
||||
String city = data['city'] ?? 'N/D';
|
||||
|
||||
// --- CALCOLO TEMPO UTILIZZO (HH:MM) ---
|
||||
int playtimeSec = data['playtime'] ?? 0;
|
||||
int hours = playtimeSec ~/ 3600;
|
||||
int minutes = (playtimeSec % 3600) ~/ 60;
|
||||
String playtimeStr = "${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}";
|
||||
|
||||
DateTime? created;
|
||||
if (data['accountCreated'] != null) created = (data['accountCreated'] as Timestamp).toDate();
|
||||
|
||||
|
|
@ -59,8 +67,9 @@ class AdminScreen extends StatelessWidget {
|
|||
String lastActiveStr = lastActive != null ? DateFormat('dd MMM yyyy - HH:mm').format(lastActive) : 'N/D';
|
||||
|
||||
IconData platformIcon = Icons.device_unknown;
|
||||
if (platform == 'iOS') platformIcon = Icons.apple;
|
||||
if (platform == 'iOS' || platform == 'macOS') platformIcon = Icons.apple;
|
||||
if (platform == 'Android') platformIcon = Icons.android;
|
||||
if (platform == 'Windows') platformIcon = Icons.window;
|
||||
|
||||
return Card(
|
||||
color: theme.text.withOpacity(0.05),
|
||||
|
|
@ -79,17 +88,62 @@ class AdminScreen extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(name, style: TextStyle(color: theme.playerBlue, fontSize: 22, fontWeight: FontWeight.w900)),
|
||||
Icon(platformIcon, color: theme.text.withOpacity(0.7)),
|
||||
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
backgroundColor: theme.background,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
side: BorderSide(color: theme.playerBlue, width: 2),
|
||||
),
|
||||
title: Text("Info Connessione", style: TextStyle(color: theme.text, fontWeight: FontWeight.bold)),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("🌐 IP: $ip", style: TextStyle(color: theme.text, fontSize: 16)),
|
||||
const SizedBox(height: 10),
|
||||
Text("📍 Città: $city", style: TextStyle(color: theme.text, fontSize: 16)),
|
||||
const SizedBox(height: 10),
|
||||
Text("📱 OS: $platform", style: TextStyle(color: theme.text, fontSize: 16)),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text("CHIUDI", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold)),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.text.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(platformIcon, color: theme.text.withOpacity(0.8), size: 24),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Text("Liv. $level", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 16)),
|
||||
const SizedBox(width: 15),
|
||||
Text("$xp XP", style: TextStyle(color: theme.text.withOpacity(0.7))),
|
||||
const SizedBox(width: 15),
|
||||
Text("Vittorie: $wins", style: TextStyle(color: Colors.amber.shade700, fontWeight: FontWeight.bold)),
|
||||
Text("Liv. $level", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 14)),
|
||||
const SizedBox(width: 10),
|
||||
Text("$xp XP", style: TextStyle(color: theme.text.withOpacity(0.7), fontSize: 12)),
|
||||
const SizedBox(width: 10),
|
||||
Text("Vittorie: $wins", style: TextStyle(color: Colors.amber.shade700, fontWeight: FontWeight.bold, fontSize: 12)),
|
||||
const Spacer(),
|
||||
// --- TEMPO DI GIOCO ---
|
||||
Icon(Icons.timer, color: theme.text.withOpacity(0.6), size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(playtimeStr, style: TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 14)),
|
||||
],
|
||||
),
|
||||
const Padding(
|
||||
|
|
|
|||
|
|
@ -741,11 +741,105 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
// ===========================================================================
|
||||
// INTERFACCIA PRINCIPALE
|
||||
// ===========================================================================
|
||||
|
||||
BoxDecoration _glassBoxDecoration(ThemeColors theme, AppThemeType themeType) {
|
||||
return BoxDecoration(
|
||||
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
border: Border.all(
|
||||
color: themeType == AppThemeType.doodle ? theme.text : Colors.white.withOpacity(0.2),
|
||||
width: themeType == AppThemeType.doodle ? 2 : 1.5,
|
||||
),
|
||||
boxShadow: themeType == AppThemeType.doodle
|
||||
? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(4, 4), blurRadius: 0)]
|
||||
: [BoxShadow(color: Colors.black.withOpacity(0.2), blurRadius: 10)],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTopBar(BuildContext context, ThemeColors theme, AppThemeType themeType, String playerName, int playerLevel) {
|
||||
Color inkColor = const Color(0xFF111122);
|
||||
|
||||
return Padding(
|
||||
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(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: _glassBoxDecoration(theme, themeType),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 18,
|
||||
backgroundColor: theme.playerBlue.withOpacity(0.2),
|
||||
child: Icon(Icons.person, color: theme.playerBlue, size: 20),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(playerName.toUpperCase(), style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.bold, fontSize: 16))),
|
||||
Text("LIV. $playerLevel", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.8) : theme.playerBlue, fontWeight: FontWeight.bold, fontSize: 11))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// --- DESTRA: RIQUADRO STATISTICHE E AUDIO ---
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
decoration: _glassBoxDecoration(theme, themeType),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(themeType == AppThemeType.music ? FontAwesomeIcons.microphone : Icons.emoji_events, color: Colors.amber.shade600, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text("${StorageService.instance.wins}", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.w900))),
|
||||
const SizedBox(width: 12),
|
||||
Icon(themeType == AppThemeType.music ? FontAwesomeIcons.compactDisc : Icons.sentiment_very_dissatisfied, color: theme.playerRed.withOpacity(0.8), size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text("${StorageService.instance.losses}", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.w900))),
|
||||
|
||||
const SizedBox(width: 12),
|
||||
Container(width: 1, height: 20, color: (themeType == AppThemeType.doodle ? inkColor : Colors.white).withOpacity(0.2)),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
AnimatedBuilder(
|
||||
animation: AudioService.instance,
|
||||
builder: (context, child) {
|
||||
bool isMuted = AudioService.instance.isMuted;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
AudioService.instance.toggleMute();
|
||||
},
|
||||
child: Icon(
|
||||
isMuted ? Icons.volume_off : Icons.volume_up,
|
||||
color: isMuted ? theme.playerRed : (themeType == AppThemeType.doodle ? inkColor : theme.text),
|
||||
size: 20,
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCyberCard(Widget card, AppThemeType themeType) {
|
||||
if (themeType == AppThemeType.cyberpunk) return AnimatedCyberBorder(child: card);
|
||||
return card;
|
||||
|
|
@ -768,179 +862,28 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
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'; // Aggiunto Grimorio
|
||||
if (themeType == AppThemeType.grimorio) bgImage = 'assets/images/grimorio.jpg';
|
||||
|
||||
int wins = StorageService.instance.wins;
|
||||
int losses = StorageService.instance.losses;
|
||||
String playerName = StorageService.instance.playerName;
|
||||
if (playerName.isEmpty) playerName = "GUEST";
|
||||
|
||||
int level = StorageService.instance.playerLevel;
|
||||
int currentXP = StorageService.instance.totalXP;
|
||||
double xpProgress = (currentXP % 100) / 100.0;
|
||||
int playerLevel = StorageService.instance.playerLevel;
|
||||
|
||||
Widget uiContent = SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// --- NUOVO HEADER FISSATO ---
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// BLOCCO SINISTRO: AVATAR E NOME
|
||||
// 1. TOP BAR (Sempre visibile in alto)
|
||||
_buildTopBar(context, theme, themeType, playerName, playerLevel),
|
||||
|
||||
// 2. CONTENUTO SCORREVOLE (Logo + Bottoni)
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _showNameDialog,
|
||||
child: themeType == AppThemeType.doodle
|
||||
? CustomPaint(
|
||||
painter: DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: 1, isCircle: true),
|
||||
child: SizedBox(width: 50, height: 50, child: Icon(Icons.person, color: inkColor, size: 30)),
|
||||
)
|
||||
: SizedBox(
|
||||
width: 50, height: 50,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CircularProgressIndicator(value: xpProgress, color: theme.playerBlue, strokeWidth: 3, backgroundColor: theme.gridLine.withOpacity(0.2)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4))]),
|
||||
child: CircleAvatar(backgroundColor: theme.playerBlue.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerBlue, size: 26)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _showNameDialog,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(playerName, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontSize: 24, fontWeight: FontWeight.w900, letterSpacing: 1.5, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 2), blurRadius: 2)]))),
|
||||
Text("LIV. $level", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.8) : theme.playerBlue, fontSize: 14, fontWeight: FontWeight.bold, letterSpacing: 1))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// BLOCCO DESTRO: STATISTICHE E AUDIO
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HistoryScreen())),
|
||||
child: themeType == AppThemeType.doodle
|
||||
? Transform.rotate(
|
||||
angle: 0.04,
|
||||
child: CustomPaint(
|
||||
painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 2),
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.emoji_events, color: inkColor, size: 20), const SizedBox(width: 6),
|
||||
Text("$wins", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))), const SizedBox(width: 12),
|
||||
Icon(Icons.sentiment_very_dissatisfied, color: inkColor, size: 20), const SizedBox(width: 6),
|
||||
Text("$losses", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontWeight: FontWeight.w900))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [theme.text.withOpacity(0.15), theme.text.withOpacity(0.02)]),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.1), width: 1.5),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), offset: const Offset(2, 4), blurRadius: 8), BoxShadow(color: Colors.white.withOpacity(0.05), offset: const Offset(-1, -1), blurRadius: 2)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(themeType == AppThemeType.music ? FontAwesomeIcons.microphone : Icons.emoji_events, color: Colors.amber.shade600, size: 16), const SizedBox(width: 6),
|
||||
Text("$wins", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))), const SizedBox(width: 12),
|
||||
Icon(themeType == AppThemeType.music ? FontAwesomeIcons.compactDisc : Icons.sentiment_very_dissatisfied, color: theme.playerRed.withOpacity(0.8), size: 16), const SizedBox(width: 6),
|
||||
Text("$losses", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// PULSANTE AUDIO FISSATO A DESTRA
|
||||
AnimatedBuilder(
|
||||
animation: AudioService.instance,
|
||||
builder: (context, child) {
|
||||
bool isMuted = AudioService.instance.isMuted;
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
AudioService.instance.toggleMute();
|
||||
},
|
||||
child: themeType == AppThemeType.doodle
|
||||
? CustomPaint(
|
||||
painter: DoodleBackgroundPainter(fillColor: Colors.white, strokeColor: inkColor, seed: 99, isCircle: true),
|
||||
child: SizedBox(
|
||||
width: 45, height: 45,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isMuted ? Icons.volume_off : Icons.volume_up, color: inkColor, size: 18),
|
||||
Text(isMuted ? "OFF" : "ON", style: getSharedTextStyle(themeType, TextStyle(color: inkColor, fontSize: 10, fontWeight: FontWeight.w900))),
|
||||
],
|
||||
)
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
width: 45, height: 45,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.background.withOpacity(0.8),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: theme.gridLine.withOpacity(0.5), width: 1.5),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.3), blurRadius: 5, offset: const Offset(0, 4))],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isMuted ? Icons.volume_off : Icons.volume_up, color: theme.playerBlue, size: 16),
|
||||
Text(isMuted ? "OFF" : "ON", style: getSharedTextStyle(themeType, TextStyle(color: theme.text, fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
// --- FINE HEADER FISSATO ---
|
||||
|
||||
const Spacer(),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: Transform.rotate(
|
||||
angle: themeType == AppThemeType.doodle ? -0.04 : 0,
|
||||
|
|
@ -976,8 +919,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
),
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// --- MENU IN BASE AL TEMA ---
|
||||
if (themeType == AppThemeType.music) ...[
|
||||
|
|
@ -1027,21 +969,19 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
const SizedBox(height: 40), // Margine in fondo per assicurare che ci sia respiro
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0, iconTheme: IconThemeData(color: theme.text)),
|
||||
extendBodyBehindAppBar: true, // Rimosso l'AppBar vuoto che "spingeva" giù il SafeArea!
|
||||
body: Stack(
|
||||
children: [
|
||||
// 1. Sfondo base a tinta unita
|
||||
|
|
|
|||
|
|
@ -7,617 +7,16 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../logic/game_controller.dart';
|
||||
import '../../models/game_board.dart';
|
||||
import '../../core/theme_manager.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/app_colors.dart'; // L'import mancante!
|
||||
import '../../services/multiplayer_service.dart';
|
||||
import '../../services/storage_service.dart';
|
||||
import '../game/game_screen.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
TextStyle _getTextStyle(AppThemeType themeType, TextStyle baseStyle) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
return GoogleFonts.permanentMarker(textStyle: baseStyle);
|
||||
} else if (themeType == AppThemeType.arcade) {
|
||||
return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith(
|
||||
fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null,
|
||||
letterSpacing: 0.5,
|
||||
));
|
||||
} else if (themeType == AppThemeType.grimorio) {
|
||||
return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold));
|
||||
}
|
||||
return baseStyle;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// WIDGET INTERNI DI SUPPORTO (PULSANTI NEON)
|
||||
// ===========================================================================
|
||||
|
||||
class _NeonShapeButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
final bool isLocked;
|
||||
final bool isSpecial;
|
||||
|
||||
const _NeonShapeButton({
|
||||
required this.icon, required this.label, required this.isSelected,
|
||||
required this.theme, required this.themeType, required this.onTap,
|
||||
this.isLocked = false, this.isSpecial = false
|
||||
});
|
||||
|
||||
Color _getDoodleColor() {
|
||||
switch (label) {
|
||||
case 'Rombo': return Colors.blue.shade700;
|
||||
case 'Croce': return Colors.teal.shade700;
|
||||
case 'Buco': return Colors.pink.shade600;
|
||||
case 'Clessidra': return Colors.deepPurple.shade600;
|
||||
case 'Caos': return Colors.blueGrey.shade800;
|
||||
default: return Colors.blue.shade700;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = isLocked ? Colors.grey : _getDoodleColor();
|
||||
double tilt = (label.length % 2 == 0) ? -0.03 : 0.04;
|
||||
|
||||
return Transform.rotate(
|
||||
angle: tilt,
|
||||
child: GestureDetector(
|
||||
onTap: isLocked ? null : onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||
transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(15), topRight: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(6), bottomRight: Radius.circular(18),
|
||||
),
|
||||
border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: isSelected ? 2.5 : 1.5),
|
||||
boxShadow: isSelected
|
||||
? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)]
|
||||
: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(isLocked ? Icons.lock : icon, color: isSelected ? Colors.white : doodleColor, size: 20),
|
||||
const SizedBox(height: 2),
|
||||
Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 9, fontWeight: FontWeight.w900, letterSpacing: 0.2))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color mainColor = isSpecial && !isLocked ? Colors.purpleAccent : theme.playerBlue;
|
||||
return GestureDetector(
|
||||
onTap: isLocked ? null : onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isLocked
|
||||
? [Colors.grey.withOpacity(0.1), Colors.black.withOpacity(0.2)]
|
||||
: isSelected
|
||||
? [mainColor.withOpacity(0.3), mainColor.withOpacity(0.1)]
|
||||
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
|
||||
),
|
||||
border: Border.all(
|
||||
color: isLocked ? Colors.transparent : (isSelected ? mainColor : Colors.white.withOpacity(0.1)),
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
boxShadow: isLocked ? [] : isSelected
|
||||
? [BoxShadow(color: mainColor.withOpacity(0.5), blurRadius: 15, spreadRadius: 1, offset: const Offset(0, 0))]
|
||||
: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
|
||||
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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),
|
||||
Text(isLocked ? "Liv. 10" : label, style: _getTextStyle(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))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NeonSizeButton extends StatelessWidget {
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _NeonSizeButton({required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = label == 'MAX' ? Colors.red.shade700 : Colors.blueGrey.shade600;
|
||||
double tilt = (label == 'M' || label == 'MAX') ? 0.05 : -0.04;
|
||||
|
||||
return Transform.rotate(
|
||||
angle: tilt,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: 42, height: 40,
|
||||
transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.elliptical(25, 20)),
|
||||
border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: 2),
|
||||
boxShadow: isSelected
|
||||
? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)]
|
||||
: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 13, fontWeight: FontWeight.w900))),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
width: 42, height: 42,
|
||||
transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isSelected
|
||||
? [theme.playerRed.withOpacity(0.3), theme.playerRed.withOpacity(0.1)]
|
||||
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
|
||||
),
|
||||
border: Border.all(color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1), width: isSelected ? 2 : 1),
|
||||
boxShadow: isSelected
|
||||
? [BoxShadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 15, spreadRadius: 1)]
|
||||
: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
|
||||
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Text(label, style: _getTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NeonTimeSwitch extends StatelessWidget {
|
||||
final bool isTimeMode;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _NeonTimeSwitch({required this.isTimeMode, required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = Colors.orange.shade700;
|
||||
return Transform.rotate(
|
||||
angle: -0.015,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
transform: Matrix4.translationValues(0, isTimeMode ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isTimeMode ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8), topRight: Radius.circular(15),
|
||||
bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6),
|
||||
),
|
||||
border: Border.all(color: isTimeMode ? theme.text : doodleColor.withOpacity(0.5), width: 2.5),
|
||||
boxShadow: isTimeMode
|
||||
? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(4, 5), blurRadius: 0)]
|
||||
: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
|
||||
Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isTimeMode
|
||||
? [Colors.amber.withOpacity(0.25), Colors.amber.withOpacity(0.05)]
|
||||
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
|
||||
),
|
||||
border: Border.all(color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1), width: isTimeMode ? 2 : 1),
|
||||
boxShadow: isTimeMode
|
||||
? [BoxShadow(color: Colors.amber.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
|
||||
: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
|
||||
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))),
|
||||
Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: _getTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NeonPrivacySwitch extends StatelessWidget {
|
||||
final bool isPublic;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _NeonPrivacySwitch({required this.isPublic, required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600;
|
||||
return Transform.rotate(
|
||||
angle: 0.015,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isPublic ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(15), topRight: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15),
|
||||
),
|
||||
border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5),
|
||||
boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isPublic ? 'PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
|
||||
Text(isPublic ? 'In Bacheca' : 'Solo Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isPublic
|
||||
? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)]
|
||||
: [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)],
|
||||
),
|
||||
border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1),
|
||||
boxShadow: isPublic
|
||||
? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
|
||||
: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(isPublic ? 'STANZA PUBBLICA' : 'PRIVATA', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.white : theme.text.withOpacity(0.8), fontWeight: FontWeight.w900, fontSize: 10, letterSpacing: 1.0))),
|
||||
Text(isPublic ? 'Tutti ti vedono' : 'Solo con Codice', style: _getTextStyle(themeType, TextStyle(color: isPublic ? Colors.greenAccent.shade200 : theme.playerRed.withOpacity(0.7), fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- NUOVO WIDGET: BOTTONE INVITA PREFERITO ---
|
||||
class _NeonInviteFavoriteButton extends StatelessWidget {
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _NeonInviteFavoriteButton({required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = Colors.pink.shade600;
|
||||
return Transform.rotate(
|
||||
angle: -0.015,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8), topRight: Radius.circular(15),
|
||||
bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6),
|
||||
),
|
||||
border: Border.all(color: doodleColor.withOpacity(0.5), width: 2.5),
|
||||
boxShadow: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.favorite, color: doodleColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('PREFERITI', style: _getTextStyle(themeType, TextStyle(color: doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0))),
|
||||
Text('Invita amico', style: _getTextStyle(themeType, TextStyle(color: doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Colors.pinkAccent.withOpacity(0.25), Colors.pinkAccent.withOpacity(0.05)],
|
||||
),
|
||||
border: Border.all(color: Colors.pinkAccent, width: 1.5),
|
||||
boxShadow: [BoxShadow(color: Colors.pinkAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.favorite, color: Colors.pinkAccent, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('PREFERITI', style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5))),
|
||||
Text('Invita amico', style: _getTextStyle(themeType, TextStyle(color: Colors.pinkAccent.shade200, fontSize: 9, fontWeight: FontWeight.bold))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NeonActionButton extends StatelessWidget {
|
||||
final String label;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
|
||||
const _NeonActionButton({required this.label, required this.color, required this.onTap, required this.theme, required this.themeType});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02;
|
||||
return Transform.rotate(
|
||||
angle: tilt,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(10), topRight: Radius.circular(20),
|
||||
bottomLeft: Radius.circular(25), bottomRight: Radius.circular(10),
|
||||
),
|
||||
border: Border.all(color: theme.text, width: 3.0),
|
||||
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)],
|
||||
),
|
||||
child: Center(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [color.withOpacity(0.9), color.withOpacity(0.6)]),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.3), width: 1.5),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.5), offset: const Offset(4, 8), blurRadius: 12),
|
||||
BoxShadow(color: color.withOpacity(0.3), offset: const Offset(0, 0), blurRadius: 15, spreadRadius: 1),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Text(label, style: _getTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AnimatedCyberBorder extends StatefulWidget {
|
||||
final Widget child;
|
||||
const _AnimatedCyberBorder({required this.child});
|
||||
@override
|
||||
State<_AnimatedCyberBorder> createState() => _AnimatedCyberBorderState();
|
||||
}
|
||||
|
||||
class _AnimatedCyberBorderState extends State<_AnimatedCyberBorder> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
@override
|
||||
void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); }
|
||||
@override
|
||||
void dispose() { _controller.dispose(); super.dispose(); }
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<ThemeManager>().currentColors;
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: _CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 20, spreadRadius: 2)]),
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CyberBorderPainter extends CustomPainter {
|
||||
final double animationValue;
|
||||
final Color color1;
|
||||
final Color color2;
|
||||
|
||||
_CyberBorderPainter({required this.animationValue, required this.color1, required this.color2});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final rect = Offset.zero & size;
|
||||
final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(20));
|
||||
final Paint paint = Paint()
|
||||
..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.0
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 3);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// SCHERMATA LOBBY (PRINCIPALE)
|
||||
// ===========================================================================
|
||||
import '../../widgets/painters.dart';
|
||||
import 'lobby_widgets.dart'; // Importa i widget separati
|
||||
|
||||
class LobbyScreen extends StatefulWidget {
|
||||
final String? initialRoomCode;
|
||||
|
|
@ -702,7 +101,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
}
|
||||
}
|
||||
|
||||
// --- NUOVA LOGICA: CREA STANZA E INVITA IN UN COLPO SOLO ---
|
||||
Future<void> _createRoomAndInvite(String targetUid, String targetName) async {
|
||||
if (_isLoading) return;
|
||||
setState(() => _isLoading = true);
|
||||
|
|
@ -760,7 +158,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
|
||||
void _showError(String message) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message, style: const TextStyle(color: Colors.white)), backgroundColor: Colors.red)); }
|
||||
|
||||
// --- FINESTRA PREFERITI ---
|
||||
void _showFavoritesDialogForCreation() {
|
||||
final favs = StorageService.instance.favorites;
|
||||
|
||||
|
|
@ -776,7 +173,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
title: Text("I TUOI PREFERITI", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))),
|
||||
title: Text("I TUOI PREFERITI", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold))),
|
||||
content: Container(
|
||||
width: double.maxFinite,
|
||||
height: 300,
|
||||
|
|
@ -787,27 +184,27 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
child: favs.isEmpty
|
||||
? Center(child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Text("Non hai ancora aggiunto nessun preferito dalla Classifica!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6)))),
|
||||
child: Text("Non hai ancora aggiunto nessun preferito dalla Classifica!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6)))),
|
||||
))
|
||||
: ListView.builder(
|
||||
itemCount: favs.length,
|
||||
itemBuilder: (c, i) {
|
||||
return ListTile(
|
||||
title: Text(favs[i]['name']!, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontSize: 18, fontWeight: FontWeight.bold))),
|
||||
title: Text(favs[i]['name']!, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontSize: 18, fontWeight: FontWeight.bold))),
|
||||
trailing: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
_createRoomAndInvite(favs[i]['uid']!, favs[i]['name']!);
|
||||
},
|
||||
child: Text("SFIDA", style: _getTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
|
||||
child: Text("SFIDA", style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx), child: Text("CHIUDI", style: _getTextStyle(themeType, TextStyle(color: theme.playerRed))))
|
||||
TextButton(onPressed: () => Navigator.pop(ctx), child: Text("CHIUDI", style: getLobbyTextStyle(themeType, TextStyle(color: theme.playerRed))))
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
@ -826,8 +223,8 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CircularProgressIndicator(color: theme.playerRed), const SizedBox(height: 25),
|
||||
Text("CODICE STANZA", style: _getTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))),
|
||||
Text(code, style: _getTextStyle(themeType, TextStyle(fontSize: 40, fontWeight: FontWeight.w900, color: theme.playerRed, letterSpacing: 8, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 10)]))),
|
||||
Text("CODICE STANZA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: theme.text.withOpacity(0.6), letterSpacing: 2))),
|
||||
Text(code, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 40, fontWeight: FontWeight.w900, color: theme.playerRed, letterSpacing: 8, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 10)]))),
|
||||
const SizedBox(height: 25),
|
||||
Transform.rotate(
|
||||
angle: themeType == AppThemeType.doodle ? 0.02 : 0,
|
||||
|
|
@ -844,9 +241,9 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
child: Column(
|
||||
children: [
|
||||
Icon(_isPublicRoom ? Icons.podcasts : Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12),
|
||||
Text(_isPublicRoom ? "Sei in Bacheca!" : "Condividi link", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))),
|
||||
Text(_isPublicRoom ? "Sei in Bacheca!" : "Condividi link", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.w900, fontSize: 18))),
|
||||
const SizedBox(height: 8),
|
||||
Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))),
|
||||
Text(_isPublicRoom ? "Aspettiamo che uno sfidante si unisca dalla lobby pubblica." : "Condividi il codice. La partita inizierà appena si unirà.", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.8), fontSize: 14, height: 1.5))),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -855,7 +252,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
|
||||
if (themeType == AppThemeType.cyberpunk) {
|
||||
dialogContent = _AnimatedCyberBorder(child: dialogContent);
|
||||
dialogContent = AnimatedCyberBorder(child: dialogContent);
|
||||
} else {
|
||||
dialogContent = Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
|
|
@ -904,7 +301,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
_cleanupGhostRoom();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text("ANNULLA", style: _getTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))),
|
||||
child: Text("ANNULLA", style: getLobbyTextStyle(themeType, TextStyle(color: Colors.red, fontWeight: FontWeight.w900, fontSize: 20, letterSpacing: 2.0, shadows: themeType == AppThemeType.doodle ? [] : [const Shadow(color: Colors.black, blurRadius: 2)]))),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -927,7 +324,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
if (themeType == AppThemeType.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||
|
||||
bool isChaosUnlocked = StorageService.instance.playerLevel >= 10;
|
||||
bool isChaosUnlocked = true;
|
||||
|
||||
// --- PANNELLO IMPOSTAZIONI STANZA ---
|
||||
Widget hostPanel = Transform.rotate(
|
||||
|
|
@ -947,24 +344,24 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(child: Text("IMPOSTAZIONI STANZA", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))),
|
||||
Center(child: Text("IMPOSTAZIONI STANZA", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(fontSize: 12, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.6), letterSpacing: 2.0)))),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
Text("FORMA ARENA", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
Text("FORMA ARENA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(child: _NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: _selectedShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.classic))),
|
||||
Expanded(child: NeonShapeButton(icon: Icons.diamond_outlined, label: 'Rombo', isSelected: _selectedShape == ArenaShape.classic, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.classic))),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(child: _NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: _selectedShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.cross))),
|
||||
Expanded(child: NeonShapeButton(icon: Icons.add, label: 'Croce', isSelected: _selectedShape == ArenaShape.cross, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.cross))),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(child: _NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: _selectedShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.donut))),
|
||||
Expanded(child: NeonShapeButton(icon: Icons.donut_large, label: 'Buco', isSelected: _selectedShape == ArenaShape.donut, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.donut))),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(child: _NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: _selectedShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.hourglass))),
|
||||
Expanded(child: NeonShapeButton(icon: Icons.hourglass_bottom, label: 'Clessidra', isSelected: _selectedShape == ArenaShape.hourglass, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedShape = ArenaShape.hourglass))),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(child: _NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: _selectedShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setState(() => _selectedShape = ArenaShape.chaos))),
|
||||
Expanded(child: NeonShapeButton(icon: Icons.all_inclusive, label: 'Caos', isSelected: _selectedShape == ArenaShape.chaos, theme: theme, themeType: themeType, isSpecial: true, isLocked: !isChaosUnlocked, onTap: () => setState(() => _selectedShape = ArenaShape.chaos))),
|
||||
],
|
||||
),
|
||||
|
||||
|
|
@ -972,15 +369,15 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
Divider(color: themeType == AppThemeType.doodle ? theme.text.withOpacity(0.5) : Colors.white.withOpacity(0.05), thickness: themeType == AppThemeType.doodle ? 2.5 : 1.5),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Text("GRANDEZZA", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
Text("GRANDEZZA", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
_NeonSizeButton(label: 'S', isSelected: _selectedRadius == 3, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 3)),
|
||||
_NeonSizeButton(label: 'M', isSelected: _selectedRadius == 4, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 4)),
|
||||
_NeonSizeButton(label: 'L', isSelected: _selectedRadius == 5, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 5)),
|
||||
_NeonSizeButton(label: 'MAX', isSelected: _selectedRadius == 6, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 6)),
|
||||
NeonSizeButton(label: 'S', isSelected: _selectedRadius == 3, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 3)),
|
||||
NeonSizeButton(label: 'M', isSelected: _selectedRadius == 4, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 4)),
|
||||
NeonSizeButton(label: 'L', isSelected: _selectedRadius == 5, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 5)),
|
||||
NeonSizeButton(label: 'MAX', isSelected: _selectedRadius == 6, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 6)),
|
||||
],
|
||||
),
|
||||
|
||||
|
|
@ -988,23 +385,21 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
Divider(color: themeType == AppThemeType.doodle ? theme.text.withOpacity(0.5) : Colors.white.withOpacity(0.05), thickness: themeType == AppThemeType.doodle ? 2.5 : 1.5),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Text("TEMPO E OPZIONI", style: _getTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
Text("TEMPO E OPZIONI", style: getLobbyTextStyle(themeType, TextStyle(fontSize: 10, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// RIGA 1: TEMPO (OCCUPA TUTTO LO SPAZIO)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode))),
|
||||
Expanded(child: NeonTimeSwitch(isTimeMode: _isTimeMode, theme: theme, themeType: themeType, onTap: () => setState(() => _isTimeMode = !_isTimeMode))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// RIGA 2: VISIBILITÀ E INVITI
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _NeonPrivacySwitch(isPublic: _isPublicRoom, theme: theme, themeType: themeType, onTap: () => setState(() => _isPublicRoom = !_isPublicRoom))),
|
||||
Expanded(child: NeonPrivacySwitch(isPublic: _isPublicRoom, theme: theme, themeType: themeType, onTap: () => setState(() => _isPublicRoom = !_isPublicRoom))),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(child: _NeonInviteFavoriteButton(theme: theme, themeType: themeType, onTap: _showFavoritesDialogForCreation)),
|
||||
Expanded(child: NeonInviteFavoriteButton(theme: theme, themeType: themeType, onTap: _showFavoritesDialogForCreation)),
|
||||
],
|
||||
)
|
||||
],
|
||||
|
|
@ -1013,7 +408,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
|
||||
if (themeType == AppThemeType.cyberpunk) {
|
||||
hostPanel = _AnimatedCyberBorder(child: hostPanel);
|
||||
hostPanel = AnimatedCyberBorder(child: hostPanel);
|
||||
}
|
||||
|
||||
Widget uiContent = SafeArea(
|
||||
|
|
@ -1030,7 +425,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
Expanded(
|
||||
child: Text("MULTIPLAYER", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(fontSize: 20, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 2))),
|
||||
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
|
||||
],
|
||||
|
|
@ -1050,11 +445,11 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType),
|
||||
child: NeonActionButton(label: "AVVIA", color: theme.playerRed, onTap: _createRoom, theme: theme, themeType: themeType),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: _NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType),
|
||||
child: NeonActionButton(label: "ANNULLA", color: Colors.grey.shade600, onTap: () => setState(() => _isCreatingRoom = false), theme: theme, themeType: themeType),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -1063,12 +458,12 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType),
|
||||
NeonActionButton(label: "CREA PARTITA", color: theme.playerRed, onTap: () { FocusScope.of(context).unfocus(); setState(() => _isCreatingRoom = true); }, theme: theme, themeType: themeType),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("OPPURE", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("OPPURE", style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
|
||||
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
||||
],
|
||||
),
|
||||
|
|
@ -1087,10 +482,10 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
child: TextField(
|
||||
controller: _codeController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
|
||||
style: _getTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])),
|
||||
style: getLobbyTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: theme.text, letterSpacing: 12, shadows: themeType == AppThemeType.doodle ? [] : [Shadow(color: theme.playerBlue.withOpacity(0.5), blurRadius: 8)])),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
||||
hintText: "CODICE", hintStyle: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "",
|
||||
hintText: "CODICE", hintStyle: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.3), letterSpacing: 10, fontSize: 20)), counterText: "",
|
||||
filled: themeType != AppThemeType.doodle,
|
||||
fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05),
|
||||
enabledBorder: themeType == AppThemeType.doodle ? InputBorder.none : OutlineInputBorder(borderSide: BorderSide(color: theme.gridLine.withOpacity(0.5), width: 2.0), borderRadius: BorderRadius.circular(15)),
|
||||
|
|
@ -1100,7 +495,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
_NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType),
|
||||
NeonActionButton(label: "UNISCITI", color: theme.playerBlue, onTap: () => _joinRoomByCode(_codeController.text), theme: theme, themeType: themeType),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -1109,13 +504,12 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
Row(
|
||||
children: [
|
||||
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("LOBBY PUBBLICA", style: _getTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 10), child: Text("LOBBY PUBBLICA", style: getLobbyTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? theme.text : theme.text.withOpacity(0.5), fontWeight: FontWeight.bold, letterSpacing: 2.0, fontSize: 13)))),
|
||||
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
|
||||
// --- LA VERA E PROPRIA BACHECA PUBBLICA ---
|
||||
StreamBuilder<QuerySnapshot>(
|
||||
stream: _multiplayerService.getPublicRooms(),
|
||||
builder: (context, snapshot) {
|
||||
|
|
@ -1125,8 +519,8 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
|
||||
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))),
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0),
|
||||
child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1151,8 +545,8 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
|
||||
if (docs.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
||||
child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5)))),
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0),
|
||||
child: Center(child: Text("Nessuna stanza pubblica al momento.\nCreane una tu!", textAlign: TextAlign.center, style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), height: 1.5, fontSize: 16)))),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1185,8 +579,8 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
return Transform.rotate(
|
||||
angle: themeType == AppThemeType.doodle ? (index % 2 == 0 ? 0.01 : -0.01) : 0,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 15),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
|
|
@ -1195,27 +589,28 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed)),
|
||||
const SizedBox(width: 12),
|
||||
CircleAvatar(radius: 25, backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed, size: 28)),
|
||||
const SizedBox(width: 15),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Stanza di $host", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 16))),
|
||||
const SizedBox(height: 4),
|
||||
Text("Raggio: $r • $prettyShape • ${time ? 'A Tempo' : 'Relax'}", style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 11))),
|
||||
Text("Stanza di $host", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 18))),
|
||||
const SizedBox(height: 6),
|
||||
Text("Raggio: $r • $prettyShape • ${time ? 'A Tempo' : 'Relax'}", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 12))),
|
||||
],
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
backgroundColor: theme.playerBlue, foregroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
elevation: themeType == AppThemeType.doodle ? 0 : 2,
|
||||
side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 2) : BorderSide.none,
|
||||
),
|
||||
onPressed: () => _joinRoomByCode(doc.id),
|
||||
child: Text("ENTRA", style: _getTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))),
|
||||
child: Text("ENTRA", style: getLobbyTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.0))),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
@ -1225,7 +620,6 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
|||
);
|
||||
}
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
616
lib/ui/multiplayer/lobby_widgets.dart
Normal file
616
lib/ui/multiplayer/lobby_widgets.dart
Normal file
|
|
@ -0,0 +1,616 @@
|
|||
// ===========================================================================
|
||||
// FILE: lib/ui/multiplayer/lobby_widgets.dart
|
||||
// ===========================================================================
|
||||
|
||||
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';
|
||||
|
||||
TextStyle getLobbyTextStyle(AppThemeType themeType, TextStyle baseStyle) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
return GoogleFonts.permanentMarker(textStyle: baseStyle);
|
||||
} else if (themeType == AppThemeType.arcade) {
|
||||
return GoogleFonts.pressStart2p(textStyle: baseStyle.copyWith(
|
||||
fontSize: baseStyle.fontSize != null ? baseStyle.fontSize! * 0.75 : null,
|
||||
letterSpacing: 0.5,
|
||||
));
|
||||
} else if (themeType == AppThemeType.grimorio) {
|
||||
return GoogleFonts.cinzelDecorative(textStyle: baseStyle.copyWith(fontWeight: FontWeight.bold));
|
||||
} else if (themeType == AppThemeType.music) {
|
||||
return GoogleFonts.audiowide(textStyle: baseStyle.copyWith(letterSpacing: 1.5));
|
||||
}
|
||||
return baseStyle;
|
||||
}
|
||||
|
||||
class NeonShapeButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
final bool isLocked;
|
||||
final bool isSpecial;
|
||||
|
||||
const NeonShapeButton({
|
||||
super.key, required this.icon, required this.label, required this.isSelected,
|
||||
required this.theme, required this.themeType, required this.onTap,
|
||||
this.isLocked = false, this.isSpecial = false
|
||||
});
|
||||
|
||||
Color _getDoodleColor() {
|
||||
switch (label) {
|
||||
case 'Rombo': return Colors.blue.shade700;
|
||||
case 'Croce': return Colors.teal.shade700;
|
||||
case 'Buco': return Colors.pink.shade600;
|
||||
case 'Clessidra': return Colors.deepPurple.shade600;
|
||||
case 'Caos': return Colors.blueGrey.shade800;
|
||||
default: return Colors.blue.shade700;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = isLocked ? Colors.grey : _getDoodleColor();
|
||||
double tilt = (label.length % 2 == 0) ? -0.03 : 0.04;
|
||||
|
||||
return Transform.rotate(
|
||||
angle: tilt,
|
||||
child: GestureDetector(
|
||||
onTap: isLocked ? null : onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||
transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(15), topRight: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(6), bottomRight: Radius.circular(18),
|
||||
),
|
||||
border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: isSelected ? 2.5 : 1.5),
|
||||
boxShadow: isSelected
|
||||
? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)]
|
||||
: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color mainColor = isSpecial && !isLocked ? Colors.purpleAccent : theme.playerBlue;
|
||||
return GestureDetector(
|
||||
onTap: isLocked ? null : onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||
transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isLocked
|
||||
? [Colors.grey.withOpacity(0.1), Colors.black.withOpacity(0.2)]
|
||||
: isSelected
|
||||
? [mainColor.withOpacity(0.3), mainColor.withOpacity(0.1)]
|
||||
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
|
||||
),
|
||||
border: Border.all(
|
||||
color: isLocked ? Colors.transparent : (isSelected ? mainColor : Colors.white.withOpacity(0.1)),
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
boxShadow: isLocked ? [] : isSelected
|
||||
? [BoxShadow(color: mainColor.withOpacity(0.5), blurRadius: 15, spreadRadius: 1, offset: const Offset(0, 0))]
|
||||
: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
|
||||
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
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)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NeonSizeButton extends StatelessWidget {
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const NeonSizeButton({super.key, required this.label, required this.isSelected, required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = label == 'MAX' ? Colors.red.shade700 : Colors.blueGrey.shade600;
|
||||
double tilt = (label == 'M' || label == 'MAX') ? 0.05 : -0.04;
|
||||
|
||||
return Transform.rotate(
|
||||
angle: tilt,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
width: 42, height: 40,
|
||||
transform: Matrix4.translationValues(0, isSelected ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.elliptical(25, 20)),
|
||||
border: Border.all(color: isSelected ? theme.text : doodleColor.withOpacity(0.5), width: 2),
|
||||
boxShadow: isSelected
|
||||
? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(3, 4), blurRadius: 0)]
|
||||
: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)],
|
||||
),
|
||||
child: Center(
|
||||
child: FittedBox(fit: BoxFit.scaleDown, child: Text(label, style: getLobbyTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : doodleColor, fontSize: 13, fontWeight: FontWeight.w900)))),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
width: 42, height: 42,
|
||||
transform: Matrix4.translationValues(0, isSelected ? 2 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isSelected
|
||||
? [theme.playerRed.withOpacity(0.3), theme.playerRed.withOpacity(0.1)]
|
||||
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
|
||||
),
|
||||
border: Border.all(color: isSelected ? theme.playerRed : Colors.white.withOpacity(0.1), width: isSelected ? 2 : 1),
|
||||
boxShadow: isSelected
|
||||
? [BoxShadow(color: theme.playerRed.withOpacity(0.5), blurRadius: 15, spreadRadius: 1)]
|
||||
: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
|
||||
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: FittedBox(fit: BoxFit.scaleDown, child: Text(label, style: getLobbyTextStyle(themeType, TextStyle(color: isSelected ? Colors.white : theme.text.withOpacity(0.6), fontSize: 12, fontWeight: isSelected ? FontWeight.w900 : FontWeight.bold)))),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NeonTimeSwitch extends StatelessWidget {
|
||||
final bool isTimeMode;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const NeonTimeSwitch({super.key, required this.isTimeMode, required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = Colors.orange.shade700;
|
||||
return Transform.rotate(
|
||||
angle: -0.015,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
transform: Matrix4.translationValues(0, isTimeMode ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isTimeMode ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8), topRight: Radius.circular(15),
|
||||
bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6),
|
||||
),
|
||||
border: Border.all(color: isTimeMode ? theme.text : doodleColor.withOpacity(0.5), width: 2.5),
|
||||
boxShadow: isTimeMode
|
||||
? [BoxShadow(color: theme.text.withOpacity(0.8), offset: const Offset(4, 5), blurRadius: 0)]
|
||||
: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(2, 2), blurRadius: 0)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.white : doodleColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isTimeMode
|
||||
? [Colors.amber.withOpacity(0.25), Colors.amber.withOpacity(0.05)]
|
||||
: [theme.text.withOpacity(0.1), theme.text.withOpacity(0.02)],
|
||||
),
|
||||
border: Border.all(color: isTimeMode ? Colors.amber : Colors.white.withOpacity(0.1), width: isTimeMode ? 2 : 1),
|
||||
boxShadow: isTimeMode
|
||||
? [BoxShadow(color: Colors.amber.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
|
||||
: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4)),
|
||||
BoxShadow(color: Colors.white.withOpacity(0.05), blurRadius: 2, offset: const Offset(-1, -1)),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isTimeMode ? Icons.timer : Icons.timer_off, color: isTimeMode ? Colors.amber : theme.text.withOpacity(0.5), size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? 'A TEMPO' : 'RELAX', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.white : theme.text.withOpacity(0.5), fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text(isTimeMode ? '15s a mossa' : 'Senza limiti', style: getLobbyTextStyle(themeType, TextStyle(color: isTimeMode ? Colors.amber.shade200 : theme.text.withOpacity(0.4), fontSize: 9, fontWeight: FontWeight.bold)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NeonPrivacySwitch extends StatelessWidget {
|
||||
final bool isPublic;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const NeonPrivacySwitch({super.key, required this.isPublic, required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = isPublic ? Colors.green.shade600 : Colors.red.shade600;
|
||||
return Transform.rotate(
|
||||
angle: 0.015,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
transform: Matrix4.translationValues(0, isPublic ? 3 : 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: isPublic ? doodleColor : Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(15), topRight: Radius.circular(8),
|
||||
bottomLeft: Radius.circular(6), bottomRight: Radius.circular(15),
|
||||
),
|
||||
border: Border.all(color: isPublic ? theme.text : doodleColor.withOpacity(0.5), width: 2.5),
|
||||
boxShadow: [BoxShadow(color: isPublic ? theme.text.withOpacity(0.8) : doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.white : doodleColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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 : doodleColor, 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.white : doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: isPublic
|
||||
? [Colors.greenAccent.withOpacity(0.25), Colors.greenAccent.withOpacity(0.05)]
|
||||
: [theme.playerRed.withOpacity(0.25), theme.playerRed.withOpacity(0.05)],
|
||||
),
|
||||
border: Border.all(color: isPublic ? Colors.greenAccent : theme.playerRed, width: isPublic ? 2 : 1),
|
||||
boxShadow: isPublic
|
||||
? [BoxShadow(color: Colors.greenAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)]
|
||||
: [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 6, offset: const Offset(2, 4))],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(isPublic ? Icons.public : Icons.lock, color: isPublic ? Colors.greenAccent : theme.playerRed, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NeonInviteFavoriteButton extends StatelessWidget {
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const NeonInviteFavoriteButton({super.key, required this.theme, required this.themeType, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
Color doodleColor = Colors.pink.shade600;
|
||||
return Transform.rotate(
|
||||
angle: -0.015,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(8), topRight: Radius.circular(15),
|
||||
bottomLeft: Radius.circular(15), bottomRight: Radius.circular(6),
|
||||
),
|
||||
border: Border.all(color: doodleColor.withOpacity(0.5), width: 2.5),
|
||||
boxShadow: [BoxShadow(color: doodleColor.withOpacity(0.2), offset: const Offset(4, 5), blurRadius: 0)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.favorite, color: doodleColor, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('PREFERITI', style: getLobbyTextStyle(themeType, TextStyle(color: doodleColor, fontWeight: FontWeight.w900, fontSize: 12, letterSpacing: 1.0)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('Invita amico', style: getLobbyTextStyle(themeType, TextStyle(color: doodleColor.withOpacity(0.8), fontSize: 9, fontWeight: FontWeight.bold)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Colors.pinkAccent.withOpacity(0.25), Colors.pinkAccent.withOpacity(0.05)],
|
||||
),
|
||||
border: Border.all(color: Colors.pinkAccent, width: 1.5),
|
||||
boxShadow: [BoxShadow(color: Colors.pinkAccent.withOpacity(0.3), blurRadius: 15, spreadRadius: 2)],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.favorite, color: Colors.pinkAccent, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('PREFERITI', style: getLobbyTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 11, letterSpacing: 1.5)))),
|
||||
FittedBox(fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text('Invita amico', style: getLobbyTextStyle(themeType, TextStyle(color: Colors.pinkAccent.shade200, fontSize: 9, fontWeight: FontWeight.bold)))),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NeonActionButton extends StatelessWidget {
|
||||
final String label;
|
||||
final Color color;
|
||||
final VoidCallback onTap;
|
||||
final ThemeColors theme;
|
||||
final AppThemeType themeType;
|
||||
|
||||
const NeonActionButton({super.key, required this.label, required this.color, required this.onTap, required this.theme, required this.themeType});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (themeType == AppThemeType.doodle) {
|
||||
double tilt = (label == "UNISCITI" || label == "ANNULLA") ? -0.015 : 0.02;
|
||||
return Transform.rotate(
|
||||
angle: tilt,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(10), topRight: Radius.circular(20),
|
||||
bottomLeft: Radius.circular(25), bottomRight: Radius.circular(10),
|
||||
),
|
||||
border: Border.all(color: theme.text, width: 3.0),
|
||||
boxShadow: [BoxShadow(color: theme.text.withOpacity(0.9), offset: const Offset(4, 4), blurRadius: 0)],
|
||||
),
|
||||
child: Center(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Text(label, style: getLobbyTextStyle(themeType, const TextStyle(fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 3.0, color: Colors.white))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [color.withOpacity(0.9), color.withOpacity(0.6)]),
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.3), width: 1.5),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.5), offset: const Offset(4, 8), blurRadius: 12),
|
||||
BoxShadow(color: color.withOpacity(0.3), offset: const Offset(0, 0), blurRadius: 15, spreadRadius: 1),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0),
|
||||
child: Text(label, style: getLobbyTextStyle(themeType, const TextStyle(fontSize: 16, fontWeight: FontWeight.w900, letterSpacing: 2.0, color: Colors.white, shadows: [Shadow(color: Colors.black, blurRadius: 2, offset: Offset(1, 1))]))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedCyberBorder extends StatefulWidget {
|
||||
final Widget child;
|
||||
const AnimatedCyberBorder({super.key, required this.child});
|
||||
@override
|
||||
State<AnimatedCyberBorder> createState() => _AnimatedCyberBorderState();
|
||||
}
|
||||
|
||||
class _AnimatedCyberBorderState extends State<AnimatedCyberBorder> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
@override
|
||||
void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: const Duration(seconds: 3))..repeat(); }
|
||||
@override
|
||||
void dispose() { _controller.dispose(); super.dispose(); }
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<ThemeManager>().currentColors;
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: _CyberBorderPainter(animationValue: _controller.value, color1: theme.playerBlue, color2: theme.playerRed),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.15), blurRadius: 20, spreadRadius: 2)]),
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CyberBorderPainter extends CustomPainter {
|
||||
final double animationValue;
|
||||
final Color color1;
|
||||
final Color color2;
|
||||
|
||||
_CyberBorderPainter({required this.animationValue, required this.color1, required this.color2});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final rect = Offset.zero & size;
|
||||
final RRect rrect = RRect.fromRectAndRadius(rect, const Radius.circular(20));
|
||||
final Paint paint = Paint()
|
||||
..shader = SweepGradient(colors: [color1, color2, color1, color2, color1], stops: const [0.0, 0.25, 0.5, 0.75, 1.0], transform: GradientRotation(animationValue * 2 * math.pi)).createShader(rect)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3.0
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.solid, 3);
|
||||
canvas.drawRRect(rrect, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _CyberBorderPainter oldDelegate) => oldDelegate.animationValue != animationValue;
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import cloud_firestore
|
|||
import firebase_app_check
|
||||
import firebase_auth
|
||||
import firebase_core
|
||||
import package_info_plus
|
||||
import share_plus
|
||||
import shared_preferences_foundation
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
|
||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1401,6 +1401,8 @@ PODS:
|
|||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.4.0)
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
|
@ -1416,6 +1418,7 @@ DEPENDENCIES:
|
|||
- firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
|
||||
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
|
||||
|
|
@ -1458,6 +1461,8 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
share_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
shared_preferences_foundation:
|
||||
|
|
@ -1491,6 +1496,7 @@ SPEC CHECKSUMS:
|
|||
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
|
||||
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
|
|
|
|||
|
|
@ -6,7 +6,19 @@ class MainFlutterWindow: NSWindow {
|
|||
let flutterViewController = FlutterViewController()
|
||||
let windowFrame = self.frame
|
||||
self.contentViewController = flutterViewController
|
||||
self.setFrame(windowFrame, display: true)
|
||||
|
||||
// 1. Definiamo le proporzioni esatte da smartphone
|
||||
let phoneSize = NSSize(width: 400, height: 800)
|
||||
let newRect = NSRect(origin: windowFrame.origin, size: phoneSize)
|
||||
self.setFrame(newRect, display: true)
|
||||
|
||||
// 2. Blocchiamo il ridimensionamento! Il Mac non potrà più allargarla a dismisura
|
||||
self.minSize = phoneSize
|
||||
self.maxSize = phoneSize
|
||||
|
||||
// 3. IL TRUCCO MAGICO: Cambiamo il nome del salvataggio automatico.
|
||||
// Questo costringe macOS a dimenticare la vecchia finestra larga e usare questa nuova.
|
||||
self.setFrameAutosaveName("TetraQMobileSimulatorWindow")
|
||||
|
||||
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||
|
||||
|
|
|
|||
16
pubspec.lock
16
pubspec.lock
|
|
@ -557,6 +557,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.0.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ dependencies:
|
|||
google_fonts: ^8.0.2
|
||||
font_awesome_flutter: ^10.12.0
|
||||
firebase_app_check: ^0.4.1+5
|
||||
package_info_plus: ^9.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Reference in a new issue