diff --git a/lib/features/classes/presentation/pages/class_students_page.dart b/lib/features/classes/presentation/pages/class_students_page.dart index d1cc66a..878f800 100644 --- a/lib/features/classes/presentation/pages/class_students_page.dart +++ b/lib/features/classes/presentation/pages/class_students_page.dart @@ -61,29 +61,23 @@ class _ClassStudentsPageState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + if (_isCheckingAccess) { - return const Scaffold( - backgroundColor: Color(0xFFF8F9FA), - body: Center( - child: CircularProgressIndicator(color: Color(0xFF82C9BD)), - ), + return Scaffold( + backgroundColor: cs.surface, + body: Center(child: CircularProgressIndicator(color: cs.primary)), ); } if (!_accessGranted) { return Scaffold( - backgroundColor: const Color(0xFFF8F9FA), + backgroundColor: cs.surface, appBar: AppBar( - backgroundColor: const Color(0xFF82C9BD), + backgroundColor: cs.surface, + foregroundColor: cs.onSurface, elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), - title: const Text( - 'Acesso Negado', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - ), + title: const Text('Acesso Negado'), ), body: Center( child: Padding( @@ -91,16 +85,12 @@ class _ClassStudentsPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.lock_outline, - size: 64, - color: Color(0xFF82C9BD), - ), + Icon(Icons.lock_outline, size: 64, color: cs.primary), const SizedBox(height: 24), - const Text( + Text( 'Sem permissão', style: TextStyle( - color: Color(0xFF2D3748), + color: cs.onSurface, fontSize: 20, fontWeight: FontWeight.bold, ), @@ -108,7 +98,7 @@ class _ClassStudentsPageState extends State { const SizedBox(height: 12), Text( 'Só podes ver os alunos das tuas próprias disciplinas.', - style: TextStyle(color: Colors.grey[600], fontSize: 14), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14), textAlign: TextAlign.center, ), ], @@ -119,31 +109,28 @@ class _ClassStudentsPageState extends State { } return Scaffold( - backgroundColor: const Color(0xFFF8F9FA), + backgroundColor: cs.surface, appBar: AppBar( - backgroundColor: const Color(0xFF82C9BD), + backgroundColor: cs.surface, + foregroundColor: cs.onSurface, elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), - onPressed: () => Navigator.of(context).pop(), - ), title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.className, - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.bold, ), ), - const Text( + Text( 'Alunos Matriculados', style: TextStyle( - color: Colors.white70, + color: cs.onSurfaceVariant, fontSize: 13, - fontWeight: FontWeight.w300, + fontWeight: FontWeight.w400, ), ), ], @@ -157,9 +144,7 @@ class _ClassStudentsPageState extends State { .snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: CircularProgressIndicator(color: Color(0xFF82C9BD)), - ); + return Center(child: CircularProgressIndicator(color: cs.primary)); } if (snapshot.hasError) { @@ -167,11 +152,15 @@ class _ClassStudentsPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.error_outline, size: 48, color: Colors.grey[400]), + Icon( + Icons.error_outline, + size: 48, + color: cs.onSurfaceVariant, + ), const SizedBox(height: 16), Text( 'Erro ao carregar alunos', - style: TextStyle(color: Colors.grey[600], fontSize: 16), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 16), ), ], ), @@ -182,33 +171,44 @@ class _ClassStudentsPageState extends State { if (enrollments.isEmpty) { return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.people_outline, size: 64, color: Colors.grey[300]), - const SizedBox(height: 24), - Text( - 'Nenhum aluno entrou nesta disciplina ainda.', - style: TextStyle(color: Colors.grey[600], fontSize: 16), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: Text( - 'Partilha o código da disciplina para os alunos se juntarem.', - style: TextStyle(color: Colors.grey[500], fontSize: 13), + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.people_outline, + size: 64, + color: cs.onSurfaceVariant.withValues(alpha: 0.4), + ), + const SizedBox(height: 24), + Text( + 'Nenhum aluno entrou nesta disciplina ainda.', + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 16, + ), textAlign: TextAlign.center, ), - ), - ], + const SizedBox(height: 8), + Text( + 'Partilha o código da disciplina para os alunos se juntarem.', + style: TextStyle( + color: cs.onSurfaceVariant.withValues(alpha: 0.7), + fontSize: 13, + ), + textAlign: TextAlign.center, + ), + ], + ), ), ); } - return ListView.builder( - padding: const EdgeInsets.all(16.0), + return ListView.separated( + padding: const EdgeInsets.all(16), itemCount: enrollments.length, + separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, index) { final enrollment = enrollments[index].data() as Map; @@ -217,52 +217,51 @@ class _ClassStudentsPageState extends State { final joinedAt = enrollment['joinedAt'] as Timestamp?; return Container( - margin: const EdgeInsets.only(bottom: 12.0), decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16.0), + color: cs.surface, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline.withValues(alpha: 0.15)), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), + color: cs.shadow.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), ), ], ), child: ListTile( - contentPadding: const EdgeInsets.all(16.0), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), leading: Container( width: 48, height: 48, decoration: BoxDecoration( - color: const Color(0xFF82C9BD).withOpacity(0.1), - borderRadius: BorderRadius.circular(12.0), - ), - child: const Icon( - Icons.person, - color: Color(0xFF82C9BD), - size: 24, + color: cs.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), ), + child: Icon(Icons.person, color: cs.primary, size: 24), ), title: Text( studentName, - style: const TextStyle( - color: Color(0xFF2D3748), - fontSize: 16, + style: TextStyle( + color: cs.onSurface, + fontSize: 15, fontWeight: FontWeight.w600, ), ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text( - joinedAt != null - ? 'Entrou em ${_formatDate(joinedAt.toDate())}' - : 'Data desconhecida', - style: TextStyle(color: Colors.grey[600], fontSize: 13), + subtitle: Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + joinedAt != null + ? 'Entrou em ${_formatDate(joinedAt.toDate())}' + : 'Data desconhecida', + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 13, ), - ], + ), ), ), ); diff --git a/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart b/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart index 56d92bd..de53412 100644 --- a/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart +++ b/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart @@ -56,11 +56,30 @@ class TeacherClassesListWidget extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'As Minhas Disciplinas', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: Theme.of(context).colorScheme.onSurface, - fontWeight: FontWeight.bold, + InkWell( + onTap: () => _showClassesList(context, classes), + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'As Minhas Disciplinas', + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Icon( + Icons.expand_more, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 22, + ), + ], + ), ), ), const SizedBox(height: 16), @@ -94,6 +113,119 @@ class TeacherClassesListWidget extends StatelessWidget { ); } + void _showClassesList( + BuildContext context, + List classes, + ) { + final cs = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) => DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 0.9, + expand: false, + builder: (context, scrollController) { + return Column( + children: [ + Container( + margin: const EdgeInsets.only(top: 8), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'As Minhas Disciplinas', + style: TextStyle( + color: cs.onSurface, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: classes.length, + itemBuilder: (context, index) { + final data = classes[index].data() as Map; + final classId = classes[index].id; + final className = data['name'] as String? ?? 'Sem nome'; + final classCode = data['code'] as String? ?? '----'; + return Card( + margin: const EdgeInsets.only(bottom: 12), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide(color: cs.outline.withOpacity(0.2)), + ), + child: ListTile( + leading: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: cs.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.school, + color: cs.primary, + size: 24, + ), + ), + title: Text( + className, + style: TextStyle( + fontWeight: FontWeight.bold, + color: cs.onSurface, + ), + ), + subtitle: Text( + 'Código: $classCode', + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 13, + ), + ), + trailing: Icon( + Icons.arrow_forward_ios, + color: cs.primary, + size: 16, + ), + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ClassStudentsPage( + classId: classId, + className: className, + ), + ), + ); + }, + ), + ); + }, + ), + ), + const SizedBox(height: 20), + ], + ); + }, + ), + ); + } + Widget _buildClassCard(DocumentSnapshot doc, BuildContext context) { final data = doc.data() as Map; final classId = doc.id; diff --git a/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart b/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart index 37925ff..fced2cb 100644 --- a/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart +++ b/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart @@ -22,8 +22,8 @@ class _TeacherQuickActionsWidgetState extends State { /// Mesmas dimensões dos cards em "As Minhas Disciplinas". static const double _scrollCardWidth = 200; - static const double _scrollRowHeight = 150; - static const double _cardMinHeight = 150; + static const double _scrollRowHeight = 156; + static const double _cardMinHeight = 156; static const EdgeInsets _cardPadding = EdgeInsets.all(16); static const double _titleFontSize = 16; static const double _subtitleFontSize = 13; @@ -42,12 +42,30 @@ class _TeacherQuickActionsWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Ações Rápidas', - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: 20, - fontWeight: FontWeight.bold, + InkWell( + onTap: () => _showQuickActionsList(context), + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Ações Rápidas', + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 8), + Icon( + Icons.expand_more, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 22, + ), + ], + ), ), ), const SizedBox(height: 12), @@ -167,6 +185,147 @@ class _TeacherQuickActionsWidgetState extends State { ); } + void _showQuickActionsList(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final items = [ + _TeacherActionItem( + title: 'Upload Conteúdo', + subtitle: 'PDFs, textos, imagens', + icon: Icons.upload_file, + useGradient: true, + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const TeacherMaterialsPage()), + ); + }, + ), + _TeacherActionItem( + title: 'Criar Disciplina', + subtitle: 'Gerar código de acesso', + icon: Icons.school, + onTap: () { + Navigator.pop(context); + _showCreateClassDialog(context); + }, + ), + _TeacherActionItem( + title: 'Criar Quiz', + subtitle: 'Avaliações interativas', + icon: Icons.quiz, + onTap: () { + Navigator.pop(context); + context.go('/teacher/quiz/create'); + }, + ), + _TeacherActionItem( + title: 'Analytics', + subtitle: 'Desempenho da disciplina', + icon: Icons.analytics, + onTap: () { + Navigator.pop(context); + context.go('/teacher/analytics'); + }, + ), + ]; + + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) => DraggableScrollableSheet( + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 0.9, + expand: false, + builder: (context, scrollController) { + return Column( + children: [ + Container( + margin: const EdgeInsets.only(top: 8), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Text( + 'Ações Rápidas', + style: TextStyle( + color: cs.onSurface, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + Expanded( + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.symmetric(horizontal: 16), + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; + return Card( + margin: const EdgeInsets.only(bottom: 12), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide(color: cs.outline.withOpacity(0.2)), + ), + child: ListTile( + leading: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: item.useGradient + ? cs.primary.withOpacity(0.1) + : cs.secondary.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + item.icon, + color: item.useGradient ? cs.primary : cs.secondary, + size: 24, + ), + ), + title: Text( + item.title, + style: TextStyle( + fontWeight: FontWeight.bold, + color: cs.onSurface, + ), + ), + subtitle: Text( + item.subtitle, + style: TextStyle( + color: cs.onSurfaceVariant, + fontSize: 13, + ), + ), + trailing: Icon( + Icons.arrow_forward_ios, + color: cs.primary, + size: 16, + ), + onTap: item.onTap, + ), + ); + }, + ), + ), + const SizedBox(height: 20), + ], + ); + }, + ), + ); + } + void _showCreateClassDialog(BuildContext context) { final TextEditingController nameController = TextEditingController(); String? selectedSchoolClassId; @@ -442,3 +601,19 @@ class _TeacherQuickActionsWidgetState extends State { } } } + +class _TeacherActionItem { + final String title; + final String subtitle; + final IconData icon; + final bool useGradient; + final VoidCallback onTap; + + _TeacherActionItem({ + required this.title, + required this.subtitle, + required this.icon, + this.useGradient = false, + required this.onTap, + }); +}