import 'dart:io'; import 'package:flutter/material.dart'; import 'package:playmaker/classe/theme.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:image_picker/image_picker.dart'; import 'package:cached_network_image/cached_network_image.dart'; // 馃憞 IMPORTA脟脙O PARA CACHE import 'package:shared_preferences/shared_preferences.dart'; // 馃憞 IMPORTA脟脙O PARA MEM脫RIA R脕PIDA import '../utils/size_extension.dart'; import 'login.dart'; import '../main.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { File? _localImageFile; String? _uploadedImageUrl; bool _isUploadingImage = false; bool _isMemoryLoaded = false; // 馃憞 VARI脕VEL M脕GICA CONTRA O PISCAR final supabase = Supabase.instance.client; @override void initState() { super.initState(); _loadUserAvatar(); } // 馃憞 L脢 A IMAGEM DA MEM脫RIA INSTANTANEAMENTE E CONFIRMA NA BD Future _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) _uploadedImageUrl = savedUrl; _isMemoryLoaded = true; // Avisa que j谩 leu a mem贸ria }); } final userId = supabase.auth.currentUser?.id; if (userId == null) return; try { final data = await supabase .from('profiles') .select('avatar_url') .eq('id', userId) .maybeSingle(); if (mounted && data != null && data['avatar_url'] != null) { final urlDoSupabase = data['avatar_url']; // Atualiza a mem贸ria se a foto na base de dados for diferente if (urlDoSupabase != savedUrl) { await prefs.setString('meu_avatar_guardado', urlDoSupabase); setState(() { _uploadedImageUrl = urlDoSupabase; }); } } } catch (e) { print("Erro ao carregar avatar: $e"); } } Future _handleImageChange() async { final ImagePicker picker = ImagePicker(); final XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery); if (pickedFile == null || !mounted) return; try { setState(() { _localImageFile = File(pickedFile.path); _isUploadingImage = true; }); final userId = supabase.auth.currentUser?.id; if (userId == null) throw Exception("Utilizador n茫o autenticado."); final String storagePath = '$userId/profile_picture.png'; await supabase.storage.from('avatars').upload( storagePath, _localImageFile!, fileOptions: const FileOptions(cacheControl: '3600', upsert: true) ); final String publicUrl = supabase.storage.from('avatars').getPublicUrl(storagePath); await supabase .from('profiles') .upsert({ 'id': userId, 'avatar_url': publicUrl }); // 馃憞 M脕GICA: GUARDA LOGO O NOVO URL NA MEM脫RIA PARA A HOME SABER! final prefs = await SharedPreferences.getInstance(); await prefs.setString('meu_avatar_guardado', publicUrl); if (mounted) { setState(() { _uploadedImageUrl = publicUrl; _isUploadingImage = false; _localImageFile = null; }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text("Foto atualizada!"), backgroundColor: Colors.green) ); } } catch (e) { if (mounted) { setState(() { _isUploadingImage = false; _localImageFile = null; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Erro: $e"), backgroundColor: AppTheme.primaryRed) ); } } } @override Widget build(BuildContext context) { final Color primaryRed = AppTheme.primaryRed; final Color bgColor = Theme.of(context).scaffoldBackgroundColor; final Color cardColor = Theme.of(context).cardTheme.color ?? Theme.of(context).colorScheme.surface; final Color textColor = Theme.of(context).colorScheme.onSurface; final Color textLightColor = textColor.withOpacity(0.6); bool isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: bgColor, appBar: AppBar( backgroundColor: primaryRed, foregroundColor: Colors.white, elevation: 0, centerTitle: true, title: Text( "Perfil e Defini莽玫es", style: TextStyle(fontSize: 18 * context.sf, fontWeight: FontWeight.w600), ), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context), ), ), body: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 16.0 * context.sf, vertical: 24.0 * context.sf), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.all(20 * context.sf), decoration: BoxDecoration( color: cardColor, 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)), ], ), child: Row( children: [ _buildTappableProfileAvatar(context, primaryRed), SizedBox(width: 16 * context.sf), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Treinador", style: TextStyle(fontSize: 18 * context.sf, fontWeight: FontWeight.bold, color: textColor), ), SizedBox(height: 4 * context.sf), Text( supabase.auth.currentUser?.email ?? "sem@email.com", style: TextStyle(color: textLightColor, fontSize: 14 * context.sf), ), ], ), ), ], ), ), SizedBox(height: 32 * context.sf), Padding( padding: EdgeInsets.only(left: 4 * context.sf, bottom: 12 * context.sf), child: Text( "Defini莽玫es", style: TextStyle(color: textLightColor, fontSize: 14 * context.sf, fontWeight: FontWeight.bold), ), ), Container( decoration: BoxDecoration( color: cardColor, 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)), ], ), child: ListTile( contentPadding: EdgeInsets.symmetric(horizontal: 20 * context.sf, vertical: 8 * context.sf), leading: Icon(isDark ? Icons.dark_mode : Icons.light_mode, color: primaryRed, size: 28 * context.sf), title: Text( "Modo Escuro", style: TextStyle(fontWeight: FontWeight.bold, color: textColor, fontSize: 16 * context.sf), ), subtitle: Text( "Altera as cores da aplica莽茫o", style: TextStyle(color: textLightColor, fontSize: 13 * context.sf), ), trailing: Switch( value: isDark, activeColor: primaryRed, onChanged: (bool value) { themeNotifier.value = value ? ThemeMode.dark : ThemeMode.light; }, ), ), ), SizedBox(height: 32 * context.sf), Padding( padding: EdgeInsets.only(left: 4 * context.sf, bottom: 12 * context.sf), child: Text( "Conta", style: TextStyle(color: textLightColor, fontSize: 14 * context.sf, fontWeight: FontWeight.bold), ), ), Container( decoration: BoxDecoration( color: cardColor, 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)), ], ), child: ListTile( contentPadding: EdgeInsets.symmetric(horizontal: 20 * context.sf, vertical: 4 * context.sf), leading: Icon(Icons.logout_outlined, color: primaryRed, size: 26 * context.sf), title: Text( "Terminar Sess茫o", style: TextStyle(color: primaryRed, fontWeight: FontWeight.bold, fontSize: 15 * context.sf), ), onTap: () => _confirmLogout(context), ), ), SizedBox(height: 50 * context.sf), Center( child: Text( "PlayMaker v1.0.0", style: TextStyle(color: textLightColor.withOpacity(0.7), fontSize: 13 * context.sf), ), ), SizedBox(height: 20 * context.sf), ], ), ), ); } // 馃憞 AVATAR OTIMIZADO: SEM LAG, COM CACHE E MEM脫RIA Widget _buildTappableProfileAvatar(BuildContext context, Color primaryRed) { return GestureDetector( onTap: () { _handleImageChange(); }, child: Stack( alignment: Alignment.center, children: [ Container( width: 72 * context.sf, height: 72 * context.sf, decoration: BoxDecoration( color: primaryRed.withOpacity(0.1), shape: BoxShape.circle, ), child: ClipOval( child: _isUploadingImage && _localImageFile != null // 1. Mostrar imagem local (galeria) ENQUANTO est谩 a fazer upload ? Image.file(_localImageFile!, fit: BoxFit.cover) // 2. Antes da mem贸ria carregar, fica s贸 o fundo (evita piscar) : !_isMemoryLoaded ? const SizedBox() // 3. Depois da mem贸ria carregar, se houver URL, desenha com Cache! : _uploadedImageUrl != null && _uploadedImageUrl!.isNotEmpty ? CachedNetworkImage( imageUrl: _uploadedImageUrl!, fit: BoxFit.cover, fadeInDuration: Duration.zero, // Fica instant芒neo! placeholder: (context, url) => const SizedBox(), errorWidget: (context, url, error) => Icon(Icons.person, color: primaryRed, size: 36 * context.sf), ) // 4. Se n茫o houver URL, mete o boneco : Icon(Icons.person, color: primaryRed, size: 36 * context.sf), ), ), // 脥CONE DE L脕PIS Positioned( bottom: 0, right: 0, child: Container( padding: EdgeInsets.all(6 * context.sf), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, shape: BoxShape.circle, border: Border.all(color: Colors.grey.withOpacity(0.2)), ), child: Icon(Icons.edit_outlined, color: primaryRed, size: 16 * context.sf), ), ), // LOADING OVERLAY (Enquanto faz o upload) if (_isUploadingImage) Positioned.fill( child: Container( decoration: BoxDecoration(color: Colors.black.withOpacity(0.4), shape: BoxShape.circle), child: const Padding( padding: EdgeInsets.all(16.0), child: CircularProgressIndicator(color: Colors.white, strokeWidth: 3), ), ), ), ], ), ); } void _confirmLogout(BuildContext context) { showDialog( context: context, builder: (ctx) => AlertDialog( backgroundColor: Theme.of(context).colorScheme.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16 * context.sf)), title: Text("Terminar Sess茫o", style: TextStyle(color: Theme.of(context).colorScheme.onSurface, fontWeight: FontWeight.bold)), content: Text("Tens a certeza que queres sair da conta?", style: TextStyle(color: Theme.of(context).colorScheme.onSurface)), actions: [ TextButton(onPressed: () => Navigator.pop(ctx), child: const Text("Cancelar", style: TextStyle(color: Colors.grey))), ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: AppTheme.primaryRed, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), onPressed: () async { // Limpa a mem贸ria do Avatar ao sair para n茫o aparecer na conta de outra pessoa! final prefs = await SharedPreferences.getInstance(); await prefs.remove('meu_avatar_guardado'); await Supabase.instance.client.auth.signOut(); if (ctx.mounted) { Navigator.of(ctx).pushAndRemoveUntil( MaterialPageRoute(builder: (context) => const LoginPage()), (Route route) => false, ); } }, child: const Text("Sair", style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)) ), ], ), ); } }