From f8e3a7686fae009bf205aea4414412d41eafd08a Mon Sep 17 00:00:00 2001 From: 240403 <240403@epvc.pt> Date: Sat, 16 May 2026 15:03:44 +0100 Subject: [PATCH] Escolher permissao no upload de conteudo --- .../pages/teacher_materials_page.dart | 228 +++++++++++++++++- 1 file changed, 216 insertions(+), 12 deletions(-) diff --git a/lib/features/materials/presentation/pages/teacher_materials_page.dart b/lib/features/materials/presentation/pages/teacher_materials_page.dart index e30bbaa..63a9461 100644 --- a/lib/features/materials/presentation/pages/teacher_materials_page.dart +++ b/lib/features/materials/presentation/pages/teacher_materials_page.dart @@ -178,6 +178,7 @@ class _TeacherMaterialsPageState extends State { final docId = materials[index].id; final url = material['url'] as String?; + final classId = material['classId'] as String?; return _buildMaterialCard( docId: docId, @@ -185,6 +186,7 @@ class _TeacherMaterialsPageState extends State { fileType: fileType, createdAt: createdAt, url: url, + classId: classId, ); }, ); @@ -355,7 +357,7 @@ class _TeacherMaterialsPageState extends State { final XFile? file = await openFile(acceptedTypeGroups: [pdfTypeGroup]); if (file == null) return; - await _uploadFile( + await _pickClassAndUpload( filePath: file.path, fileName: path.basename(file.path), fileType: 'pdf', @@ -377,7 +379,7 @@ class _TeacherMaterialsPageState extends State { ); if (image == null) return; - await _uploadFile( + await _pickClassAndUpload( filePath: image.path, fileName: path.basename(image.path), fileType: 'image', @@ -399,7 +401,7 @@ class _TeacherMaterialsPageState extends State { ); if (photo == null) return; - await _uploadFile( + await _pickClassAndUpload( filePath: photo.path, fileName: path.basename(photo.path), fileType: 'image', @@ -411,10 +413,148 @@ class _TeacherMaterialsPageState extends State { } } + Future _pickClassAndUpload({ + required String filePath, + required String fileName, + required String fileType, + }) async { + final currentUser = AuthService.currentUser; + if (currentUser == null) { + _showErrorSnackBar('Utilizador não autenticado'); + return; + } + + // Carregar as turmas do professor + List> teacherClasses = []; + try { + final snapshot = await _firestore + .collection('classes') + .where('teacherId', isEqualTo: currentUser.uid) + .orderBy('createdAt', descending: true) + .get(); + teacherClasses = snapshot.docs.map((doc) { + final data = doc.data(); + return {'id': doc.id, 'name': (data['name'] as String? ?? doc.id)}; + }).toList(); + } catch (_) {} + + if (!mounted) return; + + // Se o professor não tem turmas, fazer upload sem associar turma + if (teacherClasses.isEmpty) { + await _uploadFile( + filePath: filePath, + fileName: fileName, + fileType: fileType, + ); + return; + } + + // Mostrar diálogo de seleção de turma + String? selectedClassId = await showDialog( + context: context, + builder: (dialogContext) { + String? picked; + return StatefulBuilder( + builder: (context, setDialogState) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + title: const Text( + 'Escolher Turma', + style: TextStyle(fontWeight: FontWeight.bold), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Seleciona a turma que terá acesso a este material:', + style: TextStyle(fontSize: 14), + ), + const SizedBox(height: 16), + DropdownButtonFormField( + value: picked, + isExpanded: true, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ), + ), + hint: const Text('Seleciona a turma'), + items: teacherClasses + .map( + (c) => DropdownMenuItem( + value: c['id'], + child: Text(c['name']!), + ), + ) + .toList(), + onChanged: (value) => + setDialogState(() => picked = value), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(null), + child: const Text('Cancelar'), + ), + ElevatedButton( + onPressed: picked == null + ? null + : () => Navigator.of(dialogContext).pop(picked), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFF68D2D), + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + child: const Text('Confirmar'), + ), + ], + ), + ); + }, + ); + + // Se cancelou o diálogo, não fazer upload + if (selectedClassId == null) return; + + await _uploadFile( + filePath: filePath, + fileName: fileName, + fileType: fileType, + classId: selectedClassId, + ); + } + Future _uploadFile({ required String filePath, required String fileName, required String fileType, + String? classId, }) async { final currentUser = FirebaseAuth.instance.currentUser; if (currentUser == null) { @@ -443,12 +583,18 @@ class _TeacherMaterialsPageState extends State { final downloadUrl = await ref.getDownloadURL(); // Criar documento no Firestore - await FirebaseFirestore.instance.collection('materials').add({ + final materialData = { 'teacherId': uid, 'fileName': cleanFileName, 'url': downloadUrl, 'createdAt': FieldValue.serverTimestamp(), - }); + }; + if (classId != null && classId.isNotEmpty) { + materialData['classId'] = classId; + } + await FirebaseFirestore.instance + .collection('materials') + .add(materialData); if (mounted) { _showSuccessSnackBar('Material enviado com sucesso!'); @@ -569,12 +715,25 @@ class _TeacherMaterialsPageState extends State { ); } + Future _getClassName(String classId) async { + try { + final doc = await _firestore.collection('classes').doc(classId).get(); + if (doc.exists) { + return doc.data()?['name'] as String?; + } + return null; + } catch (_) { + return null; + } + } + Widget _buildMaterialCard({ required String docId, required String fileName, required String fileType, required Timestamp? createdAt, String? url, + String? classId, }) { IconData iconData; Color iconColor; @@ -635,13 +794,58 @@ class _TeacherMaterialsPageState extends State { maxLines: 1, overflow: TextOverflow.ellipsis, ), - subtitle: Text( - formattedDate, - style: const TextStyle( - color: Color(0xFF718096), - fontSize: 13, - ), - ), + subtitle: classId != null + ? FutureBuilder( + future: _getClassName(classId), + builder: (context, snap) { + final className = snap.data; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + formattedDate, + style: const TextStyle( + color: Color(0xFF718096), + fontSize: 13, + ), + ), + if (className != null) ...[ + const SizedBox(height: 2), + Row( + children: [ + const Icon( + Icons.school_outlined, + size: 12, + color: Color(0xFF82C9BD), + ), + const SizedBox(width: 4), + Flexible( + child: Text( + className, + style: const TextStyle( + color: Color(0xFF82C9BD), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ], + ); + }, + ) + : Text( + formattedDate, + style: const TextStyle( + color: Color(0xFF718096), + fontSize: 13, + ), + ), trailing: IconButton( icon: const Icon(Icons.delete_outline, color: Colors.red), tooltip: 'Eliminar',