Compare commits
1 commit
main
...
v_20260329
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dee241f055 |
11 changed files with 12 additions and 23584 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
BIN
android/.DS_Store
vendored
BIN
android/.DS_Store
vendored
Binary file not shown.
BIN
android/app/.DS_Store
vendored
BIN
android/app/.DS_Store
vendored
Binary file not shown.
BIN
ios/.DS_Store
vendored
BIN
ios/.DS_Store
vendored
Binary file not shown.
|
|
@ -652,7 +652,7 @@ class GameController extends ChangeNotifier {
|
||||||
} else if (isVsCPU) {
|
} else if (isVsCPU) {
|
||||||
int myScore = board.scoreRed; int cpuScore = board.scoreBlue;
|
int myScore = board.scoreRed; int cpuScore = board.scoreBlue;
|
||||||
bool isWin = myScore > cpuScore;
|
bool isWin = myScore > cpuScore;
|
||||||
calculatedXP = isWin ? (10 + (cpuLevel ~/ 2)).clamp(10, 25) : (isDraw ? 5 : 2);
|
calculatedXP = isWin ? (10 + (cpuLevel * 2)) : (isDraw ? 5 : 2);
|
||||||
|
|
||||||
if (isWin) {
|
if (isWin) {
|
||||||
await StorageService.instance.addWin();
|
await StorageService.instance.addWin();
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import '../multiplayer/lobby_screen.dart';
|
||||||
import '../admin/admin_screen.dart';
|
import '../admin/admin_screen.dart';
|
||||||
import '../settings/settings_screen.dart';
|
import '../settings/settings_screen.dart';
|
||||||
import '../game/game_screen.dart';
|
import '../game/game_screen.dart';
|
||||||
import '../profile/profile_screen.dart';
|
|
||||||
import 'package:tetraq/l10n/app_localizations.dart';
|
import 'package:tetraq/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../../widgets/painters.dart';
|
import '../../widgets/painters.dart';
|
||||||
|
|
@ -78,10 +77,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (StorageService.instance.playerName.isEmpty) {
|
if (StorageService.instance.playerName.isEmpty) {
|
||||||
Navigator.push(
|
HomeModals.showNameDialog(context, () {
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (_) => const ProfileScreen()),
|
|
||||||
).then((_) {
|
|
||||||
StorageService.instance.syncLeaderboard();
|
StorageService.instance.syncLeaderboard();
|
||||||
_listenToInvites();
|
_listenToInvites();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
@ -510,12 +506,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () => HomeModals.showNameDialog(context, () => setState(() {})),
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (_) => const ProfileScreen()),
|
|
||||||
).then((_) => setState(() {}));
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
decoration: _glassBoxDecoration(theme, themeType),
|
decoration: _glassBoxDecoration(theme, themeType),
|
||||||
|
|
|
||||||
|
|
@ -1,473 +0,0 @@
|
||||||
// ===========================================================================
|
|
||||||
// FILE: lib/ui/profile/profile_screen.dart
|
|
||||||
// ===========================================================================
|
|
||||||
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
import '../../core/theme_manager.dart';
|
|
||||||
import '../../core/app_colors.dart';
|
|
||||||
import '../../services/storage_service.dart';
|
|
||||||
import '../../widgets/painters.dart';
|
|
||||||
import '../../widgets/cyber_border.dart';
|
|
||||||
|
|
||||||
class ProfileScreen extends StatefulWidget {
|
|
||||||
const ProfileScreen({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ProfileScreen> createState() => _ProfileScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ProfileScreenState extends State<ProfileScreen> {
|
|
||||||
final TextEditingController _nameController = TextEditingController();
|
|
||||||
final TextEditingController _passController = TextEditingController();
|
|
||||||
|
|
||||||
bool _isLoading = false;
|
|
||||||
bool _obscurePassword = true;
|
|
||||||
String _errorMessage = "";
|
|
||||||
List<String> _nameSuggestions = [];
|
|
||||||
|
|
||||||
bool _isGhostMode = false;
|
|
||||||
late User _currentUser;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_currentUser = FirebaseAuth.instance.currentUser!;
|
|
||||||
_loadGhostMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_nameController.dispose();
|
|
||||||
_passController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadGhostMode() async {
|
|
||||||
try {
|
|
||||||
var doc = await FirebaseFirestore.instance.collection('leaderboard').doc(_currentUser.uid).get();
|
|
||||||
if (doc.exists && doc.data()!.containsKey('isGhost')) {
|
|
||||||
setState(() {
|
|
||||||
_isGhostMode = doc.data()!['isGhost'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Errore caricamento Ghost Mode: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _toggleGhostMode(bool value) async {
|
|
||||||
setState(() => _isGhostMode = value);
|
|
||||||
try {
|
|
||||||
await FirebaseFirestore.instance.collection('leaderboard').doc(_currentUser.uid).set(
|
|
||||||
{'isGhost': value}, SetOptions(merge: true)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Errore salvataggio Ghost Mode: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getPlayerTitle(int level) {
|
|
||||||
if (level < 10) return "Principiante";
|
|
||||||
if (level < 20) return "Apprendista";
|
|
||||||
if (level < 40) return "Sfidante";
|
|
||||||
if (level < 60) return "Tattico dell'Arena";
|
|
||||||
if (level < 80) return "Maestro dei Quadrati";
|
|
||||||
if (level < 100) return "Gran Maestro";
|
|
||||||
if (level < 130) return "Campione della Griglia";
|
|
||||||
if (level < 160) return "Entità Digitale";
|
|
||||||
if (level < 200) return "Oracolo del Codice";
|
|
||||||
return "Leggenda Suprema";
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleRegistration() async {
|
|
||||||
final name = _nameController.text.trim().toUpperCase();
|
|
||||||
final password = _passController.text.trim();
|
|
||||||
|
|
||||||
setState(() { _errorMessage = ""; _nameSuggestions.clear(); _isLoading = true; });
|
|
||||||
|
|
||||||
if (name.isEmpty || password.isEmpty) {
|
|
||||||
setState(() { _errorMessage = "Compila tutti i campi!"; _isLoading = false; });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (password.length < 6) {
|
|
||||||
setState(() { _errorMessage = "Password troppo corta (min. 6 caratteri)"; _isLoading = false; });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Controllo univocità del nome
|
|
||||||
var existingUser = await FirebaseFirestore.instance.collection('leaderboard').where('name', isEqualTo: name).get();
|
|
||||||
|
|
||||||
if (existingUser.docs.isNotEmpty && existingUser.docs.first.id != _currentUser.uid) {
|
|
||||||
// Nome già preso, generiamo suggerimenti
|
|
||||||
List<String> suggestions = [];
|
|
||||||
int attempts = 0;
|
|
||||||
final rand = Random();
|
|
||||||
while(suggestions.length < 3 && attempts < 15) {
|
|
||||||
String candidate = "$name${rand.nextInt(99) + 1}";
|
|
||||||
var check = await FirebaseFirestore.instance.collection('leaderboard').where('name', isEqualTo: candidate).get();
|
|
||||||
if (check.docs.isEmpty && !suggestions.contains(candidate)) {
|
|
||||||
suggestions.add(candidate);
|
|
||||||
}
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = "Nome già in uso! Scegline un altro:";
|
|
||||||
_nameSuggestions = suggestions;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Registrazione sicura
|
|
||||||
final fakeEmail = "${name.replaceAll(' ', '')}@tetraq.game".toLowerCase();
|
|
||||||
|
|
||||||
if (_currentUser.isAnonymous) {
|
|
||||||
final credential = EmailAuthProvider.credential(email: fakeEmail, password: password);
|
|
||||||
await _currentUser.linkWithCredential(credential);
|
|
||||||
}
|
|
||||||
|
|
||||||
await StorageService.instance.savePlayerName(name);
|
|
||||||
await StorageService.instance.syncLeaderboard();
|
|
||||||
|
|
||||||
setState(() { _isLoading = false; });
|
|
||||||
if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Account Protetto con Successo!"), backgroundColor: Colors.green));
|
|
||||||
|
|
||||||
} on FirebaseAuthException catch (e) {
|
|
||||||
String msg = "Errore di connessione.";
|
|
||||||
if (e.code == 'email-already-in-use' || e.code == 'credential-already-in-use') msg = "Utente già registrato. Se sei tu, fai il login.";
|
|
||||||
setState(() { _errorMessage = msg; _isLoading = false; });
|
|
||||||
} catch (e) {
|
|
||||||
setState(() { _errorMessage = "Errore: $e"; _isLoading = false; });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _deleteAccount() async {
|
|
||||||
bool confirm = await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (ctx) => AlertDialog(
|
|
||||||
backgroundColor: Colors.black87,
|
|
||||||
title: const Text("ATTENZIONE", style: TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold)),
|
|
||||||
content: const Text("Stai per eliminare definitivamente il tuo profilo, i tuoi XP e le statistiche.\nL'operazione è irreversibile.\n\nVuoi procedere?", style: TextStyle(color: Colors.white)),
|
|
||||||
actions: [
|
|
||||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text("ANNULLA", style: TextStyle(color: Colors.grey))),
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
|
|
||||||
onPressed: () => Navigator.pop(ctx, true),
|
|
||||||
child: const Text("SÌ, ELIMINA", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
) ?? false;
|
|
||||||
|
|
||||||
if (!confirm) return;
|
|
||||||
|
|
||||||
setState(() => _isLoading = true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Elimina record da Firestore
|
|
||||||
await FirebaseFirestore.instance.collection('leaderboard').doc(_currentUser.uid).delete();
|
|
||||||
|
|
||||||
// 2. Elimina l'utente Auth
|
|
||||||
await _currentUser.delete();
|
|
||||||
|
|
||||||
// 3. Pulisci i dati locali sensibili
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
await prefs.remove('totalXP');
|
|
||||||
await prefs.remove('wins');
|
|
||||||
await prefs.remove('losses');
|
|
||||||
await prefs.remove('cpuLevel');
|
|
||||||
await prefs.remove('playerName');
|
|
||||||
await prefs.remove('favorites');
|
|
||||||
|
|
||||||
// 4. Ricrea un anonimo pulito e torna alla Home
|
|
||||||
await FirebaseAuth.instance.signInAnonymously();
|
|
||||||
await StorageService.instance.init();
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
|
||||||
}
|
|
||||||
} on FirebaseAuthException catch (e) {
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
if (e.code == 'requires-recent-login') {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Per sicurezza, riavvia l'app prima di eliminare l'account."), backgroundColor: Colors.redAccent));
|
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Errore: ${e.message}"), backgroundColor: Colors.redAccent));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final themeManager = context.watch<ThemeManager>();
|
|
||||||
final theme = themeManager.currentColors;
|
|
||||||
final themeType = themeManager.currentThemeType;
|
|
||||||
Color inkColor = const Color(0xFF111122);
|
|
||||||
|
|
||||||
int wins = StorageService.instance.wins;
|
|
||||||
int losses = StorageService.instance.losses;
|
|
||||||
int totalGames = wins + losses;
|
|
||||||
double winRate = totalGames > 0 ? (wins / totalGames) * 100 : 0.0;
|
|
||||||
|
|
||||||
int level = StorageService.instance.playerLevel;
|
|
||||||
String title = _getPlayerTitle(level);
|
|
||||||
String playerName = StorageService.instance.playerName;
|
|
||||||
if (playerName.isEmpty) playerName = "GUEST";
|
|
||||||
|
|
||||||
bool isAnon = _currentUser.isAnonymous;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: theme.background,
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text("PROFILO GIOCATORE", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.w900, letterSpacing: 1.5))),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
elevation: 0,
|
|
||||||
iconTheme: IconThemeData(color: themeType == AppThemeType.doodle ? inkColor : theme.text),
|
|
||||||
),
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
if (themeType == AppThemeType.doodle)
|
|
||||||
Positioned.fill(child: CustomPaint(painter: FullScreenGridPainter(Colors.blue.withOpacity(0.15)))),
|
|
||||||
|
|
||||||
SingleChildScrollView(
|
|
||||||
physics: const BouncingScrollPhysics(),
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
// --- SEZIONE 1: IDENTITÀ ---
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(color: themeType == AppThemeType.doodle ? inkColor : theme.playerBlue.withOpacity(0.3), width: 2),
|
|
||||||
boxShadow: themeType == AppThemeType.doodle ? [BoxShadow(color: inkColor.withOpacity(0.8), offset: const Offset(4, 4))] : [],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
CircleAvatar(radius: 40, backgroundColor: theme.playerBlue.withOpacity(0.2), child: Icon(Icons.person, size: 45, color: theme.playerBlue)),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
Text(playerName, style: getSharedTextStyle(themeType, TextStyle(fontSize: 28, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? inkColor : theme.text))),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(title, style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: theme.playerRed))),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(isAnon ? Icons.warning_amber_rounded : Icons.verified_user, color: isAnon ? Colors.orange : Colors.green, size: 18),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Text(isAnon ? "Account non protetto" : "Account protetto sul Cloud", style: getSharedTextStyle(themeType, TextStyle(color: isAnon ? Colors.orange : Colors.green, fontWeight: FontWeight.bold, fontSize: 12))),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// --- SEZIONE 2: STATISTICHE AVANZATE ---
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(child: _buildStatCard("Vittorie", "$wins", Icons.emoji_events, Colors.amber, theme, themeType)),
|
|
||||||
const SizedBox(width: 15),
|
|
||||||
Expanded(child: _buildStatCard("Sconfitte", "$losses", Icons.sentiment_very_dissatisfied, theme.playerRed, theme, themeType)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
_buildStatCard("Win Rate Globale", "${winRate.toStringAsFixed(1)}%", Icons.pie_chart, theme.playerBlue, theme, themeType, isWide: true),
|
|
||||||
const SizedBox(height: 25),
|
|
||||||
|
|
||||||
// --- SEZIONE 3: REGISTRAZIONE (Solo se anonimo) ---
|
|
||||||
if (isAnon) ...[
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.orange.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(color: Colors.orange, width: 2),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text("Metti al sicuro i tuoi progressi!", textAlign: TextAlign.center, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : Colors.white, fontWeight: FontWeight.bold, fontSize: 16))),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
TextField(
|
|
||||||
controller: _nameController, textCapitalization: TextCapitalization.characters, textAlign: TextAlign.center, maxLength: 8,
|
|
||||||
style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : Colors.white, fontSize: 20, fontWeight: FontWeight.bold)),
|
|
||||||
decoration: InputDecoration(hintText: "Scegli un Nome", hintStyle: TextStyle(color: Colors.grey.withOpacity(0.6)), filled: true, fillColor: Colors.black12, counterText: "", border: OutlineInputBorder(borderRadius: BorderRadius.circular(15), borderSide: BorderSide.none)),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
TextField(
|
|
||||||
controller: _passController, obscureText: _obscurePassword, textAlign: TextAlign.center, maxLength: 20,
|
|
||||||
style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : Colors.white, fontSize: 20, fontWeight: FontWeight.bold)),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: "Scegli Password", hintStyle: TextStyle(color: Colors.grey.withOpacity(0.6)), filled: true, fillColor: Colors.black12, counterText: "", border: OutlineInputBorder(borderRadius: BorderRadius.circular(15), borderSide: BorderSide.none),
|
|
||||||
suffixIcon: IconButton(icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off, color: Colors.grey), onPressed: () => setState(() => _obscurePassword = !_obscurePassword)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_errorMessage.isNotEmpty) ...[
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Text(_errorMessage, textAlign: TextAlign.center, style: const TextStyle(color: Colors.redAccent, fontWeight: FontWeight.bold)),
|
|
||||||
],
|
|
||||||
if (_nameSuggestions.isNotEmpty) ...[
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8, alignment: WrapAlignment.center,
|
|
||||||
children: _nameSuggestions.map((s) => ActionChip(
|
|
||||||
label: Text(s, style: const TextStyle(fontWeight: FontWeight.bold)),
|
|
||||||
backgroundColor: theme.playerBlue.withOpacity(0.2),
|
|
||||||
side: BorderSide(color: theme.playerBlue),
|
|
||||||
onPressed: () { _nameController.text = s; _handleRegistration(); },
|
|
||||||
)).toList(),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
_isLoading
|
|
||||||
? const Center(child: CircularProgressIndicator(color: Colors.orange))
|
|
||||||
: ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.orange, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 15), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15))),
|
|
||||||
onPressed: _handleRegistration,
|
|
||||||
child: Text("SALVA PROFILO", style: getSharedTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.5))),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 25),
|
|
||||||
],
|
|
||||||
|
|
||||||
// --- SEZIONE 4: IMPOSTAZIONI PRIVACY ---
|
|
||||||
Text("PRIVACY", style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.6) : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
SwitchListTile(
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
activeColor: theme.playerBlue,
|
|
||||||
title: Text("Modalità Fantasma", style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.bold))),
|
|
||||||
subtitle: Text("Nessuno ti vedrà online o potrà invitarti.", style: TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.6) : theme.text.withOpacity(0.5), fontSize: 12)),
|
|
||||||
value: _isGhostMode,
|
|
||||||
onChanged: _toggleGhostMode,
|
|
||||||
),
|
|
||||||
const Divider(),
|
|
||||||
|
|
||||||
// --- SEZIONE 5: GESTIONE PREFERITI ---
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
Text("AMICI PREFERITI", style: getSharedTextStyle(themeType, TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.6) : theme.text.withOpacity(0.5), letterSpacing: 1.5))),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_buildFavoritesList(theme, themeType, inkColor),
|
|
||||||
|
|
||||||
const SizedBox(height: 40),
|
|
||||||
|
|
||||||
// --- SEZIONE 6: DANGER ZONE ---
|
|
||||||
OutlinedButton.icon(
|
|
||||||
style: OutlinedButton.styleFrom(
|
|
||||||
foregroundColor: Colors.redAccent,
|
|
||||||
side: const BorderSide(color: Colors.redAccent, width: 2),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 15),
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.delete_forever),
|
|
||||||
label: Text("ELIMINA PROFILO", style: getSharedTextStyle(themeType, const TextStyle(fontWeight: FontWeight.w900, letterSpacing: 1.5))),
|
|
||||||
onPressed: _deleteAccount,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
if (_isLoading && !isAnon)
|
|
||||||
Positioned.fill(child: Container(color: Colors.black54, child: const Center(child: CircularProgressIndicator(color: Colors.redAccent)))),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildStatCard(String title, String value, IconData icon, Color color, ThemeColors theme, AppThemeType themeType, {bool isWide = false}) {
|
|
||||||
Color inkColor = const Color(0xFF111122);
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(15),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.05),
|
|
||||||
borderRadius: BorderRadius.circular(15),
|
|
||||||
border: Border.all(color: themeType == AppThemeType.doodle ? inkColor : color.withOpacity(0.3), width: 1.5),
|
|
||||||
boxShadow: themeType == AppThemeType.doodle ? [BoxShadow(color: inkColor.withOpacity(0.8), offset: const Offset(3, 3))] : [],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: isWide ? CrossAxisAlignment.center : CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Icon(icon, color: color, size: 20),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(title, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.6) : theme.text.withOpacity(0.6), fontSize: 11, fontWeight: FontWeight.bold))),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Center(
|
|
||||||
child: Text(value, style: getSharedTextStyle(themeType, TextStyle(fontSize: 24, fontWeight: FontWeight.w900, color: themeType == AppThemeType.doodle ? inkColor : theme.text))),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFavoritesList(ThemeColors theme, AppThemeType themeType, Color inkColor) {
|
|
||||||
final favs = StorageService.instance.favorites;
|
|
||||||
|
|
||||||
if (favs.isEmpty) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), border: Border.all(color: Colors.grey.withOpacity(0.3), style: BorderStyle.solid)),
|
|
||||||
child: Center(child: Text("Nessun amico salvato.", style: TextStyle(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.5) : theme.text.withOpacity(0.5)))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: favs.length,
|
|
||||||
itemBuilder: (ctx, i) {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: themeType == AppThemeType.doodle ? Colors.white : theme.text.withOpacity(0.02),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(color: themeType == AppThemeType.doodle ? inkColor.withOpacity(0.2) : theme.text.withOpacity(0.1)),
|
|
||||||
),
|
|
||||||
child: ListTile(
|
|
||||||
leading: Icon(Icons.star, color: Colors.amber.shade600),
|
|
||||||
title: Text(favs[i]['name']!, style: getSharedTextStyle(themeType, TextStyle(color: themeType == AppThemeType.doodle ? inkColor : theme.text, fontWeight: FontWeight.bold))),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(Icons.close, color: Colors.redAccent),
|
|
||||||
onPressed: () async {
|
|
||||||
await StorageService.instance.toggleFavorite(favs[i]['uid']!, favs[i]['name']!);
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FullScreenGridPainter extends CustomPainter {
|
|
||||||
final Color gridColor;
|
|
||||||
FullScreenGridPainter(this.gridColor);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final Paint paperGridPaint = Paint()..color = gridColor..strokeWidth = 1.0..style = PaintingStyle.stroke;
|
|
||||||
double paperStep = 20.0;
|
|
||||||
for (double i = 0; i <= size.width; i += paperStep) canvas.drawLine(Offset(i, 0), Offset(i, size.height), paperGridPaint);
|
|
||||||
for (double i = 0; i <= size.height; i += paperStep) canvas.drawLine(Offset(0, i), Offset(size.width, i), paperGridPaint);
|
|
||||||
}
|
|
||||||
@override bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
16
pubspec.lock
16
pubspec.lock
|
|
@ -141,10 +141,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -545,18 +545,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -854,10 +854,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.10"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: tetraq
|
name: tetraq
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.1.9+4
|
version: 1.1.8+2
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.7
|
sdk: ^3.10.7
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue