import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/services/auth_service.dart'; /// Página para o aluno entrar numa disciplina usando o código class JoinClassPage extends ConsumerStatefulWidget { const JoinClassPage({super.key}); @override ConsumerState createState() => _JoinClassPageState(); } class _JoinClassPageState extends ConsumerState { final _codeController = TextEditingController(); final _nameController = TextEditingController(); bool _isLoading = false; @override void dispose() { _codeController.dispose(); _nameController.dispose(); super.dispose(); } Future _joinClass() async { final code = _codeController.text.trim().toUpperCase(); final customName = _nameController.text.trim(); if (code.isEmpty) { _showError('Insere o código da disciplina'); return; } if (customName.isEmpty) { _showError('Insere o nome da disciplina'); return; } setState(() => _isLoading = true); try { final currentUser = AuthService.currentUser; if (currentUser == null) { setState(() => _isLoading = false); _showError('Erro: Utilizador não autenticado'); return; } // Verificar role — apenas alunos podem entrar por código final userRole = await AuthService.getUserRole(currentUser.uid); if (userRole != 'student') { setState(() => _isLoading = false); _showError('Apenas alunos podem entrar em disciplinas por código.'); return; } // Procurar disciplina pelo código final classQuery = await FirebaseFirestore.instance .collection('classes') .where('code', isEqualTo: code) .limit(1) .get(); if (classQuery.docs.isEmpty) { setState(() => _isLoading = false); _showError('Código de disciplina inválido'); return; } final classDoc = classQuery.docs.first; final classId = classDoc.id; // Verificar se já está inscrito nesta disciplina final existingEnrollment = await FirebaseFirestore.instance .collection('enrollments') .where('classId', isEqualTo: classId) .where('studentId', isEqualTo: currentUser.uid) .limit(1) .get(); if (existingEnrollment.docs.isNotEmpty) { setState(() => _isLoading = false); _showError('Já estás inscrito nesta disciplina'); return; } // Criar documento de inscrição await FirebaseFirestore.instance.collection('enrollments').add({ 'classId': classId, 'studentId': currentUser.uid, 'studentName': currentUser.displayName ?? currentUser.email?.split('@')[0] ?? 'Aluno', 'customClassName': customName, 'joinedAt': FieldValue.serverTimestamp(), }); setState(() => _isLoading = false); // Mostrar sucesso if (mounted) { final colorScheme = Theme.of(context).colorScheme; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Icon(Icons.check_circle, color: Colors.white), const SizedBox(width: 8), const Text('Entraste na disciplina com sucesso!'), ], ), backgroundColor: colorScheme.primary, duration: const Duration(seconds: 2), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), ); // Voltar para a home Navigator.of(context).pop(); } } catch (e) { setState(() => _isLoading = false); _showError('Erro ao entrar na disciplina: $e'); } } void _showError(String message) { final colorScheme = Theme.of(context).colorScheme; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ Icon(Icons.error_outline, color: Colors.white), const SizedBox(width: 8), Expanded(child: Text(message)), ], ), backgroundColor: colorScheme.error, duration: const Duration(seconds: 3), behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), ); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final isDark = theme.brightness == Brightness.dark; return Scaffold( body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? [ colorScheme.surfaceContainerHighest, colorScheme.surface, colorScheme.surfaceContainerLow, ] : const [ Color(0xFFD4E8E8), Color(0xFFE8D4C0), Color(0xFFD8E0E8), ], ), ), child: SafeArea( top: false, child: Column( children: [ // Custom AppBar Container( padding: const EdgeInsets.only( left: 16.0, right: 16.0, bottom: 20.0, top: 52.0, ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( icon: Icon( Icons.arrow_back, color: colorScheme.onSurface, ), onPressed: () => Navigator.of(context).pop(), ), Expanded( child: Text( 'Adicionar uma Disciplina', style: TextStyle( color: colorScheme.onSurface, fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ], ), ), // Body content Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), // Header illustration card Center( child: Card( elevation: isDark ? 4 : 8, shadowColor: isDark ? Colors.black.withOpacity(0.4) : Colors.black.withOpacity(0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: Container( width: double.infinity, padding: const EdgeInsets.all(32), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: isDark ? [ colorScheme.primary.withOpacity(0.1), colorScheme.secondary.withOpacity(0.05), ] : [ colorScheme.primary.withOpacity(0.05), colorScheme.secondary.withOpacity(0.02), ], ), ), child: Column( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(20), border: Border.all( color: colorScheme.primary.withOpacity( 0.2, ), width: 2, ), ), child: Icon( Icons.group_add, color: colorScheme.primary, size: 40, ), ), const SizedBox(height: 24), Text( 'Insere o código da disciplina', style: theme.textTheme.headlineSmall ?.copyWith( color: colorScheme.onSurface, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), Text( 'O professor partilhou contigo um código de 6 caracteres.', style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), ), ), const SizedBox(height: 32), // Instructions card Card( elevation: isDark ? 2 : 4, shadowColor: isDark ? Colors.black.withOpacity(0.3) : Colors.black.withOpacity(0.08), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.info_outline, color: colorScheme.primary, size: 20, ), const SizedBox(width: 8), Text( 'Como funciona', style: theme.textTheme.titleMedium ?.copyWith( color: colorScheme.onSurface, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 12), _buildInstructionItem( context, '1.', 'Pedir ao professor o código da disciplina', colorScheme, ), const SizedBox(height: 8), _buildInstructionItem( context, '2.', 'Inserir o código no campo abaixo', colorScheme, ), const SizedBox(height: 8), _buildInstructionItem( context, '3.', 'Escrever o nome da disciplina', colorScheme, ), const SizedBox(height: 8), _buildInstructionItem( context, '4.', 'Clicar em "Adicionar uma Disciplina" para confirmar', colorScheme, ), ], ), ), ), const SizedBox(height: 32), // Campo de código Container( decoration: BoxDecoration( color: theme.cardColor, borderRadius: BorderRadius.circular(16), border: Border.all( color: colorScheme.outline.withOpacity(0.3), width: 1, ), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: TextField( controller: _codeController, textCapitalization: TextCapitalization.characters, maxLength: 6, style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, letterSpacing: 8, color: colorScheme.onSurface, ), textAlign: TextAlign.center, decoration: InputDecoration( hintText: 'XXXXXX', hintStyle: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, letterSpacing: 8, color: colorScheme.onSurfaceVariant.withOpacity( 0.5, ), ), border: InputBorder.none, contentPadding: const EdgeInsets.all(24), counterText: '', prefixIcon: Icon( Icons.vpn_key, color: colorScheme.primary, size: 24, ), prefixIconConstraints: const BoxConstraints( minWidth: 48, minHeight: 48, ), ), ), ), const SizedBox(height: 24), // Campo de nome da disciplina Container( decoration: BoxDecoration( color: theme.cardColor, borderRadius: BorderRadius.circular(16), border: Border.all( color: colorScheme.outline.withOpacity(0.3), width: 1, ), boxShadow: [ BoxShadow( color: isDark ? Colors.black.withOpacity(0.2) : Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: TextField( controller: _nameController, style: theme.textTheme.bodyLarge?.copyWith( color: colorScheme.onSurface, ), decoration: InputDecoration( hintText: 'Nome da disciplina', hintStyle: theme.textTheme.bodyLarge?.copyWith( color: colorScheme.onSurfaceVariant.withOpacity( 0.5, ), ), border: InputBorder.none, contentPadding: const EdgeInsets.all(20), prefixIcon: Icon( Icons.edit, color: colorScheme.primary, size: 24, ), prefixIconConstraints: const BoxConstraints( minWidth: 48, minHeight: 48, ), ), ), ), const SizedBox(height: 32), // Botão de entrar SizedBox( width: double.infinity, height: 56, child: ElevatedButton( onPressed: _isLoading ? null : _joinClass, style: ElevatedButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: colorScheme.onPrimary, elevation: 2, shadowColor: colorScheme.primary.withOpacity(0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), disabledBackgroundColor: colorScheme.primary .withOpacity(0.5), ), child: _isLoading ? SizedBox( width: 24, height: 24, child: CircularProgressIndicator( color: colorScheme.onPrimary, strokeWidth: 2, ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.login, size: 20, color: colorScheme.onPrimary, ), const SizedBox(width: 8), Text( 'Adicionar uma Disciplina', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: colorScheme.onPrimary, ), ), ], ), ), ), const SizedBox(height: 16), // Help text Center( child: TextButton.icon( onPressed: () { _showHelpDialog(context, colorScheme); }, icon: Icon( Icons.help_outline, size: 16, color: colorScheme.primary, ), label: Text( 'Precisas de ajuda?', style: TextStyle( color: colorScheme.primary, fontSize: 14, fontWeight: FontWeight.w500, ), ), ), ), ], ), ), ), ], ), ), ), ); } Widget _buildInstructionItem( BuildContext context, String number, String text, ColorScheme colorScheme, ) { final theme = Theme.of(context); return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 24, height: 24, decoration: BoxDecoration( color: colorScheme.primary, borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( number, style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 12), Expanded( child: Text( text, style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), ), ), ], ); } void _showHelpDialog(BuildContext context, ColorScheme colorScheme) { showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: Theme.of(context).cardColor, title: Row( children: [ Icon(Icons.help_outline, color: colorScheme.primary), const SizedBox(width: 8), Expanded( child: Text( 'Ajuda - Código da Disciplina', style: TextStyle(color: colorScheme.onSurface), ), ), ], ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'O código da disciplina é um código único de 6 caracteres que o teu professor cria para cada disciplina.', style: TextStyle(color: colorScheme.onSurfaceVariant), ), const SizedBox(height: 12), Text( 'Se não tens o código, contacta o teu professor.', style: TextStyle(color: colorScheme.onSurfaceVariant), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text( 'Entendido', style: TextStyle(color: colorScheme.primary), ), ), ], ), ); } }