From 5cd496cd43319f3d3e1f856b3bb41c59e1123025 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 11 Mar 2026 19:25:53 +0000 Subject: [PATCH] Eliminar creche_app/lib/features/profile/profile_screen.dart --- .../lib/features/profile/profile_screen.dart | 335 ------------------ 1 file changed, 335 deletions(-) delete mode 100644 creche_app/lib/features/profile/profile_screen.dart diff --git a/creche_app/lib/features/profile/profile_screen.dart b/creche_app/lib/features/profile/profile_screen.dart deleted file mode 100644 index 3dcb651..0000000 --- a/creche_app/lib/features/profile/profile_screen.dart +++ /dev/null @@ -1,335 +0,0 @@ -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), - ), - ); -}