import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:uuid/uuid.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '/core/auth_provider.dart'; import '/core/supabase_client.dart'; import '/shared/widgets/custom_button.dart'; const _bg = Color(0xFF0D1117); const _card = Color(0xFF161B22); const _blue = Color(0xFF4FC3F7); String _roleLabel(String r) { switch (r) { case 'principal': return 'Diretora'; case 'admin': return 'Administrador'; case 'teacher': return 'Educadora'; case 'staff': return 'Auxiliar'; case 'parent': return 'Encarregado'; default: return r; } } Color _roleColor(String r) { switch (r) { case 'principal': return const Color(0xFFFFD700); case 'admin': return const Color(0xFFFF7043); case 'teacher': return _blue; case 'staff': return const Color(0xFFA5D6A7); case 'parent': return const Color(0xFFFFB300); default: return Colors.grey; } } class ProfileScreen extends ConsumerStatefulWidget { const ProfileScreen({super.key}); @override ConsumerState createState() => _ProfileScreenState(); } class _ProfileScreenState extends ConsumerState { final _nameCtrl = TextEditingController(); final _phoneCtrl = TextEditingController(); final _newPassCtrl = TextEditingController(); final _confPassCtrl = TextEditingController(); bool _isSaving = false; bool _changingPw = false; bool _showPwForm = false; bool _obscureNew = true; @override void dispose() { _nameCtrl.dispose(); _phoneCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final profileAsync = ref.watch(currentProfileProvider); return profileAsync.when( data: (profile) { if (profile == null) { return const Scaffold(backgroundColor: _bg, body: Center(child: Text('Perfil não encontrado', style: TextStyle(color: Colors.white)))); } // só preenche se vazio (evita reset ao rebuild) if (_nameCtrl.text.isEmpty) _nameCtrl.text = profile.fullName; if (_phoneCtrl.text.isEmpty) _phoneCtrl.text = profile.phone ?? ''; final roleColor = _roleColor(profile.role); return Scaffold( backgroundColor: _bg, appBar: AppBar( backgroundColor: _card, title: const Text('O meu perfil', style: TextStyle(color: _blue, fontWeight: FontWeight.bold)), elevation: 0, ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column(children: [ // ── Avatar ───────────────────────────────────────── Center(child: Stack(children: [ Container( width: 100, height: 100, decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all(color: roleColor.withOpacity(0.5), width: 2.5), color: roleColor.withOpacity(0.1), ), child: profile.avatarUrl != null ? ClipOval(child: Image.network(profile.avatarUrl!, fit: BoxFit.cover)) : Center(child: Text( profile.fullName.isNotEmpty ? profile.fullName[0].toUpperCase() : 'U', style: TextStyle(color: roleColor, fontSize: 38, fontWeight: FontWeight.bold))), ), Positioned(bottom: 0, right: 0, child: GestureDetector( onTap: () => _pickAvatar(profile.id), child: Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration(color: _blue, shape: BoxShape.circle, border: Border.all(color: _bg, width: 2)), child: const Icon(Icons.camera_alt, color: Colors.white, size: 16), ), )), ])), const SizedBox(height: 12), // Role badge (só visualização — não pode mudar) Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: roleColor.withOpacity(0.12), borderRadius: BorderRadius.circular(20), border: Border.all(color: roleColor.withOpacity(0.3)), ), child: Text(_roleLabel(profile.role).toUpperCase(), style: TextStyle(color: roleColor, fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 1.2)), ), const SizedBox(height: 6), Text('A tua função é atribuída pela Diretora', style: TextStyle(color: Colors.white.withOpacity(0.3), fontSize: 11)), const SizedBox(height: 28), // ── Dados pessoais ───────────────────────────────── _Section(title: 'Dados Pessoais', children: [ _Field(ctrl: _nameCtrl, label: 'Nome completo', icon: Icons.person_outline), const SizedBox(height: 14), _Field(ctrl: _phoneCtrl, label: 'Telefone', icon: Icons.phone_outlined, type: TextInputType.phone), const SizedBox(height: 14), // Email — só leitura Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: Colors.white.withOpacity(0.03), borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.white.withOpacity(0.06)), ), child: Row(children: [ Icon(Icons.alternate_email, color: _blue.withOpacity(0.5), size: 19), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Email', style: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 11)), const SizedBox(height: 2), Text(Supabase.instance.client.auth.currentUser?.email ?? '', style: const TextStyle(color: Colors.white70, fontSize: 14)), ])), const Icon(Icons.lock_outline, color: Colors.white24, size: 14), ]), ), const SizedBox(height: 20), CustomButton(text: 'Guardar Alterações', isLoading: _isSaving, onPressed: () => _save(profile.id), icon: Icons.save_outlined), ]), const SizedBox(height: 16), // ── Alterar Senha ────────────────────────────────── _Section( title: 'Segurança', trailing: TextButton( onPressed: () => setState(() => _showPwForm = !_showPwForm), child: Text(_showPwForm ? 'Cancelar' : 'Alterar senha', style: const TextStyle(color: _blue, fontSize: 12)), ), children: [ if (!_showPwForm) Text('Podes alterar a tua senha a qualquer momento.', style: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 13)) else ...[ _Field(ctrl: _newPassCtrl, label: 'Nova senha', icon: Icons.lock_outline, obscure: _obscureNew, suffix: IconButton( icon: Icon(_obscureNew ? Icons.visibility_off : Icons.visibility, color: Colors.white38, size: 18), onPressed: () => setState(() => _obscureNew = !_obscureNew), )), const SizedBox(height: 12), _Field(ctrl: _confPassCtrl, label: 'Confirmar nova senha', icon: Icons.lock_outline, obscure: _obscureNew), const SizedBox(height: 16), CustomButton(text: 'Actualizar Senha', isLoading: _changingPw, onPressed: _changePassword, icon: Icons.security), ], ], ), const SizedBox(height: 16), // ── Sair ─────────────────────────────────────────── GestureDetector( onTap: () async { await ref.read(authNotifierProvider.notifier).signOut(); if (context.mounted) context.go('/login'); }, child: Container( height: 50, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.red.withOpacity(0.4)), color: Colors.red.withOpacity(0.06), ), child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.logout, color: Colors.red, size: 18), SizedBox(width: 8), Text('Terminar Sessão', style: TextStyle(color: Colors.red, fontSize: 14, fontWeight: FontWeight.w500)), ]), ), ), const SizedBox(height: 32), ]), ), ); }, loading: () => const Scaffold(backgroundColor: _bg, body: Center(child: CircularProgressIndicator(color: _blue))), error: (e, _) => Scaffold(backgroundColor: _bg, body: Center(child: Text('Erro: $e', style: const TextStyle(color: Colors.red)))), ); } Future _pickAvatar(String profileId) async { final picker = ImagePicker(); final file = await picker.pickImage(source: ImageSource.gallery, imageQuality: 70); if (file == null) return; final supabase = ref.read(supabaseProvider); final bytes = await file.readAsBytes(); final path = 'avatars/${const Uuid().v4()}.jpg'; await supabase.storage.from('photos').uploadBinary(path, bytes); final url = supabase.storage.from('photos').getPublicUrl(path); await supabase.from('profiles').update({'avatar_url': url}).eq('id', profileId); ref.invalidate(currentProfileProvider); } Future _save(String profileId) async { setState(() => _isSaving = true); try { final supabase = ref.read(supabaseProvider); await supabase.from('profiles').update({ 'full_name': _nameCtrl.text.trim(), 'phone': _phoneCtrl.text.trim(), // NÃO inclui 'role' — utilizador não pode mudar o próprio role }).eq('id', profileId); ref.invalidate(currentProfileProvider); if (mounted) _snack('Perfil actualizado! ✓', ok: true); } catch (e) { if (mounted) _snack('Erro: $e'); } finally { if (mounted) setState(() => _isSaving = false); } } Future _changePassword() async { final newPass = _newPassCtrl.text; final confPass = _confPassCtrl.text; if (newPass.length < 6) { _snack('A senha deve ter pelo menos 6 caracteres.'); return; } if (newPass != confPass) { _snack('As senhas não coincidem.'); return; } setState(() => _changingPw = true); try { await Supabase.instance.client.auth.updateUser(UserAttributes(password: newPass)); _newPassCtrl.clear(); _confPassCtrl.clear(); setState(() => _showPwForm = false); _snack('Senha alterada com sucesso! ✓', ok: true); } catch (e) { _snack('Erro ao alterar senha: $e'); } finally { if (mounted) setState(() => _changingPw = false); } } void _snack(String msg, {bool ok = false}) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(msg, style: const TextStyle(color: Colors.white)), backgroundColor: ok ? const Color(0xFF2ECC71) : Colors.red, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), )); } } class _Section extends StatelessWidget { final String title; final Widget? trailing; final List children; const _Section({required this.title, required this.children, this.trailing}); @override Widget build(BuildContext context) => Container( width: double.infinity, padding: const EdgeInsets.all(18), decoration: BoxDecoration( color: _card, borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.white.withOpacity(0.07)), ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Text(title, style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold)), const Spacer(), if (trailing != null) trailing!, ]), const SizedBox(height: 14), ...children, ]), ); } class _Field extends StatelessWidget { final TextEditingController ctrl; final String label; final IconData icon; final bool obscure; final TextInputType type; final Widget? suffix; const _Field({required this.ctrl, required this.label, required this.icon, this.obscure = false, this.type = TextInputType.text, this.suffix}); @override Widget build(BuildContext context) => TextField( controller: ctrl, obscureText: obscure, keyboardType: type, style: const TextStyle(color: Colors.white, fontSize: 14), decoration: InputDecoration( labelText: label, labelStyle: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 13), prefixIcon: Icon(icon, color: _blue.withOpacity(0.7), size: 19), suffixIcon: suffix, filled: true, fillColor: Colors.white.withOpacity(0.04), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: Colors.white.withOpacity(0.09))), focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: const BorderSide(color: _blue, width: 1.5)), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), ), ); }