Auto-sync: 20260330_160000
This commit is contained in:
parent
047df9b914
commit
1eedefb7f7
4 changed files with 12023 additions and 2 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
|
|
@ -28,6 +28,7 @@ import '../multiplayer/lobby_screen.dart';
|
|||
import '../admin/admin_screen.dart';
|
||||
import '../settings/settings_screen.dart';
|
||||
import '../game/game_screen.dart';
|
||||
import '../profile/profile_screen.dart';
|
||||
import 'package:tetraq/l10n/app_localizations.dart';
|
||||
|
||||
import '../../widgets/painters.dart';
|
||||
|
|
@ -77,7 +78,10 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (StorageService.instance.playerName.isEmpty) {
|
||||
HomeModals.showNameDialog(context, () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const ProfileScreen()),
|
||||
).then((_) {
|
||||
StorageService.instance.syncLeaderboard();
|
||||
_listenToInvites();
|
||||
setState(() {});
|
||||
|
|
@ -506,7 +510,12 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => HomeModals.showNameDialog(context, () => setState(() {})),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const ProfileScreen()),
|
||||
).then((_) => setState(() {}));
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: _glassBoxDecoration(theme, themeType),
|
||||
|
|
|
|||
467
lib/ui/profile/profile_screen.dart
Normal file
467
lib/ui/profile/profile_screen.dart
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
// ===========================================================================
|
||||
// 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 < 5) return "Principiante";
|
||||
if (level < 10) return "Sfidante";
|
||||
if (level < 15) return "Maestro dei Quadrati";
|
||||
return "Leggenda del Neon";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
11545
report/TetraQ_30-03-26_15.35.txt
Normal file
11545
report/TetraQ_30-03-26_15.35.txt
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue