domingo
This commit is contained in:
@@ -6,9 +6,10 @@ import 'package:playmaker/pages/teamPage.dart';
|
||||
import 'package:playmaker/controllers/team_controller.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:playmaker/pages/status_page.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/size_extension.dart';
|
||||
import 'settings_screen.dart';
|
||||
// import 'home_widgets.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
@@ -29,17 +30,29 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
|
||||
final _supabase = Supabase.instance.client;
|
||||
|
||||
// 👇 NOVA VARIÁVEL PARA GUARDAR A FOTO
|
||||
String? _avatarUrl;
|
||||
bool _isMemoryLoaded = false; // 👈 A variável mágica que impede o "piscar" inicial
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadUserAvatar(); // Vai buscar a foto logo quando a Home abre!
|
||||
_loadUserAvatar();
|
||||
}
|
||||
|
||||
// 👇 FUNÇÃO PARA LER A FOTO DA BASE DE DADOS
|
||||
// 👇 FUNÇÃO OTIMIZADA: Carrega da memória instantaneamente e atualiza em background
|
||||
Future<void> _loadUserAvatar() async {
|
||||
// 1. LÊ DA MEMÓRIA RÁPIDA PRIMEIRO
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final savedUrl = prefs.getString('meu_avatar_guardado');
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (savedUrl != null) _avatarUrl = savedUrl;
|
||||
_isMemoryLoaded = true; // Avisa o ecrã que a memória já respondeu!
|
||||
});
|
||||
}
|
||||
|
||||
// 2. VAI AO SUPABASE VERIFICAR SE TROCASTE DE FOTO
|
||||
final userId = _supabase.auth.currentUser?.id;
|
||||
if (userId == null) return;
|
||||
|
||||
@@ -51,12 +64,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
.maybeSingle();
|
||||
|
||||
if (mounted && data != null && data['avatar_url'] != null) {
|
||||
setState(() {
|
||||
_avatarUrl = data['avatar_url'];
|
||||
});
|
||||
final urlDoSupabase = data['avatar_url'];
|
||||
|
||||
// Se a foto na base de dados for nova, ele guarda e atualiza!
|
||||
if (urlDoSupabase != savedUrl) {
|
||||
await prefs.setString('meu_avatar_guardado', urlDoSupabase);
|
||||
setState(() {
|
||||
_avatarUrl = urlDoSupabase;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print("Erro ao carregar avatar na Home: $e");
|
||||
debugPrint("Erro ao carregar avatar na Home: $e");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,33 +91,47 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
appBar: AppBar(
|
||||
title: Text('PlayMaker', style: TextStyle(fontSize: 20 * context.sf)),
|
||||
title: Text('PlayMaker', style: TextStyle(fontSize: 20 * context.sf, fontWeight: FontWeight.bold)),
|
||||
backgroundColor: AppTheme.primaryRed,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
|
||||
// 👇 AQUI ESTÁ A MÁGICA DA TUA FOTO NA APPBAR 👇
|
||||
leading: Padding(
|
||||
padding: EdgeInsets.all(10.0 * context.sf), // Dá um espacinho para não colar aos bordos
|
||||
padding: EdgeInsets.all(10.0 * context.sf),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
onTap: () async {
|
||||
// O 'await' faz com que a Home espere que tu feches os settings...
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const SettingsScreen()),
|
||||
);
|
||||
// ... e quando voltas, ele recarrega a foto logo!
|
||||
_loadUserAvatar();
|
||||
},
|
||||
child: CircleAvatar(
|
||||
backgroundColor: Colors.white.withOpacity(0.2), // Fundo suave caso não haja foto
|
||||
backgroundImage: _avatarUrl != null && _avatarUrl!.isNotEmpty
|
||||
? NetworkImage(_avatarUrl!)
|
||||
: null,
|
||||
child: _avatarUrl == null || _avatarUrl!.isEmpty
|
||||
? Icon(Icons.person, color: Colors.white, size: 20 * context.sf)
|
||||
: null, // Só mostra o ícone se não houver foto
|
||||
),
|
||||
// 👇 SÓ MOSTRA A IMAGEM OU O BONECO DEPOIS DE LER A MEMÓRIA
|
||||
child: !_isMemoryLoaded
|
||||
// Nos primeiros 0.05 segs, mostra só o círculo de fundo (sem boneco)
|
||||
? CircleAvatar(backgroundColor: Colors.white.withOpacity(0.2))
|
||||
|
||||
// Depois da memória responder:
|
||||
: _avatarUrl != null && _avatarUrl!.isNotEmpty
|
||||
? CachedNetworkImage(
|
||||
imageUrl: _avatarUrl!,
|
||||
fadeInDuration: Duration.zero, // Corta o atraso visual!
|
||||
imageBuilder: (context, imageProvider) => CircleAvatar(
|
||||
backgroundColor: Colors.white.withOpacity(0.2),
|
||||
backgroundImage: imageProvider,
|
||||
),
|
||||
placeholder: (context, url) => CircleAvatar(backgroundColor: Colors.white.withOpacity(0.2)),
|
||||
errorWidget: (context, url, error) => CircleAvatar(
|
||||
backgroundColor: Colors.white.withOpacity(0.2),
|
||||
child: Icon(Icons.person, color: Colors.white, size: 20 * context.sf),
|
||||
),
|
||||
)
|
||||
// Se não tiver foto nenhuma, aí sim mostra o boneco
|
||||
: CircleAvatar(
|
||||
backgroundColor: Colors.white.withOpacity(0.2),
|
||||
child: Icon(Icons.person, color: Colors.white, size: 20 * context.sf),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -144,14 +177,15 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
itemBuilder: (context, index) {
|
||||
final team = teams[index];
|
||||
return ListTile(
|
||||
title: Text(team['name'], style: TextStyle(color: Theme.of(context).colorScheme.onSurface)),
|
||||
leading: const Icon(Icons.shield, color: AppTheme.primaryRed),
|
||||
title: Text(team['name'], style: TextStyle(color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.bold)),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedTeamId = team['id'];
|
||||
_selectedTeamId = team['id'].toString();
|
||||
_selectedTeamName = team['name'];
|
||||
_teamWins = team['wins'] != null ? int.tryParse(team['wins'].toString()) ?? 0 : 0;
|
||||
_teamLosses = team['losses'] != null ? int.tryParse(team['losses'].toString()) ?? 0 : 0;
|
||||
_teamDraws = team['draws'] != null ? int.tryParse(team['draws'].toString()) ?? 0 : 0;
|
||||
_teamWins = int.tryParse(team['wins']?.toString() ?? '0') ?? 0;
|
||||
_teamLosses = int.tryParse(team['losses']?.toString() ?? '0') ?? 0;
|
||||
_teamDraws = int.tryParse(team['draws']?.toString() ?? '0') ?? 0;
|
||||
});
|
||||
Navigator.pop(context);
|
||||
},
|
||||
@@ -251,9 +285,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
color: Theme.of(context).cardTheme.color ?? Colors.white,
|
||||
borderRadius: BorderRadius.circular(16 * context.sf),
|
||||
border: Border.all(color: Colors.grey.withOpacity(0.1)),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 10, offset: const Offset(0, 4)),
|
||||
],
|
||||
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.04), blurRadius: 10, offset: const Offset(0, 4))],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -263,10 +295,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
child: Icon(Icons.shield_outlined, color: AppTheme.primaryRed, size: 42 * context.sf),
|
||||
),
|
||||
SizedBox(height: 20 * context.sf),
|
||||
Text(
|
||||
"Nenhuma Equipa Ativa",
|
||||
style: TextStyle(fontSize: 18 * context.sf, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
Text("Nenhuma Equipa Ativa", style: TextStyle(fontSize: 18 * context.sf, fontWeight: FontWeight.bold, color: textColor)),
|
||||
SizedBox(height: 8 * context.sf),
|
||||
Text(
|
||||
"Escolha uma equipa no seletor acima para ver as estatísticas e o histórico.",
|
||||
@@ -293,8 +322,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
),
|
||||
)
|
||||
: StreamBuilder<List<Map<String, dynamic>>>(
|
||||
stream: _supabase.from('games').stream(primaryKey: ['id'])
|
||||
.order('game_date', ascending: false),
|
||||
stream: _supabase.from('games').stream(primaryKey: ['id']).order('game_date', ascending: false),
|
||||
builder: (context, gameSnapshot) {
|
||||
if (gameSnapshot.hasError) return Text("Erro: ${gameSnapshot.error}", style: const TextStyle(color: Colors.red));
|
||||
if (gameSnapshot.connectionState == ConnectionState.waiting) return const Center(child: CircularProgressIndicator());
|
||||
@@ -309,6 +337,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
|
||||
if (gamesList.isEmpty) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(20 * context.sf),
|
||||
decoration: BoxDecoration(color: Theme.of(context).cardTheme.color, borderRadius: BorderRadius.circular(14)),
|
||||
alignment: Alignment.center,
|
||||
@@ -362,9 +391,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
for (var row in data) {
|
||||
String pid = row['member_id'].toString();
|
||||
namesMap[pid] = row['player_name']?.toString() ?? "Desconhecido";
|
||||
ptsMap[pid] = (ptsMap[pid] ?? 0) + (row['pts'] as int? ?? 0);
|
||||
astMap[pid] = (astMap[pid] ?? 0) + (row['ast'] as int? ?? 0);
|
||||
rbsMap[pid] = (rbsMap[pid] ?? 0) + (row['rbs'] as int? ?? 0);
|
||||
ptsMap[pid] = (ptsMap[pid] ?? 0) + (int.tryParse(row['pts']?.toString() ?? '0') ?? 0);
|
||||
astMap[pid] = (astMap[pid] ?? 0) + (int.tryParse(row['ast']?.toString() ?? '0') ?? 0);
|
||||
rbsMap[pid] = (rbsMap[pid] ?? 0) + (int.tryParse(row['rbs']?.toString() ?? '0') ?? 0);
|
||||
}
|
||||
if (ptsMap.isEmpty) return {'pts_name': '---', 'pts_val': 0, 'ast_name': '---', 'ast_val': 0, 'rbs_name': '---', 'rbs_val': 0};
|
||||
String getBest(Map<String, int> map) { var bestId = map.entries.reduce((a, b) => a.value > b.value ? a : b).key; return namesMap[bestId]!; }
|
||||
|
||||
Reference in New Issue
Block a user