Alteração do acesso às settings, mudança visual na interface de entrar numa turma

This commit is contained in:
2026-05-17 14:34:19 +01:00
parent 51ea446ae9
commit 5649f7d96a
7 changed files with 924 additions and 344 deletions

View File

@@ -1,16 +1,17 @@
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 turma usando o código
class JoinClassPage extends StatefulWidget {
class JoinClassPage extends ConsumerStatefulWidget {
const JoinClassPage({super.key});
@override
State<JoinClassPage> createState() => _JoinClassPageState();
ConsumerState<JoinClassPage> createState() => _JoinClassPageState();
}
class _JoinClassPageState extends State<JoinClassPage> {
class _JoinClassPageState extends ConsumerState<JoinClassPage> {
final _codeController = TextEditingController();
bool _isLoading = false;
@@ -67,8 +68,7 @@ class _JoinClassPageState extends State<JoinClassPage> {
final classDoc = classQuery.docs.first;
final classId = classDoc.id;
final classSchoolClassId =
classDoc.data()['schoolClassId'] as String?;
final classSchoolClassId = classDoc.data()['schoolClassId'] as String?;
// Verificar se o aluno está autorizado a entrar nesta turma
// O schoolClassId do aluno deve corresponder ao schoolClassId da disciplina
@@ -112,11 +112,22 @@ class _JoinClassPageState extends State<JoinClassPage> {
// Mostrar sucesso
if (mounted) {
final colorScheme = Theme.of(context).colorScheme;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Entraste na turma com sucesso!'),
backgroundColor: Color(0xFF10B981),
duration: Duration(seconds: 2),
SnackBar(
content: Row(
children: [
Icon(Icons.check_circle, color: Colors.white),
const SizedBox(width: 8),
const Text('Entraste na turma com sucesso!'),
],
),
backgroundColor: colorScheme.primary,
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
);
@@ -130,149 +141,436 @@ class _JoinClassPageState extends State<JoinClassPage> {
}
void _showError(String message) {
final colorScheme = Theme.of(context).colorScheme;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: const Color(0xFFEF4444),
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(
backgroundColor: const Color(0xFFF8F9FA),
appBar: AppBar(
backgroundColor: const Color(0xFF82C9BD),
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
),
title: const Text(
'Entrar numa Turma',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
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),
],
),
),
),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Ícone e descrição
Center(
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFF82C9BD).withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(
Icons.group_add,
color: Color(0xFF82C9BD),
size: 40,
),
),
),
const SizedBox(height: 24),
const Center(
child: Text(
'Insere o código da turma',
style: TextStyle(
color: Color(0xFF2D3748),
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 8),
Center(
child: Text(
'O professor partilhou contigo um código de 6 caracteres.',
style: TextStyle(color: Colors.grey[600], fontSize: 14),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 32),
// Campo de código
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE2E8F0), width: 1),
),
child: TextField(
controller: _codeController,
textCapitalization: TextCapitalization.characters,
maxLength: 6,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 4,
color: Color(0xFF2D3748),
),
textAlign: TextAlign.center,
decoration: InputDecoration(
hintText: 'XXXXXX',
hintStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 4,
color: Colors.grey[400],
),
border: InputBorder.none,
contentPadding: const EdgeInsets.all(20),
counterText: '',
),
),
),
const SizedBox(height: 24),
// Botão de entrar
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: _isLoading ? null : _joinClass,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF82C9BD),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
disabledBackgroundColor: const Color(
0xFF82C9BD,
).withOpacity(0.5),
),
child: _isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Text(
'Entrar na Turma',
child: SafeArea(
child: Column(
children: [
// Custom AppBar
Container(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
IconButton(
icon: Icon(
Icons.arrow_back,
color: colorScheme.onSurface,
),
onPressed: () => Navigator.of(context).pop(),
),
Expanded(
child: Text(
'Entrar numa Turma',
style: TextStyle(
fontSize: 16,
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 turma',
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 turma',
colorScheme,
),
const SizedBox(height: 8),
_buildInstructionItem(
context,
'2.',
'Inserir o código no campo abaixo',
colorScheme,
),
const SizedBox(height: 8),
_buildInstructionItem(
context,
'3.',
'Clicar em "Entrar na Turma" 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: 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(
'Entrar na Turma',
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),
Text(
'Ajuda - Código da Turma',
style: TextStyle(color: colorScheme.onSurface),
),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'O código da turma é um código único de 6 caracteres que o teu professor cria para cada turma.',
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),
),
),
],
),
);
}
}