Histórico a funcionar
This commit is contained in:
@@ -327,7 +327,8 @@ class _TutorChatPageState extends State<TutorChatPage>
|
|||||||
|
|
||||||
void _addWelcomeMessage() {
|
void _addWelcomeMessage() {
|
||||||
final welcomeMessage = {
|
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!
|
Estou aqui para te ajudar a aprender de forma confiante e motivadora!
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadAvailableMaterials() async {
|
Future<void> _loadAvailableMaterials() async {
|
||||||
final materials = await MaterialsRAGService.getAvailableMaterialsForStudent();
|
final materials =
|
||||||
|
await MaterialsRAGService.getAvailableMaterialsForStudent();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() => _availableMaterials = materials);
|
setState(() => _availableMaterials = materials);
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'AI Study Assistant',
|
'Assistente de Estudo AI',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -374,13 +375,21 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _selectedMaterialIds.isEmpty
|
color: _selectedMaterialIds.isEmpty
|
||||||
? Theme.of(context).colorScheme.outline.withOpacity(0.15)
|
? Theme.of(
|
||||||
: Theme.of(context).colorScheme.primary.withOpacity(0.12),
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.15)
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withOpacity(0.12),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _selectedMaterialIds.isEmpty
|
color: _selectedMaterialIds.isEmpty
|
||||||
? Theme.of(context).colorScheme.outline.withOpacity(0.4)
|
? Theme.of(
|
||||||
: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.4)
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -390,7 +399,9 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
Icons.attach_file,
|
Icons.attach_file,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: _selectedMaterialIds.isEmpty
|
color: _selectedMaterialIds.isEmpty
|
||||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
? Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant
|
||||||
: Theme.of(context).colorScheme.primary,
|
: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
@@ -401,8 +412,12 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: _selectedMaterialIds.isEmpty
|
color: _selectedMaterialIds.isEmpty
|
||||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
? Theme.of(
|
||||||
: Theme.of(context).colorScheme.primary,
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant
|
||||||
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -410,10 +425,10 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_selectedMaterialIds.isNotEmpty) ...
|
if (_selectedMaterialIds.isNotEmpty)
|
||||||
_selectedMaterialIds.map((id) {
|
..._selectedMaterialIds.map((id) {
|
||||||
final name = _availableMaterials
|
final name =
|
||||||
.firstWhere(
|
_availableMaterials.firstWhere(
|
||||||
(m) => m['id'] == id,
|
(m) => m['id'] == id,
|
||||||
orElse: () => {'id': id, 'name': id},
|
orElse: () => {'id': id, 'name': id},
|
||||||
)['name'] ??
|
)['name'] ??
|
||||||
@@ -460,34 +475,34 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
// Text field
|
// Text field
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _messageController,
|
controller: _messageController,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
vertical: 14,
|
vertical: 14,
|
||||||
),
|
),
|
||||||
hintText: 'Faz a tua pergunta!',
|
hintText: 'Faz a tua pergunta!',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => _handleSendMessage(),
|
||||||
|
textInputAction: TextInputAction.send,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onSubmitted: (_) => _handleSendMessage(),
|
|
||||||
textInputAction: TextInputAction.send,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Send button
|
// Send button
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -508,7 +523,9 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
: null,
|
: null,
|
||||||
color: _messageController.text.isNotEmpty
|
color: _messageController.text.isNotEmpty
|
||||||
? null
|
? null
|
||||||
: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.3),
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
boxShadow: _messageController.text.isNotEmpty
|
boxShadow: _messageController.text.isNotEmpty
|
||||||
? [
|
? [
|
||||||
@@ -523,7 +540,8 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: _messageController.text.isNotEmpty && !_isLoading
|
onPressed:
|
||||||
|
_messageController.text.isNotEmpty && !_isLoading
|
||||||
? _handleSendMessage
|
? _handleSendMessage
|
||||||
: null,
|
: null,
|
||||||
icon: _isLoading
|
icon: _isLoading
|
||||||
|
|||||||
@@ -32,8 +32,12 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
|
|
||||||
// disciplina seleccionada no tab "Gerar Quiz" (null = vista de disciplinas)
|
// disciplina seleccionada no tab "Gerar Quiz" (null = vista de disciplinas)
|
||||||
String? _selectedMaterialDisciplineId;
|
String? _selectedMaterialDisciplineId;
|
||||||
|
// disciplina seleccionada no tab "Histórico" (null = vista de disciplinas)
|
||||||
|
String? _selectedHistoryDisciplineId;
|
||||||
// disciplina seleccionada no tab "Do Professor" (null = vista de disciplinas)
|
// disciplina seleccionada no tab "Do Professor" (null = vista de disciplinas)
|
||||||
String? _selectedDisciplineId;
|
String? _selectedDisciplineId;
|
||||||
|
// classId → name para o histórico
|
||||||
|
Map<String, String> _historyClassNames = {};
|
||||||
|
|
||||||
// generating state
|
// generating state
|
||||||
String? _generatingForId;
|
String? _generatingForId;
|
||||||
@@ -261,9 +265,92 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
.limit(30)
|
.limit(30)
|
||||||
.get();
|
.get();
|
||||||
final list = snap.docs.map((d) => {'id': d.id, ...d.data()}).toList();
|
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<String, String>.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<dynamic>?;
|
||||||
|
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<String>()
|
||||||
|
.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)
|
if (mounted)
|
||||||
setState(() {
|
setState(() {
|
||||||
_history = list;
|
_history = list;
|
||||||
|
_historyClassNames = histClassNames;
|
||||||
_loadingHistory = false;
|
_loadingHistory = false;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -651,7 +738,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
Future<void> _previewPDF(Map<String, String> mat, String name) async {
|
Future<void> _previewPDF(Map<String, String> mat, String name) async {
|
||||||
final matId = mat['id']!;
|
final matId = mat['id']!;
|
||||||
final matName = name.replaceAll('.pdf', '').replaceAll('_', ' ');
|
final matName = name.replaceAll('.pdf', '').replaceAll('_', ' ');
|
||||||
|
|
||||||
// Mostrar loading
|
// Mostrar loading
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -666,25 +753,30 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Obter o texto completo do PDF usando o método existente
|
// Obter o texto completo do PDF usando o método existente
|
||||||
final teacherId = mat['teacherId'];
|
final teacherId = mat['teacherId'];
|
||||||
if (teacherId == null) {
|
if (teacherId == null) {
|
||||||
Navigator.of(context).pop();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final fullText = await MaterialsRAGService.getFullPDFText(matName, teacherId);
|
final fullText = await MaterialsRAGService.getFullPDFText(
|
||||||
|
matName,
|
||||||
|
teacherId,
|
||||||
|
);
|
||||||
|
|
||||||
Navigator.of(context).pop(); // Fechar loading
|
Navigator.of(context).pop(); // Fechar loading
|
||||||
|
|
||||||
if (fullText.isEmpty) {
|
if (fullText.isEmpty) {
|
||||||
_showSnack('Não foi possível carregar o conteúdo do PDF.');
|
_showSnack('Não foi possível carregar o conteúdo do PDF.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mostrar o conteúdo em um diálogo scrollável melhorado
|
// Mostrar o conteúdo em um diálogo scrollável melhorado
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -711,14 +803,19 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
height: 4,
|
height: 4,
|
||||||
margin: const EdgeInsets.symmetric(vertical: 12),
|
margin: const EdgeInsets.symmetric(vertical: 12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.4),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant.withOpacity(0.4),
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(2),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Header melhorado
|
// Header melhorado
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
@@ -728,7 +825,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
begin: Alignment.topLeft,
|
begin: Alignment.topLeft,
|
||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
top: Radius.circular(24),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -740,9 +839,15 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.2),
|
color: Colors.white.withOpacity(0.2),
|
||||||
borderRadius: BorderRadius.circular(12),
|
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),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -777,24 +882,37 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
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),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Stats bar
|
// Stats bar
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.15),
|
color: Colors.white.withOpacity(0.15),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: Colors.white.withOpacity(0.2)),
|
border: Border.all(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
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),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -807,7 +925,10 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.2),
|
color: Colors.white.withOpacity(0.2),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@@ -827,32 +948,45 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Content area melhorado
|
// Content area melhorado
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.all(20),
|
margin: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.1),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.outline.withOpacity(0.1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Content header
|
// Content header
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
color: Theme.of(
|
||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest,
|
||||||
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
top: Radius.circular(16),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.text_fields,
|
Icon(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
Icons.text_fields,
|
||||||
size: 18),
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
'Conteúdo do Material',
|
'Conteúdo do Material',
|
||||||
@@ -864,9 +998,14 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -881,7 +1020,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Text content
|
// Text content
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -904,25 +1043,31 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Footer melhorado
|
// Footer melhorado
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)),
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(24),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Icon(Icons.info_outline,
|
child: Icon(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
Icons.info_outline,
|
||||||
size: 16),
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -933,7 +1078,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
'Texto extraído automaticamente do PDF',
|
'Texto extraído automaticamente do PDF',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurfaceVariant,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -942,15 +1089,19 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
'Formatação otimizada para melhor legibilidade',
|
'Formatação otimizada para melhor legibilidade',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
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,
|
Icon(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
Icons.keyboard_arrow_up,
|
||||||
size: 16),
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -958,7 +1109,6 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.error('Error previewing PDF: $e');
|
Logger.error('Error previewing PDF: $e');
|
||||||
Navigator.of(context).pop(); // Fechar loading
|
Navigator.of(context).pop(); // Fechar loading
|
||||||
@@ -1089,6 +1239,10 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
setState(() => _selectedMaterialDisciplineId = null);
|
setState(() => _selectedMaterialDisciplineId = null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (_selectedHistoryDisciplineId != null) {
|
||||||
|
setState(() => _selectedHistoryDisciplineId = null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_selectedDisciplineId != null) {
|
if (_selectedDisciplineId != null) {
|
||||||
setState(() => _selectedDisciplineId = null);
|
setState(() => _selectedDisciplineId = null);
|
||||||
return;
|
return;
|
||||||
@@ -1107,6 +1261,8 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_selectedMaterialDisciplineId != null) {
|
if (_selectedMaterialDisciplineId != null) {
|
||||||
setState(() => _selectedMaterialDisciplineId = null);
|
setState(() => _selectedMaterialDisciplineId = null);
|
||||||
|
} else if (_selectedHistoryDisciplineId != null) {
|
||||||
|
setState(() => _selectedHistoryDisciplineId = null);
|
||||||
} else if (_selectedDisciplineId != null) {
|
} else if (_selectedDisciplineId != null) {
|
||||||
setState(() => _selectedDisciplineId = null);
|
setState(() => _selectedDisciplineId = null);
|
||||||
} else {
|
} else {
|
||||||
@@ -1349,16 +1505,13 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
ColorScheme cs,
|
ColorScheme cs,
|
||||||
) {
|
) {
|
||||||
final cleanName = name.replaceAll('.pdf', '').replaceAll('_', ' ');
|
final cleanName = name.replaceAll('.pdf', '').replaceAll('_', ' ');
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 4),
|
margin: const EdgeInsets.only(bottom: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.surface,
|
color: cs.surface,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(color: cs.outline.withOpacity(0.08), width: 1),
|
||||||
color: cs.outline.withOpacity(0.08),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: cs.shadow.withOpacity(0.04),
|
color: cs.shadow.withOpacity(0.04),
|
||||||
@@ -1376,7 +1529,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
onTap: isGenerating ? null : () => _showMaterialOptions(mat, name, cs),
|
onTap: isGenerating
|
||||||
|
? null
|
||||||
|
: () => _showMaterialOptions(mat, name, cs),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -1403,11 +1558,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(Icons.picture_as_pdf, color: cs.secondary, size: 26),
|
||||||
Icons.picture_as_pdf,
|
|
||||||
color: cs.secondary,
|
|
||||||
size: 26,
|
|
||||||
),
|
|
||||||
if (!isGenerating)
|
if (!isGenerating)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 2,
|
bottom: 2,
|
||||||
@@ -1429,9 +1580,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1450,7 +1601,10 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.primaryContainer.withOpacity(0.5),
|
color: cs.primaryContainer.withOpacity(0.5),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
@@ -1458,11 +1612,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(Icons.touch_app, size: 12, color: cs.primary),
|
||||||
Icons.touch_app,
|
|
||||||
size: 12,
|
|
||||||
color: cs.primary,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
isGenerating ? 'A gerar...' : 'Toca para opções',
|
isGenerating ? 'A gerar...' : 'Toca para opções',
|
||||||
@@ -1478,18 +1628,18 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Action indicator
|
// Action indicator
|
||||||
Container(
|
Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isGenerating
|
color: isGenerating
|
||||||
? cs.primary.withOpacity(0.1)
|
? cs.primary.withOpacity(0.1)
|
||||||
: cs.surfaceVariant.withOpacity(0.5),
|
: cs.surfaceVariant.withOpacity(0.5),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isGenerating
|
color: isGenerating
|
||||||
? cs.primary.withOpacity(0.2)
|
? cs.primary.withOpacity(0.2)
|
||||||
: cs.outline.withOpacity(0.1),
|
: cs.outline.withOpacity(0.1),
|
||||||
),
|
),
|
||||||
@@ -1519,9 +1669,13 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMaterialOptions(Map<String, String> mat, String name, ColorScheme cs) {
|
void _showMaterialOptions(
|
||||||
|
Map<String, String> mat,
|
||||||
|
String name,
|
||||||
|
ColorScheme cs,
|
||||||
|
) {
|
||||||
final cleanName = name.replaceAll('.pdf', '').replaceAll('_', ' ');
|
final cleanName = name.replaceAll('.pdf', '').replaceAll('_', ' ');
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
@@ -1552,7 +1706,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
borderRadius: BorderRadius.circular(3),
|
borderRadius: BorderRadius.circular(3),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Header melhorado com gradient
|
// Header melhorado com gradient
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 24),
|
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
@@ -1567,9 +1721,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
end: Alignment.bottomRight,
|
end: Alignment.bottomRight,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(color: cs.outline.withOpacity(0.1)),
|
||||||
color: cs.outline.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -1620,9 +1772,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|
||||||
// Title section
|
// Title section
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1641,7 +1793,10 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: cs.primaryContainer.withOpacity(0.6),
|
color: cs.primaryContainer.withOpacity(0.6),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
@@ -1661,19 +1816,15 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Instructions
|
// Instructions
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(Icons.lightbulb_outline, color: cs.tertiary, size: 20),
|
||||||
Icons.lightbulb_outline,
|
|
||||||
color: cs.tertiary,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -1688,9 +1839,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
// Options melhoradas
|
// Options melhoradas
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
@@ -1714,9 +1865,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
_previewPDF(mat, name);
|
_previewPDF(mat, name);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Generate Quiz option com design premium
|
// Generate Quiz option com design premium
|
||||||
_buildPremiumOptionTile(
|
_buildPremiumOptionTile(
|
||||||
icon: Icons.quiz_rounded,
|
icon: Icons.quiz_rounded,
|
||||||
@@ -1738,7 +1889,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1765,10 +1916,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: gradient,
|
gradient: gradient,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: Border.all(
|
border: Border.all(color: color.withOpacity(0.2), width: 1.5),
|
||||||
color: color.withOpacity(0.2),
|
|
||||||
width: 1.5,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: color.withOpacity(0.1),
|
color: color.withOpacity(0.1),
|
||||||
@@ -1786,10 +1934,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color.withOpacity(0.15),
|
color: color.withOpacity(0.15),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
border: Border.all(color: color.withOpacity(0.3), width: 1),
|
||||||
color: color.withOpacity(0.3),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -1815,9 +1960,9 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|
||||||
// Text content
|
// Text content
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -1852,7 +1997,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Arrow indicator
|
// Arrow indicator
|
||||||
Container(
|
Container(
|
||||||
width: 32,
|
width: 32,
|
||||||
@@ -1861,11 +2006,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
color: color.withOpacity(0.1),
|
color: color.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(Icons.chevron_right, color: color, size: 18),
|
||||||
Icons.chevron_right,
|
|
||||||
color: color,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1918,10 +2059,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: TextStyle(
|
style: TextStyle(fontSize: 12, color: cs.onSurfaceVariant),
|
||||||
fontSize: 12,
|
|
||||||
color: cs.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1933,6 +2071,18 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, List<Map<String, dynamic>>> _groupHistoryByDiscipline() {
|
||||||
|
final groups = <String, List<Map<String, dynamic>>>{};
|
||||||
|
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) {
|
Widget _buildHistoryTab(ColorScheme cs) {
|
||||||
if (_loadingHistory) {
|
if (_loadingHistory) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@@ -1960,12 +2110,140 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
return ListView.separated(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
itemCount: _history.length,
|
itemCount: realDisciplineIds.length,
|
||||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||||
itemBuilder: (context, i) {
|
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<Map<String, dynamic>> 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')
|
final matName = (item['materialName'] as String? ?? 'Material')
|
||||||
.replaceAll('.pdf', '')
|
.replaceAll('.pdf', '')
|
||||||
.replaceAll('_', ' ');
|
.replaceAll('_', ' ');
|
||||||
|
|||||||
Reference in New Issue
Block a user