diff --git a/lib/features/ai_tutor/presentation/pages/tutor_chat_page.dart b/lib/features/ai_tutor/presentation/pages/tutor_chat_page.dart index 4924036..9f35e5a 100644 --- a/lib/features/ai_tutor/presentation/pages/tutor_chat_page.dart +++ b/lib/features/ai_tutor/presentation/pages/tutor_chat_page.dart @@ -327,7 +327,8 @@ class _TutorChatPageState extends State void _addWelcomeMessage() { final welcomeMessage = { - 'content': '''**Olá! Sou o GOAT, o teu Assistente IA oficial do Teach it.** 🐐 + 'content': + '''**Olá! Sou a Alt, o teu Assistente IA oficial do Teach it.** Estou aqui para te ajudar a aprender de forma confiante e motivadora! diff --git a/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart b/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart index b57afdf..e7a6988 100644 --- a/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart +++ b/lib/features/ai_tutor/presentation/pages/tutor_chat_page_simple.dart @@ -36,7 +36,8 @@ class _TutorChatPageSimpleState extends State } Future _loadAvailableMaterials() async { - final materials = await MaterialsRAGService.getAvailableMaterialsForStudent(); + final materials = + await MaterialsRAGService.getAvailableMaterialsForStudent(); if (mounted) { setState(() => _availableMaterials = materials); } @@ -86,7 +87,7 @@ class _TutorChatPageSimpleState extends State crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'AI Study Assistant', + 'Assistente de Estudo AI', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -374,13 +375,21 @@ class _TutorChatPageSimpleState extends State ), decoration: BoxDecoration( color: _selectedMaterialIds.isEmpty - ? Theme.of(context).colorScheme.outline.withOpacity(0.15) - : Theme.of(context).colorScheme.primary.withOpacity(0.12), + ? Theme.of( + context, + ).colorScheme.outline.withOpacity(0.15) + : Theme.of( + context, + ).colorScheme.primary.withOpacity(0.12), borderRadius: BorderRadius.circular(20), border: Border.all( color: _selectedMaterialIds.isEmpty - ? Theme.of(context).colorScheme.outline.withOpacity(0.4) - : Theme.of(context).colorScheme.primary.withOpacity(0.5), + ? Theme.of( + context, + ).colorScheme.outline.withOpacity(0.4) + : Theme.of( + context, + ).colorScheme.primary.withOpacity(0.5), ), ), child: Row( @@ -390,7 +399,9 @@ class _TutorChatPageSimpleState extends State Icons.attach_file, size: 14, color: _selectedMaterialIds.isEmpty - ? Theme.of(context).colorScheme.onSurfaceVariant + ? Theme.of( + context, + ).colorScheme.onSurfaceVariant : Theme.of(context).colorScheme.primary, ), const SizedBox(width: 4), @@ -401,8 +412,12 @@ class _TutorChatPageSimpleState extends State style: TextStyle( fontSize: 12, color: _selectedMaterialIds.isEmpty - ? Theme.of(context).colorScheme.onSurfaceVariant - : Theme.of(context).colorScheme.primary, + ? Theme.of( + context, + ).colorScheme.onSurfaceVariant + : Theme.of( + context, + ).colorScheme.primary, fontWeight: FontWeight.w500, ), ), @@ -410,10 +425,10 @@ class _TutorChatPageSimpleState extends State ), ), ), - if (_selectedMaterialIds.isNotEmpty) ... - _selectedMaterialIds.map((id) { - final name = _availableMaterials - .firstWhere( + if (_selectedMaterialIds.isNotEmpty) + ..._selectedMaterialIds.map((id) { + final name = + _availableMaterials.firstWhere( (m) => m['id'] == id, orElse: () => {'id': id, 'name': id}, )['name'] ?? @@ -460,34 +475,34 @@ class _TutorChatPageSimpleState extends State // Text field Expanded( child: TextField( - controller: _messageController, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).colorScheme.onSurface, - fontWeight: FontWeight.w500, - ), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 14, - ), - hintText: 'Faz a tua pergunta!', - hintStyle: TextStyle( - color: Theme.of(context).colorScheme.onSurfaceVariant, - fontWeight: FontWeight.w400, - fontSize: 16, + controller: _messageController, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + hintText: 'Faz a tua pergunta!', + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w400, + fontSize: 16, + ), + ), + onSubmitted: (_) => _handleSendMessage(), + textInputAction: TextInputAction.send, + onChanged: (value) { + setState(() {}); + }, ), ), - onSubmitted: (_) => _handleSendMessage(), - textInputAction: TextInputAction.send, - onChanged: (value) { - setState(() {}); - }, - ), - ), - // Send button + // Send button Padding( padding: const EdgeInsets.only(right: 8.0), child: Container( @@ -508,7 +523,9 @@ class _TutorChatPageSimpleState extends State : null, color: _messageController.text.isNotEmpty ? null - : Theme.of(context).colorScheme.outline.withOpacity(0.3), + : Theme.of( + context, + ).colorScheme.outline.withOpacity(0.3), borderRadius: BorderRadius.circular(22), boxShadow: _messageController.text.isNotEmpty ? [ @@ -523,7 +540,8 @@ class _TutorChatPageSimpleState extends State : null, ), child: IconButton( - onPressed: _messageController.text.isNotEmpty && !_isLoading + onPressed: + _messageController.text.isNotEmpty && !_isLoading ? _handleSendMessage : null, icon: _isLoading diff --git a/lib/features/quiz/presentation/pages/quiz_list_page.dart b/lib/features/quiz/presentation/pages/quiz_list_page.dart index 1c43b1a..86fa793 100644 --- a/lib/features/quiz/presentation/pages/quiz_list_page.dart +++ b/lib/features/quiz/presentation/pages/quiz_list_page.dart @@ -32,8 +32,12 @@ class _QuizListPageState extends State // disciplina seleccionada no tab "Gerar Quiz" (null = vista de disciplinas) String? _selectedMaterialDisciplineId; + // disciplina seleccionada no tab "Histórico" (null = vista de disciplinas) + String? _selectedHistoryDisciplineId; // disciplina seleccionada no tab "Do Professor" (null = vista de disciplinas) String? _selectedDisciplineId; + // classId → name para o histórico + Map _historyClassNames = {}; // generating state String? _generatingForId; @@ -261,9 +265,92 @@ class _QuizListPageState extends State .limit(30) .get(); final list = snap.docs.map((d) => {'id': d.id, ...d.data()}).toList(); + + // Resolver nomes de disciplinas para o histórico + // 1) Usar _materialClassNames (já carregados) para quizzes gerados pelo aluno + // 2) Para teacher quizzes, buscar classId via teacherQuizzes collection + final histClassNames = Map.from(_materialClassNames); + + // Enriquecer items de histórico com classId se em falta + final teacherQuizIds = list + .where((e) => e['teacherQuizId'] != null && e['classId'] == null) + .map((e) => e['teacherQuizId'] as String) + .toSet(); + if (teacherQuizIds.isNotEmpty) { + final tqDocs = await Future.wait( + teacherQuizIds.map( + (id) => FirebaseFirestore.instance + .collection('teacherQuizzes') + .doc(id) + .get(), + ), + ); + for (final doc in tqDocs.where((d) => d.exists)) { + final cids = doc.data()?['classIds'] as List?; + if (cids != null && cids.isNotEmpty) { + final cid = cids.first as String; + // Associar teacherQuizId → classId + for (final item in list) { + if (item['teacherQuizId'] == doc.id && item['classId'] == null) { + item['classId'] = cid; + } + } + // Buscar nome da classe se ainda não temos + if (!histClassNames.containsKey(cid)) { + final classDoc = await FirebaseFirestore.instance + .collection('classes') + .doc(cid) + .get(); + if (classDoc.exists) { + histClassNames[cid] = + classDoc.data()?['name'] as String? ?? cid; + } + } + } + } + } + + // Para quizzes gerados pelo aluno sem classId: inferir pelo materialId + final materialIds = list + .where((e) => e['materialId'] != null && e['classId'] == null) + .map((e) => e['materialId'] as String) + .toSet(); + if (materialIds.isNotEmpty) { + // Procurar nos materiais já carregados + for (final mat in _materials) { + if (materialIds.contains(mat['id']) && mat['classId'] != null) { + for (final item in list) { + if (item['materialId'] == mat['id'] && item['classId'] == null) { + item['classId'] = mat['classId']; + } + } + } + } + // Buscar nomes de classes extra + final extraCids = list + .map((e) => e['classId'] as String?) + .whereType() + .where((id) => !histClassNames.containsKey(id)) + .toSet(); + if (extraCids.isNotEmpty) { + final docs = await Future.wait( + extraCids.map( + (id) => FirebaseFirestore.instance + .collection('classes') + .doc(id) + .get(), + ), + ); + for (final doc in docs.where((d) => d.exists)) { + histClassNames[doc.id] = doc.data()?['name'] as String? ?? doc.id; + } + } + } + if (mounted) setState(() { _history = list; + _historyClassNames = histClassNames; _loadingHistory = false; }); } catch (e) { @@ -651,7 +738,7 @@ class _QuizListPageState extends State Future _previewPDF(Map mat, String name) async { final matId = mat['id']!; final matName = name.replaceAll('.pdf', '').replaceAll('_', ' '); - + // Mostrar loading showDialog( context: context, @@ -666,25 +753,30 @@ class _QuizListPageState extends State ), ), ); - + try { // Obter o texto completo do PDF usando o método existente final teacherId = mat['teacherId']; if (teacherId == null) { Navigator.of(context).pop(); - _showSnack('Erro: não foi possível identificar o professor do material.'); + _showSnack( + 'Erro: não foi possível identificar o professor do material.', + ); return; } - - final fullText = await MaterialsRAGService.getFullPDFText(matName, teacherId); - + + final fullText = await MaterialsRAGService.getFullPDFText( + matName, + teacherId, + ); + Navigator.of(context).pop(); // Fechar loading - + if (fullText.isEmpty) { _showSnack('Não foi possível carregar o conteúdo do PDF.'); return; } - + // Mostrar o conteúdo em um diálogo scrollável melhorado showModalBottomSheet( context: context, @@ -711,14 +803,19 @@ class _QuizListPageState extends State height: 4, margin: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.4), + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant.withOpacity(0.4), borderRadius: BorderRadius.circular(2), ), ), - + // Header melhorado Container( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 16, + ), decoration: BoxDecoration( gradient: LinearGradient( colors: [ @@ -728,7 +825,9 @@ class _QuizListPageState extends State begin: Alignment.topLeft, end: Alignment.bottomRight, ), - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + borderRadius: const BorderRadius.vertical( + top: Radius.circular(24), + ), ), child: Column( children: [ @@ -740,9 +839,15 @@ class _QuizListPageState extends State decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white.withOpacity(0.3)), + border: Border.all( + color: Colors.white.withOpacity(0.3), + ), + ), + child: Icon( + Icons.picture_as_pdf, + color: Colors.white, + size: 24, ), - child: Icon(Icons.picture_as_pdf, color: Colors.white, size: 24), ), const SizedBox(width: 16), Expanded( @@ -777,24 +882,37 @@ class _QuizListPageState extends State ), child: IconButton( onPressed: () => Navigator.of(context).pop(), - icon: const Icon(Icons.close, color: Colors.white, size: 20), + icon: const Icon( + Icons.close, + color: Colors.white, + size: 20, + ), ), ), ], ), const SizedBox(height: 16), - + // Stats bar Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.white.withOpacity(0.2)), + border: Border.all( + color: Colors.white.withOpacity(0.2), + ), ), child: Row( children: [ - Icon(Icons.description, color: Colors.white.withOpacity(0.9), size: 18), + Icon( + Icons.description, + color: Colors.white.withOpacity(0.9), + size: 18, + ), const SizedBox(width: 8), Expanded( child: Text( @@ -807,7 +925,10 @@ class _QuizListPageState extends State ), ), Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(8), @@ -827,32 +948,45 @@ class _QuizListPageState extends State ], ), ), - + // Content area melhorado Expanded( child: Container( margin: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHighest, + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), border: Border.all( - color: Theme.of(context).colorScheme.outline.withOpacity(0.1), + color: Theme.of( + context, + ).colorScheme.outline.withOpacity(0.1), ), ), child: Column( children: [ // Content header Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHighest, - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), + ), ), child: Row( children: [ - Icon(Icons.text_fields, - color: Theme.of(context).colorScheme.primary, - size: 18), + Icon( + Icons.text_fields, + color: Theme.of(context).colorScheme.primary, + size: 18, + ), const SizedBox(width: 8), Text( 'Conteúdo do Material', @@ -864,9 +998,14 @@ class _QuizListPageState extends State ), const Spacer(), Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + color: Theme.of( + context, + ).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( @@ -881,7 +1020,7 @@ class _QuizListPageState extends State ], ), ), - + // Text content Expanded( child: Container( @@ -904,25 +1043,31 @@ class _QuizListPageState extends State ), ), ), - + // Footer melhorado Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceVariant, - borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(24), + ), ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + color: Theme.of( + context, + ).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), - child: Icon(Icons.info_outline, - color: Theme.of(context).colorScheme.primary, - size: 16), + child: Icon( + Icons.info_outline, + color: Theme.of(context).colorScheme.primary, + size: 16, + ), ), const SizedBox(width: 12), Expanded( @@ -933,7 +1078,9 @@ class _QuizListPageState extends State 'Texto extraído automaticamente do PDF', style: TextStyle( fontSize: 12, - color: Theme.of(context).colorScheme.onSurfaceVariant, + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, fontWeight: FontWeight.w500, ), ), @@ -942,15 +1089,19 @@ class _QuizListPageState extends State 'Formatação otimizada para melhor legibilidade', style: TextStyle( fontSize: 11, - color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.8), + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant.withOpacity(0.8), ), ), ], ), ), - Icon(Icons.keyboard_arrow_up, - color: Theme.of(context).colorScheme.onSurfaceVariant, - size: 16), + Icon( + Icons.keyboard_arrow_up, + color: Theme.of(context).colorScheme.onSurfaceVariant, + size: 16, + ), ], ), ), @@ -958,7 +1109,6 @@ class _QuizListPageState extends State ), ), ); - } catch (e) { Logger.error('Error previewing PDF: $e'); Navigator.of(context).pop(); // Fechar loading @@ -1089,6 +1239,10 @@ class _QuizListPageState extends State setState(() => _selectedMaterialDisciplineId = null); return; } + if (_selectedHistoryDisciplineId != null) { + setState(() => _selectedHistoryDisciplineId = null); + return; + } if (_selectedDisciplineId != null) { setState(() => _selectedDisciplineId = null); return; @@ -1107,6 +1261,8 @@ class _QuizListPageState extends State onPressed: () { if (_selectedMaterialDisciplineId != null) { setState(() => _selectedMaterialDisciplineId = null); + } else if (_selectedHistoryDisciplineId != null) { + setState(() => _selectedHistoryDisciplineId = null); } else if (_selectedDisciplineId != null) { setState(() => _selectedDisciplineId = null); } else { @@ -1349,16 +1505,13 @@ class _QuizListPageState extends State ColorScheme cs, ) { final cleanName = name.replaceAll('.pdf', '').replaceAll('_', ' '); - + return Container( margin: const EdgeInsets.only(bottom: 4), decoration: BoxDecoration( color: cs.surface, borderRadius: BorderRadius.circular(20), - border: Border.all( - color: cs.outline.withOpacity(0.08), - width: 1, - ), + border: Border.all(color: cs.outline.withOpacity(0.08), width: 1), boxShadow: [ BoxShadow( color: cs.shadow.withOpacity(0.04), @@ -1376,7 +1529,9 @@ class _QuizListPageState extends State color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(20), - onTap: isGenerating ? null : () => _showMaterialOptions(mat, name, cs), + onTap: isGenerating + ? null + : () => _showMaterialOptions(mat, name, cs), child: Padding( padding: const EdgeInsets.all(16), child: Row( @@ -1403,11 +1558,7 @@ class _QuizListPageState extends State child: Stack( alignment: Alignment.center, children: [ - Icon( - Icons.picture_as_pdf, - color: cs.secondary, - size: 26, - ), + Icon(Icons.picture_as_pdf, color: cs.secondary, size: 26), if (!isGenerating) Positioned( bottom: 2, @@ -1429,9 +1580,9 @@ class _QuizListPageState extends State ], ), ), - + const SizedBox(width: 16), - + // Content Expanded( child: Column( @@ -1450,7 +1601,10 @@ class _QuizListPageState extends State ), const SizedBox(height: 6), Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), decoration: BoxDecoration( color: cs.primaryContainer.withOpacity(0.5), borderRadius: BorderRadius.circular(12), @@ -1458,11 +1612,7 @@ class _QuizListPageState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - Icons.touch_app, - size: 12, - color: cs.primary, - ), + Icon(Icons.touch_app, size: 12, color: cs.primary), const SizedBox(width: 4), Text( isGenerating ? 'A gerar...' : 'Toca para opções', @@ -1478,18 +1628,18 @@ class _QuizListPageState extends State ], ), ), - + // Action indicator Container( width: 40, height: 40, decoration: BoxDecoration( - color: isGenerating + color: isGenerating ? cs.primary.withOpacity(0.1) : cs.surfaceVariant.withOpacity(0.5), borderRadius: BorderRadius.circular(12), border: Border.all( - color: isGenerating + color: isGenerating ? cs.primary.withOpacity(0.2) : cs.outline.withOpacity(0.1), ), @@ -1519,9 +1669,13 @@ class _QuizListPageState extends State ); } - void _showMaterialOptions(Map mat, String name, ColorScheme cs) { + void _showMaterialOptions( + Map mat, + String name, + ColorScheme cs, + ) { final cleanName = name.replaceAll('.pdf', '').replaceAll('_', ' '); - + showModalBottomSheet( context: context, backgroundColor: Colors.transparent, @@ -1552,7 +1706,7 @@ class _QuizListPageState extends State borderRadius: BorderRadius.circular(3), ), ), - + // Header melhorado com gradient Container( margin: const EdgeInsets.symmetric(horizontal: 24), @@ -1567,9 +1721,7 @@ class _QuizListPageState extends State end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), - border: Border.all( - color: cs.outline.withOpacity(0.1), - ), + border: Border.all(color: cs.outline.withOpacity(0.1)), ), child: Row( children: [ @@ -1620,9 +1772,9 @@ class _QuizListPageState extends State ], ), ), - + const SizedBox(width: 16), - + // Title section Expanded( child: Column( @@ -1641,7 +1793,10 @@ class _QuizListPageState extends State ), const SizedBox(height: 6), Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), decoration: BoxDecoration( color: cs.primaryContainer.withOpacity(0.6), borderRadius: BorderRadius.circular(20), @@ -1661,19 +1816,15 @@ class _QuizListPageState extends State ], ), ), - + const SizedBox(height: 24), - + // Instructions Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( children: [ - Icon( - Icons.lightbulb_outline, - color: cs.tertiary, - size: 20, - ), + Icon(Icons.lightbulb_outline, color: cs.tertiary, size: 20), const SizedBox(width: 8), Expanded( child: Text( @@ -1688,9 +1839,9 @@ class _QuizListPageState extends State ], ), ), - + const SizedBox(height: 20), - + // Options melhoradas Padding( padding: const EdgeInsets.symmetric(horizontal: 24), @@ -1714,9 +1865,9 @@ class _QuizListPageState extends State _previewPDF(mat, name); }, ), - + const SizedBox(height: 16), - + // Generate Quiz option com design premium _buildPremiumOptionTile( icon: Icons.quiz_rounded, @@ -1738,7 +1889,7 @@ class _QuizListPageState extends State ], ), ), - + const SizedBox(height: 32), ], ), @@ -1765,10 +1916,7 @@ class _QuizListPageState extends State decoration: BoxDecoration( gradient: gradient, borderRadius: BorderRadius.circular(20), - border: Border.all( - color: color.withOpacity(0.2), - width: 1.5, - ), + border: Border.all(color: color.withOpacity(0.2), width: 1.5), boxShadow: [ BoxShadow( color: color.withOpacity(0.1), @@ -1786,10 +1934,7 @@ class _QuizListPageState extends State decoration: BoxDecoration( color: color.withOpacity(0.15), borderRadius: BorderRadius.circular(16), - border: Border.all( - color: color.withOpacity(0.3), - width: 1, - ), + border: Border.all(color: color.withOpacity(0.3), width: 1), ), child: Stack( alignment: Alignment.center, @@ -1815,9 +1960,9 @@ class _QuizListPageState extends State ], ), ), - + const SizedBox(width: 16), - + // Text content Expanded( child: Column( @@ -1852,7 +1997,7 @@ class _QuizListPageState extends State ], ), ), - + // Arrow indicator Container( width: 32, @@ -1861,11 +2006,7 @@ class _QuizListPageState extends State color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), - child: Icon( - Icons.chevron_right, - color: color, - size: 18, - ), + child: Icon(Icons.chevron_right, color: color, size: 18), ), ], ), @@ -1918,10 +2059,7 @@ class _QuizListPageState extends State const SizedBox(height: 2), Text( subtitle, - style: TextStyle( - fontSize: 12, - color: cs.onSurfaceVariant, - ), + style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant), ), ], ), @@ -1933,6 +2071,18 @@ class _QuizListPageState extends State ); } + Map>> _groupHistoryByDiscipline() { + final groups = >>{}; + for (final item in _history) { + final cid = item['classId'] as String?; + final groupId = (cid != null && _historyClassNames.containsKey(cid)) + ? cid + : '__geral__'; + groups.putIfAbsent(groupId, () => []).add(item); + } + return groups; + } + Widget _buildHistoryTab(ColorScheme cs) { if (_loadingHistory) { return const Center(child: CircularProgressIndicator()); @@ -1960,12 +2110,140 @@ class _QuizListPageState extends State ), ); } + + final groups = _groupHistoryByDiscipline(); + final realDisciplineIds = groups.keys + .where((k) => k != '__geral__' && _historyClassNames.containsKey(k)) + .toList(); + + // Sem disciplinas reais ou só 1 → lista plana + if (realDisciplineIds.length <= 1) { + return _buildHistoryList(cs, _history); + } + + // Vista de quizzes de uma disciplina + if (_selectedHistoryDisciplineId != null) { + final items = groups[_selectedHistoryDisciplineId] ?? []; + final dName = + _historyClassNames[_selectedHistoryDisciplineId] ?? + _selectedHistoryDisciplineId!; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 16, 0), + child: Row( + children: [ + IconButton( + icon: Icon(Icons.arrow_back, color: cs.onSurface), + onPressed: () => + setState(() => _selectedHistoryDisciplineId = null), + ), + const SizedBox(width: 4), + Expanded( + child: Text( + dName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: cs.onSurface, + ), + ), + ), + Text( + '${items.length} quiz${items.length != 1 ? 'zes' : ''}', + style: TextStyle(fontSize: 13, color: cs.onSurfaceVariant), + ), + ], + ), + ), + const Divider(height: 1), + Expanded(child: _buildHistoryList(cs, items)), + ], + ); + } + + // Vista de disciplinas return ListView.separated( padding: const EdgeInsets.all(16), - itemCount: _history.length, + itemCount: realDisciplineIds.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, i) { - final item = _history[i]; + final dId = realDisciplineIds[i]; + final dName = _historyClassNames[dId] ?? dId; + final count = groups[dId]!.length; + return InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () => setState(() => _selectedHistoryDisciplineId = dId), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: cs.surface, + borderRadius: BorderRadius.circular(16), + border: Border.all(color: cs.outline.withValues(alpha: 0.15)), + boxShadow: [ + BoxShadow( + color: cs.shadow.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: cs.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(Icons.history_edu, color: cs.primary, size: 26), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + dName, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: cs.onSurface, + ), + ), + const SizedBox(height: 4), + Text( + '$count quiz${count != 1 ? 'zes' : ''}', + style: TextStyle( + fontSize: 13, + color: cs.onSurfaceVariant, + ), + ), + ], + ), + ), + Icon(Icons.chevron_right, color: cs.onSurfaceVariant), + ], + ), + ), + ); + }, + ); + } + + Widget _buildHistoryList(ColorScheme cs, List> items) { + return ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: items.length, + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (context, i) { + final item = items[i]; final matName = (item['materialName'] as String? ?? 'Material') .replaceAll('.pdf', '') .replaceAll('_', ' ');