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/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- package_info_plus (0.4.5):
|
||||||
|
- Flutter
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- RecaptchaInterop (101.0.0)
|
- RecaptchaInterop (101.0.0)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
|
|
@ -1412,6 +1414,7 @@ DEPENDENCIES:
|
||||||
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
|
|
||||||
|
|
@ -1455,6 +1458,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
package_info_plus:
|
||||||
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
|
|
@ -1488,6 +1493,7 @@ SPEC CHECKSUMS:
|
||||||
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
|
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
|
||||||
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
|
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
|
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,47 @@
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io' show Platform, HttpClient;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import '../core/app_colors.dart';
|
import '../core/app_colors.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart'; // <-- NUOVO IMPORT
|
||||||
|
|
||||||
class StorageService {
|
class StorageService {
|
||||||
static final StorageService instance = StorageService._internal();
|
static final StorageService instance = StorageService._internal();
|
||||||
StorageService._internal();
|
StorageService._internal();
|
||||||
|
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
|
int _sessionStart = 0;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
_checkDailyQuests();
|
_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;
|
int get savedThemeIndex => _prefs.getInt('theme') ?? AppThemeType.doodle.index;
|
||||||
Future<void> saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index);
|
Future<void> saveTheme(AppThemeType theme) async => await _prefs.setInt('theme', theme.index);
|
||||||
|
|
||||||
|
|
@ -63,12 +86,44 @@ class StorageService {
|
||||||
final user = FirebaseAuth.instance.currentUser;
|
final user = FirebaseAuth.instance.currentUser;
|
||||||
|
|
||||||
if (user != null) {
|
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({
|
await FirebaseFirestore.instance.collection('leaderboard').doc(user.uid).set({
|
||||||
'name': playerName,
|
'name': playerName,
|
||||||
'xp': totalXP,
|
'xp': totalXP,
|
||||||
'level': playerLevel,
|
'level': playerLevel,
|
||||||
'wins': wins,
|
'wins': wins,
|
||||||
'lastActive': FieldValue.serverTimestamp(),
|
'lastActive': FieldValue.serverTimestamp(),
|
||||||
|
'platform': currentPlatform,
|
||||||
|
'ip': lastIp,
|
||||||
|
'city': lastCity,
|
||||||
|
'playtime': totalPlaytime,
|
||||||
|
'appVersion': appVersion, // <-- NUOVO: Salva la versione
|
||||||
}, SetOptions(merge: true));
|
}, SetOptions(merge: true));
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
|
@ -77,7 +132,7 @@ class StorageService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NUOVO: GESTIONE PREFERITI (RUBRICA LOCALE) ---
|
// --- GESTIONE PREFERITI (RUBRICA LOCALE) ---
|
||||||
List<Map<String, String>> get favorites {
|
List<Map<String, String>> get favorites {
|
||||||
List<String> favs = _prefs.getStringList('favorites') ?? [];
|
List<String> favs = _prefs.getStringList('favorites') ?? [];
|
||||||
return favs.map((e) => Map<String, String>.from(jsonDecode(e))).toList();
|
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 {
|
Future<void> toggleFavorite(String uid, String name) async {
|
||||||
var favs = favorites;
|
var favs = favorites;
|
||||||
if (favs.any((f) => f['uid'] == uid)) {
|
if (favs.any((f) => f['uid'] == uid)) {
|
||||||
favs.removeWhere((f) => f['uid'] == uid); // Rimuove se esiste
|
favs.removeWhere((f) => f['uid'] == uid);
|
||||||
} else {
|
} 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());
|
await _prefs.setStringList('favorites', favs.map((e) => jsonEncode(e)).toList());
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +151,6 @@ class StorageService {
|
||||||
bool isFavorite(String uid) {
|
bool isFavorite(String uid) {
|
||||||
return favorites.any((f) => f['uid'] == uid);
|
return favorites.any((f) => f['uid'] == uid);
|
||||||
}
|
}
|
||||||
// ---------------------------------------------------
|
|
||||||
|
|
||||||
void _checkDailyQuests() {
|
void _checkDailyQuests() {
|
||||||
String today = DateTime.now().toIso8601String().substring(0, 10);
|
String today = DateTime.now().toIso8601String().substring(0, 10);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ class AdminScreen extends StatelessWidget {
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
body: StreamBuilder<QuerySnapshot>(
|
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(),
|
stream: FirebaseFirestore.instance.collection('leaderboard').orderBy('lastActive', descending: true).snapshots(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
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)));
|
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(
|
return ListView.builder(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
@ -46,9 +46,17 @@ class AdminScreen extends StatelessWidget {
|
||||||
int level = data['level'] ?? 1;
|
int level = data['level'] ?? 1;
|
||||||
int xp = data['xp'] ?? 0;
|
int xp = data['xp'] ?? 0;
|
||||||
int wins = data['wins'] ?? 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;
|
DateTime? created;
|
||||||
if (data['accountCreated'] != null) created = (data['accountCreated'] as Timestamp).toDate();
|
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';
|
String lastActiveStr = lastActive != null ? DateFormat('dd MMM yyyy - HH:mm').format(lastActive) : 'N/D';
|
||||||
|
|
||||||
IconData platformIcon = Icons.device_unknown;
|
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 == 'Android') platformIcon = Icons.android;
|
||||||
|
if (platform == 'Windows') platformIcon = Icons.window;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
color: theme.text.withOpacity(0.05),
|
color: theme.text.withOpacity(0.05),
|
||||||
|
|
@ -79,17 +88,62 @@ class AdminScreen extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(name, style: TextStyle(color: theme.playerBlue, fontSize: 22, fontWeight: FontWeight.w900)),
|
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),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text("Liv. $level", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 16)),
|
Text("Liv. $level", style: TextStyle(color: theme.playerRed, fontWeight: FontWeight.bold, fontSize: 14)),
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 10),
|
||||||
Text("$xp XP", style: TextStyle(color: theme.text.withOpacity(0.7))),
|
Text("$xp XP", style: TextStyle(color: theme.text.withOpacity(0.7), fontSize: 12)),
|
||||||
const SizedBox(width: 15),
|
const SizedBox(width: 10),
|
||||||
Text("Vittorie: $wins", style: TextStyle(color: Colors.amber.shade700, fontWeight: FontWeight.bold)),
|
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(
|
const Padding(
|
||||||
|
|
|
||||||
|
|
@ -741,11 +741,105 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
// INTERFACCIA PRINCIPALE
|
// 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) {
|
Widget _buildCyberCard(Widget card, AppThemeType themeType) {
|
||||||
if (themeType == AppThemeType.cyberpunk) return AnimatedCyberBorder(child: card);
|
if (themeType == AppThemeType.cyberpunk) return AnimatedCyberBorder(child: card);
|
||||||
return card;
|
return card;
|
||||||
|
|
@ -768,280 +862,126 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
||||||
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||||
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
|
if (themeType == AppThemeType.music) bgImage = 'assets/images/music_bg.jpg';
|
||||||
if (themeType == AppThemeType.arcade) bgImage = 'assets/images/arcade.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;
|
String playerName = StorageService.instance.playerName;
|
||||||
if (playerName.isEmpty) playerName = "GUEST";
|
if (playerName.isEmpty) playerName = "GUEST";
|
||||||
|
int playerLevel = StorageService.instance.playerLevel;
|
||||||
int level = StorageService.instance.playerLevel;
|
|
||||||
int currentXP = StorageService.instance.totalXP;
|
|
||||||
double xpProgress = (currentXP % 100) / 100.0;
|
|
||||||
|
|
||||||
Widget uiContent = SafeArea(
|
Widget uiContent = SafeArea(
|
||||||
child: LayoutBuilder(
|
child: Column(
|
||||||
builder: (context, constraints) {
|
children: [
|
||||||
return SingleChildScrollView(
|
// 1. TOP BAR (Sempre visibile in alto)
|
||||||
physics: const BouncingScrollPhysics(),
|
_buildTopBar(context, theme, themeType, playerName, playerLevel),
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
// 2. CONTENUTO SCORREVOLE (Logo + Bottoni)
|
||||||
child: IntrinsicHeight(
|
Expanded(
|
||||||
child: Padding(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 20.0),
|
physics: const BouncingScrollPhysics(),
|
||||||
child: Column(
|
child: Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
children: [
|
child: Column(
|
||||||
// --- NUOVO HEADER FISSATO ---
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Row(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
const SizedBox(height: 20),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Center(
|
||||||
children: [
|
child: Transform.rotate(
|
||||||
// BLOCCO SINISTRO: AVATAR E NOME
|
angle: themeType == AppThemeType.doodle ? -0.04 : 0,
|
||||||
Expanded(
|
child: GestureDetector(
|
||||||
child: Row(
|
onTap: () {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
_debugTapCount++;
|
||||||
children: [
|
if (_debugTapCount == 5) {
|
||||||
GestureDetector(
|
StorageService.instance.addXP(2000);
|
||||||
behavior: HitTestBehavior.opaque,
|
setState(() {});
|
||||||
onTap: _showNameDialog,
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
child: themeType == AppThemeType.doodle
|
SnackBar(content: Text("🛠 DEBUG MODE: +20 Livelli!", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), backgroundColor: Colors.purpleAccent, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)))
|
||||||
? CustomPaint(
|
);
|
||||||
painter: DoodleBackgroundPainter(fillColor: Colors.white.withOpacity(0.8), strokeColor: inkColor, seed: 1, isCircle: true),
|
} else if (_debugTapCount >= 7) {
|
||||||
child: SizedBox(width: 50, height: 50, child: Icon(Icons.person, color: inkColor, size: 30)),
|
_debugTapCount = 0;
|
||||||
)
|
Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen()));
|
||||||
: SizedBox(
|
}
|
||||||
width: 50, height: 50,
|
},
|
||||||
child: Stack(
|
child: FittedBox(
|
||||||
fit: StackFit.expand,
|
fit: BoxFit.scaleDown,
|
||||||
children: [
|
child: Text(
|
||||||
CircularProgressIndicator(value: xpProgress, color: theme.playerBlue, strokeWidth: 3, backgroundColor: theme.gridLine.withOpacity(0.2)),
|
loc.appTitle.toUpperCase(),
|
||||||
Padding(
|
style: getSharedTextStyle(themeType, TextStyle(
|
||||||
padding: const EdgeInsets.all(4.0),
|
fontSize: 65,
|
||||||
child: Container(
|
fontWeight: FontWeight.w900,
|
||||||
decoration: BoxDecoration(shape: BoxShape.circle, boxShadow: [BoxShadow(color: theme.playerBlue.withOpacity(0.3), blurRadius: 10, offset: const Offset(0, 4))]),
|
color: themeType == AppThemeType.doodle ? inkColor : theme.text,
|
||||||
child: CircleAvatar(backgroundColor: theme.playerBlue.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerBlue, size: 26)),
|
letterSpacing: 10,
|
||||||
),
|
shadows: themeType == AppThemeType.doodle
|
||||||
),
|
? [const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 0), Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(2.5, 2.5), blurRadius: 1)]
|
||||||
],
|
: themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(3, 6), blurRadius: 8), BoxShadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20)]
|
||||||
),
|
))
|
||||||
),
|
|
||||||
),
|
|
||||||
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))),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
// BLOCCO DESTRO: STATISTICHE E AUDIO
|
// --- MENU IN BASE AL TEMA ---
|
||||||
Column(
|
if (themeType == AppThemeType.music) ...[
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
MusicCassetteCard(title: loc.onlineTitle, subtitle: loc.onlineSub, neonColor: Colors.blueAccent, angle: -0.04, leftIcon: FontAwesomeIcons.sliders, rightIcon: FontAwesomeIcons.globe, themeType: themeType, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }),
|
||||||
children: [
|
const SizedBox(height: 12),
|
||||||
GestureDetector(
|
MusicCassetteCard(title: loc.cpuTitle, subtitle: loc.cpuSub, neonColor: Colors.purpleAccent, angle: 0.03, leftIcon: FontAwesomeIcons.desktop, rightIcon: FontAwesomeIcons.music, themeType: themeType, onTap: () => _showMatchSetupDialog(true)),
|
||||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HistoryScreen())),
|
const SizedBox(height: 12),
|
||||||
child: themeType == AppThemeType.doodle
|
MusicCassetteCard(title: loc.localTitle, subtitle: loc.localSub, neonColor: Colors.deepPurpleAccent, angle: -0.02, leftIcon: FontAwesomeIcons.headphones, rightIcon: FontAwesomeIcons.headphones, themeType: themeType, onTap: () => _showMatchSetupDialog(false)),
|
||||||
? Transform.rotate(
|
const SizedBox(height: 30),
|
||||||
angle: 0.04,
|
Row(
|
||||||
child: CustomPaint(
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
painter: DoodleBackgroundPainter(fillColor: Colors.yellow.shade100, strokeColor: inkColor, seed: 2),
|
children: [
|
||||||
child: Padding(
|
Expanded(child: MusicKnobCard(title: loc.leaderboardTitle, icon: FontAwesomeIcons.compactDisc, iconColor: Colors.amber, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()))),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
Expanded(child: MusicKnobCard(title: loc.questsTitle, icon: FontAwesomeIcons.microphoneLines, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()))),
|
||||||
child: Row(
|
Expanded(child: MusicKnobCard(title: loc.themesTitle, icon: FontAwesomeIcons.palette, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())))),
|
||||||
mainAxisSize: MainAxisSize.min,
|
Expanded(child: MusicKnobCard(title: loc.tutorialTitle, icon: FontAwesomeIcons.bookOpen, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()))),
|
||||||
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,
|
|
||||||
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 ---
|
] else ...[
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_buildCyberCard(FeatureCard(title: loc.onlineTitle, subtitle: loc.onlineSub, icon: Icons.public, color: Colors.lightBlue.shade200, theme: theme, themeType: themeType, isFeatured: true, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), themeType),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildCyberCard(FeatureCard(title: loc.cpuTitle, subtitle: loc.cpuSub, icon: Icons.smart_toy, color: Colors.purple.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), themeType),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildCyberCard(FeatureCard(title: loc.localTitle, subtitle: loc.localSub, icon: Icons.people_alt, color: Colors.red.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), themeType),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
const Spacer(),
|
Row(
|
||||||
|
children: [
|
||||||
Center(
|
Expanded(child: _buildCyberCard(FeatureCard(title: loc.leaderboardTitle, subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()), compact: true), themeType)),
|
||||||
child: Transform.rotate(
|
const SizedBox(width: 12),
|
||||||
angle: themeType == AppThemeType.doodle ? -0.04 : 0,
|
Expanded(child: _buildCyberCard(FeatureCard(title: loc.questsTitle, subtitle: "Missioni", icon: Icons.assignment_turned_in, color: Colors.green.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()), compact: true), themeType)),
|
||||||
child: GestureDetector(
|
],
|
||||||
onTap: () {
|
|
||||||
_debugTapCount++;
|
|
||||||
if (_debugTapCount == 5) {
|
|
||||||
StorageService.instance.addXP(2000);
|
|
||||||
setState(() {});
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text("🛠 DEBUG MODE: +20 Livelli!", style: getSharedTextStyle(themeType, const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))), backgroundColor: Colors.purpleAccent, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)))
|
|
||||||
);
|
|
||||||
} else if (_debugTapCount >= 7) {
|
|
||||||
_debugTapCount = 0;
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (_) => const AdminScreen()));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child: Text(
|
|
||||||
loc.appTitle.toUpperCase(),
|
|
||||||
style: getSharedTextStyle(themeType, TextStyle(
|
|
||||||
fontSize: 65,
|
|
||||||
fontWeight: FontWeight.w900,
|
|
||||||
color: themeType == AppThemeType.doodle ? inkColor : theme.text,
|
|
||||||
letterSpacing: 10,
|
|
||||||
shadows: themeType == AppThemeType.doodle
|
|
||||||
? [const Shadow(color: Colors.white, offset: Offset(-2.5, -2.5), blurRadius: 0), Shadow(color: Colors.black.withOpacity(0.25), offset: const Offset(2.5, 2.5), blurRadius: 1)]
|
|
||||||
: themeType == AppThemeType.arcade || themeType == AppThemeType.music ? [] : [BoxShadow(color: Colors.black.withOpacity(0.6), offset: const Offset(3, 6), blurRadius: 8), BoxShadow(color: theme.playerBlue.withOpacity(0.4), offset: const Offset(0, 0), blurRadius: 20)]
|
|
||||||
))
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: _buildCyberCard(FeatureCard(title: loc.themesTitle, subtitle: "Personalizza", icon: Icons.palette, color: Colors.teal.shade200, theme: theme, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), compact: true), themeType)),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(child: _buildCyberCard(FeatureCard(title: loc.tutorialTitle, subtitle: "Come giocare", icon: Icons.school, color: Colors.indigo.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()), compact: true), themeType)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
const Spacer(),
|
|
||||||
|
|
||||||
// --- MENU IN BASE AL TEMA ---
|
|
||||||
if (themeType == AppThemeType.music) ...[
|
|
||||||
MusicCassetteCard(title: loc.onlineTitle, subtitle: loc.onlineSub, neonColor: Colors.blueAccent, angle: -0.04, leftIcon: FontAwesomeIcons.sliders, rightIcon: FontAwesomeIcons.globe, themeType: themeType, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
MusicCassetteCard(title: loc.cpuTitle, subtitle: loc.cpuSub, neonColor: Colors.purpleAccent, angle: 0.03, leftIcon: FontAwesomeIcons.desktop, rightIcon: FontAwesomeIcons.music, themeType: themeType, onTap: () => _showMatchSetupDialog(true)),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
MusicCassetteCard(title: loc.localTitle, subtitle: loc.localSub, neonColor: Colors.deepPurpleAccent, angle: -0.02, leftIcon: FontAwesomeIcons.headphones, rightIcon: FontAwesomeIcons.headphones, themeType: themeType, onTap: () => _showMatchSetupDialog(false)),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(child: MusicKnobCard(title: loc.leaderboardTitle, icon: FontAwesomeIcons.compactDisc, iconColor: Colors.amber, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()))),
|
|
||||||
Expanded(child: MusicKnobCard(title: loc.questsTitle, icon: FontAwesomeIcons.microphoneLines, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()))),
|
|
||||||
Expanded(child: MusicKnobCard(title: loc.themesTitle, icon: FontAwesomeIcons.palette, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())))),
|
|
||||||
Expanded(child: MusicKnobCard(title: loc.tutorialTitle, icon: FontAwesomeIcons.bookOpen, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()))),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
] else ...[
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
_buildCyberCard(FeatureCard(title: loc.onlineTitle, subtitle: loc.onlineSub, icon: Icons.public, color: Colors.lightBlue.shade200, theme: theme, themeType: themeType, isFeatured: true, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const LobbyScreen())); }), themeType),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_buildCyberCard(FeatureCard(title: loc.cpuTitle, subtitle: loc.cpuSub, icon: Icons.smart_toy, color: Colors.purple.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(true)), themeType),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_buildCyberCard(FeatureCard(title: loc.localTitle, subtitle: loc.localSub, icon: Icons.people_alt, color: Colors.red.shade200, theme: theme, themeType: themeType, onTap: () => _showMatchSetupDialog(false)), themeType),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: _buildCyberCard(FeatureCard(title: loc.leaderboardTitle, subtitle: "Top 50 Globale", icon: Icons.leaderboard, color: Colors.amber.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const LeaderboardDialog()), compact: true), themeType)),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(child: _buildCyberCard(FeatureCard(title: loc.questsTitle, subtitle: "Missioni", icon: Icons.assignment_turned_in, color: Colors.green.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const QuestsDialog()), compact: true), themeType)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: _buildCyberCard(FeatureCard(title: loc.themesTitle, subtitle: "Personalizza", icon: Icons.palette, color: Colors.teal.shade200, theme: theme, themeType: themeType, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), compact: true), themeType)),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(child: _buildCyberCard(FeatureCard(title: loc.tutorialTitle, subtitle: "Come giocare", icon: Icons.school, color: Colors.indigo.shade200, theme: theme, themeType: themeType, onTap: () => showDialog(context: context, builder: (ctx) => const TutorialDialog()), compact: true), themeType)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
],
|
],
|
||||||
),
|
const SizedBox(height: 40), // Margine in fondo per assicurare che ci sia respiro
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
|
backgroundColor: bgImage != null ? Colors.transparent : theme.background,
|
||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true, // Rimosso l'AppBar vuoto che "spingeva" giù il SafeArea!
|
||||||
appBar: AppBar(backgroundColor: Colors.transparent, elevation: 0, iconTheme: IconThemeData(color: theme.text)),
|
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
// 1. Sfondo base a tinta unita
|
// 1. Sfondo base a tinta unita
|
||||||
|
|
|
||||||
|
|
@ -7,617 +7,16 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'dart:math' as math;
|
|
||||||
|
|
||||||
import '../../logic/game_controller.dart';
|
import '../../logic/game_controller.dart';
|
||||||
import '../../models/game_board.dart';
|
import '../../models/game_board.dart';
|
||||||
import '../../core/theme_manager.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/multiplayer_service.dart';
|
||||||
import '../../services/storage_service.dart';
|
import '../../services/storage_service.dart';
|
||||||
import '../game/game_screen.dart';
|
import '../game/game_screen.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import '../../widgets/painters.dart';
|
||||||
|
import 'lobby_widgets.dart'; // Importa i widget separati
|
||||||
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)
|
|
||||||
// ===========================================================================
|
|
||||||
|
|
||||||
class LobbyScreen extends StatefulWidget {
|
class LobbyScreen extends StatefulWidget {
|
||||||
final String? initialRoomCode;
|
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 {
|
Future<void> _createRoomAndInvite(String targetUid, String targetName) async {
|
||||||
if (_isLoading) return;
|
if (_isLoading) return;
|
||||||
setState(() => _isLoading = true);
|
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)); }
|
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() {
|
void _showFavoritesDialogForCreation() {
|
||||||
final favs = StorageService.instance.favorites;
|
final favs = StorageService.instance.favorites;
|
||||||
|
|
||||||
|
|
@ -776,7 +173,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(20),
|
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(
|
content: Container(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
height: 300,
|
height: 300,
|
||||||
|
|
@ -787,27 +184,27 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
child: favs.isEmpty
|
child: favs.isEmpty
|
||||||
? Center(child: Padding(
|
? Center(child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
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(
|
: ListView.builder(
|
||||||
itemCount: favs.length,
|
itemCount: favs.length,
|
||||||
itemBuilder: (c, i) {
|
itemBuilder: (c, i) {
|
||||||
return ListTile(
|
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(
|
trailing: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
|
style: ElevatedButton.styleFrom(backgroundColor: theme.playerBlue, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10))),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(ctx);
|
Navigator.pop(ctx);
|
||||||
_createRoomAndInvite(favs[i]['uid']!, favs[i]['name']!);
|
_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: [
|
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,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
CircularProgressIndicator(color: theme.playerRed), const SizedBox(height: 25),
|
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("CODICE STANZA", style: getLobbyTextStyle(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(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),
|
const SizedBox(height: 25),
|
||||||
Transform.rotate(
|
Transform.rotate(
|
||||||
angle: themeType == AppThemeType.doodle ? 0.02 : 0,
|
angle: themeType == AppThemeType.doodle ? 0.02 : 0,
|
||||||
|
|
@ -844,9 +241,9 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Icon(_isPublicRoom ? Icons.podcasts : Icons.share, color: theme.playerBlue, size: 32), const SizedBox(height: 12),
|
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),
|
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) {
|
if (themeType == AppThemeType.cyberpunk) {
|
||||||
dialogContent = _AnimatedCyberBorder(child: dialogContent);
|
dialogContent = AnimatedCyberBorder(child: dialogContent);
|
||||||
} else {
|
} else {
|
||||||
dialogContent = Container(
|
dialogContent = Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
|
|
@ -904,7 +301,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
_cleanupGhostRoom();
|
_cleanupGhostRoom();
|
||||||
Navigator.pop(context);
|
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.doodle) bgImage = 'assets/images/doodle_bg.jpg';
|
||||||
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
if (themeType == AppThemeType.cyberpunk) bgImage = 'assets/images/cyber_bg.jpg';
|
||||||
|
|
||||||
bool isChaosUnlocked = StorageService.instance.playerLevel >= 10;
|
bool isChaosUnlocked = true;
|
||||||
|
|
||||||
// --- PANNELLO IMPOSTAZIONI STANZA ---
|
// --- PANNELLO IMPOSTAZIONI STANZA ---
|
||||||
Widget hostPanel = Transform.rotate(
|
Widget hostPanel = Transform.rotate(
|
||||||
|
|
@ -947,24 +344,24 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
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),
|
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),
|
const SizedBox(height: 6),
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
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),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_NeonSizeButton(label: 'S', isSelected: _selectedRadius == 3, theme: theme, themeType: themeType, onTap: () => setState(() => _selectedRadius = 3)),
|
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: '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: '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: '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),
|
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),
|
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),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
// RIGA 1: TEMPO (OCCUPA TUTTO LO SPAZIO)
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
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),
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
// RIGA 2: VISIBILITÀ E INVITI
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
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),
|
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) {
|
if (themeType == AppThemeType.cyberpunk) {
|
||||||
hostPanel = _AnimatedCyberBorder(child: hostPanel);
|
hostPanel = AnimatedCyberBorder(child: hostPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget uiContent = SafeArea(
|
Widget uiContent = SafeArea(
|
||||||
|
|
@ -1030,7 +425,7 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
Expanded(
|
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
|
const SizedBox(width: 48), // Bilanciamento
|
||||||
],
|
],
|
||||||
|
|
@ -1050,11 +445,11 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
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),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
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(
|
: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
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),
|
const SizedBox(height: 20),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
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)),
|
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(
|
child: TextField(
|
||||||
controller: _codeController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 5,
|
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(
|
decoration: InputDecoration(
|
||||||
contentPadding: const EdgeInsets.symmetric(vertical: 12),
|
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,
|
filled: themeType != AppThemeType.doodle,
|
||||||
fillColor: themeType == AppThemeType.cyberpunk ? Colors.black.withOpacity(0.85) : theme.text.withOpacity(0.05),
|
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)),
|
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),
|
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(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
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)),
|
Expanded(child: Divider(color: theme.text.withOpacity(0.4), thickness: themeType == AppThemeType.doodle ? 2 : 1.0)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
|
|
||||||
// --- LA VERA E PROPRIA BACHECA PUBBLICA ---
|
|
||||||
StreamBuilder<QuerySnapshot>(
|
StreamBuilder<QuerySnapshot>(
|
||||||
stream: _multiplayerService.getPublicRooms(),
|
stream: _multiplayerService.getPublicRooms(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|
@ -1125,8 +519,8 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
|
|
||||||
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
padding: const EdgeInsets.symmetric(vertical: 40.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)))),
|
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) {
|
if (docs.isEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20.0),
|
padding: const EdgeInsets.symmetric(vertical: 40.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)))),
|
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(
|
return Transform.rotate(
|
||||||
angle: themeType == AppThemeType.doodle ? (index % 2 == 0 ? 0.01 : -0.01) : 0,
|
angle: themeType == AppThemeType.doodle ? (index % 2 == 0 ? 0.01 : -0.01) : 0,
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
|
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
|
@ -1195,27 +589,28 @@ class _LobbyScreenState extends State<LobbyScreen> with WidgetsBindingObserver {
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed)),
|
CircleAvatar(radius: 25, backgroundColor: theme.playerRed.withOpacity(0.2), child: Icon(Icons.person, color: theme.playerRed, size: 28)),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 15),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text("Stanza di $host", style: _getTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 16))),
|
Text("Stanza di $host", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text, fontWeight: FontWeight.bold, fontSize: 18))),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 6),
|
||||||
Text("Raggio: $r • $prettyShape • ${time ? 'A Tempo' : 'Relax'}", style: _getTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 11))),
|
Text("Raggio: $r • $prettyShape • ${time ? 'A Tempo' : 'Relax'}", style: getLobbyTextStyle(themeType, TextStyle(color: theme.text.withOpacity(0.6), fontSize: 12))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
backgroundColor: theme.playerBlue, foregroundColor: Colors.white,
|
backgroundColor: theme.playerBlue, foregroundColor: Colors.white,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||||
elevation: themeType == AppThemeType.doodle ? 0 : 2,
|
elevation: themeType == AppThemeType.doodle ? 0 : 2,
|
||||||
side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 2) : BorderSide.none,
|
side: themeType == AppThemeType.doodle ? BorderSide(color: theme.text, width: 2) : BorderSide.none,
|
||||||
),
|
),
|
||||||
onPressed: () => _joinRoomByCode(doc.id),
|
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_app_check
|
||||||
import firebase_auth
|
import firebase_auth
|
||||||
import firebase_core
|
import firebase_core
|
||||||
|
import package_info_plus
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
|
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
|
||||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1401,6 +1401,8 @@ PODS:
|
||||||
- nanopb/encode (= 3.30910.0)
|
- nanopb/encode (= 3.30910.0)
|
||||||
- nanopb/decode (3.30910.0)
|
- nanopb/decode (3.30910.0)
|
||||||
- nanopb/encode (3.30910.0)
|
- nanopb/encode (3.30910.0)
|
||||||
|
- package_info_plus (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
|
@ -1416,6 +1418,7 @@ DEPENDENCIES:
|
||||||
- firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
|
- firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`)
|
||||||
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
- firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- 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`)
|
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- 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
|
:path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
|
package_info_plus:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
|
|
@ -1491,6 +1496,7 @@ SPEC CHECKSUMS:
|
||||||
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
|
GTMSessionFetcher: b8ab00db932816e14b0a0664a08cb73dda6d164b
|
||||||
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
|
leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
|
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,22 @@ class MainFlutterWindow: NSWindow {
|
||||||
let flutterViewController = FlutterViewController()
|
let flutterViewController = FlutterViewController()
|
||||||
let windowFrame = self.frame
|
let windowFrame = self.frame
|
||||||
self.contentViewController = flutterViewController
|
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)
|
RegisterGeneratedPlugins(registry: flutterViewController)
|
||||||
|
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
16
pubspec.lock
16
pubspec.lock
|
|
@ -557,6 +557,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.3.0"
|
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:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ dependencies:
|
||||||
google_fonts: ^8.0.2
|
google_fonts: ^8.0.2
|
||||||
font_awesome_flutter: ^10.12.0
|
font_awesome_flutter: ^10.12.0
|
||||||
firebase_app_check: ^0.4.1+5
|
firebase_app_check: ^0.4.1+5
|
||||||
|
package_info_plus: ^9.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue