Escolher permissao no upload de conteudo
This commit is contained in:
@@ -178,6 +178,7 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
|
|
||||||
final docId = materials[index].id;
|
final docId = materials[index].id;
|
||||||
final url = material['url'] as String?;
|
final url = material['url'] as String?;
|
||||||
|
final classId = material['classId'] as String?;
|
||||||
|
|
||||||
return _buildMaterialCard(
|
return _buildMaterialCard(
|
||||||
docId: docId,
|
docId: docId,
|
||||||
@@ -185,6 +186,7 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
fileType: fileType,
|
fileType: fileType,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
url: url,
|
url: url,
|
||||||
|
classId: classId,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -355,7 +357,7 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
final XFile? file = await openFile(acceptedTypeGroups: [pdfTypeGroup]);
|
final XFile? file = await openFile(acceptedTypeGroups: [pdfTypeGroup]);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
|
||||||
await _uploadFile(
|
await _pickClassAndUpload(
|
||||||
filePath: file.path,
|
filePath: file.path,
|
||||||
fileName: path.basename(file.path),
|
fileName: path.basename(file.path),
|
||||||
fileType: 'pdf',
|
fileType: 'pdf',
|
||||||
@@ -377,7 +379,7 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
);
|
);
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
|
|
||||||
await _uploadFile(
|
await _pickClassAndUpload(
|
||||||
filePath: image.path,
|
filePath: image.path,
|
||||||
fileName: path.basename(image.path),
|
fileName: path.basename(image.path),
|
||||||
fileType: 'image',
|
fileType: 'image',
|
||||||
@@ -399,7 +401,7 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
);
|
);
|
||||||
if (photo == null) return;
|
if (photo == null) return;
|
||||||
|
|
||||||
await _uploadFile(
|
await _pickClassAndUpload(
|
||||||
filePath: photo.path,
|
filePath: photo.path,
|
||||||
fileName: path.basename(photo.path),
|
fileName: path.basename(photo.path),
|
||||||
fileType: 'image',
|
fileType: 'image',
|
||||||
@@ -411,10 +413,148 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _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<Map<String, String>> 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<String>(
|
||||||
|
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<String>(
|
||||||
|
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<String>(
|
||||||
|
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<void> _uploadFile({
|
Future<void> _uploadFile({
|
||||||
required String filePath,
|
required String filePath,
|
||||||
required String fileName,
|
required String fileName,
|
||||||
required String fileType,
|
required String fileType,
|
||||||
|
String? classId,
|
||||||
}) async {
|
}) async {
|
||||||
final currentUser = FirebaseAuth.instance.currentUser;
|
final currentUser = FirebaseAuth.instance.currentUser;
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
@@ -443,12 +583,18 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
final downloadUrl = await ref.getDownloadURL();
|
final downloadUrl = await ref.getDownloadURL();
|
||||||
|
|
||||||
// Criar documento no Firestore
|
// Criar documento no Firestore
|
||||||
await FirebaseFirestore.instance.collection('materials').add({
|
final materialData = <String, dynamic>{
|
||||||
'teacherId': uid,
|
'teacherId': uid,
|
||||||
'fileName': cleanFileName,
|
'fileName': cleanFileName,
|
||||||
'url': downloadUrl,
|
'url': downloadUrl,
|
||||||
'createdAt': FieldValue.serverTimestamp(),
|
'createdAt': FieldValue.serverTimestamp(),
|
||||||
});
|
};
|
||||||
|
if (classId != null && classId.isNotEmpty) {
|
||||||
|
materialData['classId'] = classId;
|
||||||
|
}
|
||||||
|
await FirebaseFirestore.instance
|
||||||
|
.collection('materials')
|
||||||
|
.add(materialData);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_showSuccessSnackBar('Material enviado com sucesso!');
|
_showSuccessSnackBar('Material enviado com sucesso!');
|
||||||
@@ -569,12 +715,25 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> _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({
|
Widget _buildMaterialCard({
|
||||||
required String docId,
|
required String docId,
|
||||||
required String fileName,
|
required String fileName,
|
||||||
required String fileType,
|
required String fileType,
|
||||||
required Timestamp? createdAt,
|
required Timestamp? createdAt,
|
||||||
String? url,
|
String? url,
|
||||||
|
String? classId,
|
||||||
}) {
|
}) {
|
||||||
IconData iconData;
|
IconData iconData;
|
||||||
Color iconColor;
|
Color iconColor;
|
||||||
@@ -635,13 +794,58 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: classId != null
|
||||||
formattedDate,
|
? FutureBuilder<String?>(
|
||||||
style: const TextStyle(
|
future: _getClassName(classId),
|
||||||
color: Color(0xFF718096),
|
builder: (context, snap) {
|
||||||
fontSize: 13,
|
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(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
icon: const Icon(Icons.delete_outline, color: Colors.red),
|
||||||
tooltip: 'Eliminar',
|
tooltip: 'Eliminar',
|
||||||
|
|||||||
Reference in New Issue
Block a user