Coisas
This commit is contained in:
@@ -1,5 +1,34 @@
|
|||||||
package com.example.teachit
|
package com.example.teachit
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowInsetsController
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
class MainActivity : FlutterActivity() {
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
hideSystemBars()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideSystemBars() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
window.setDecorFitsSystemWindows(false)
|
||||||
|
val controller = window.insetsController
|
||||||
|
controller?.let {
|
||||||
|
it.hide(WindowInsets.Type.systemBars())
|
||||||
|
it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
window.decorView.systemUiVisibility = (
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||||
|
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import '../../../../core/services/chat_memory_service.dart';
|
|||||||
import '../../../../core/services/materials_rag_service.dart';
|
import '../../../../core/services/materials_rag_service.dart';
|
||||||
import '../../../../core/services/rag_ai_service.dart';
|
import '../../../../core/services/rag_ai_service.dart';
|
||||||
import '../../../../core/utils/logger.dart';
|
import '../../../../core/utils/logger.dart';
|
||||||
|
import '../../../materials/presentation/pages/pdf_viewer_page.dart';
|
||||||
|
|
||||||
/// Simple AI Tutor chat interface page (for testing)
|
/// Simple AI Tutor chat interface page (for testing)
|
||||||
class TutorChatPageSimple extends StatefulWidget {
|
class TutorChatPageSimple extends StatefulWidget {
|
||||||
@@ -21,6 +22,7 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
final TextEditingController _messageController = TextEditingController();
|
final TextEditingController _messageController = TextEditingController();
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
final FocusNode _messageFocusNode = FocusNode();
|
||||||
|
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool _materialsConfirmed = false;
|
bool _materialsConfirmed = false;
|
||||||
@@ -68,6 +70,7 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_messageController.dispose();
|
_messageController.dispose();
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
|
_messageFocusNode.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,12 +677,12 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
),
|
),
|
||||||
if (_selectedMaterialIds.isNotEmpty)
|
if (_selectedMaterialIds.isNotEmpty)
|
||||||
..._selectedMaterialIds.map((id) {
|
..._selectedMaterialIds.map((id) {
|
||||||
final name =
|
final material = _availableMaterials.firstWhere(
|
||||||
_availableMaterials.firstWhere(
|
(m) => m['id'] == id,
|
||||||
(m) => m['id'] == id,
|
orElse: () => {'id': id, 'name': id},
|
||||||
orElse: () => {'id': id, 'name': id},
|
);
|
||||||
)['name'] ??
|
final name = material['name'] ?? id;
|
||||||
id;
|
final url = material['url'];
|
||||||
final cleanName = name
|
final cleanName = name
|
||||||
.replaceAll('.pdf', '')
|
.replaceAll('.pdf', '')
|
||||||
.replaceAll('_', ' ');
|
.replaceAll('_', ' ');
|
||||||
@@ -690,76 +693,98 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
final isLast = _selectedMaterialIds.length == 1;
|
final isLast = _selectedMaterialIds.length == 1;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 6),
|
padding: const EdgeInsets.only(left: 6),
|
||||||
child: Chip(
|
child: InkWell(
|
||||||
label: Text(
|
onTap: url != null
|
||||||
short,
|
? () {
|
||||||
style: const TextStyle(
|
Navigator.push(
|
||||||
fontSize: 11,
|
context,
|
||||||
color: Colors.white,
|
MaterialPageRoute(
|
||||||
fontWeight: FontWeight.w600,
|
builder: (context) =>
|
||||||
|
PdfViewerPage(
|
||||||
|
url: url,
|
||||||
|
fileName: name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
child: Chip(
|
||||||
|
label: Text(
|
||||||
|
short,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
backgroundColor: chipBg,
|
||||||
backgroundColor: chipBg,
|
deleteIconColor: Colors.white.withValues(
|
||||||
deleteIconColor: Colors.white.withValues(
|
alpha: 0.85,
|
||||||
alpha: 0.85,
|
),
|
||||||
),
|
deleteIcon: const Icon(
|
||||||
deleteIcon: const Icon(Icons.close, size: 14),
|
Icons.close,
|
||||||
onDeleted: () {
|
size: 14,
|
||||||
if (isLast) {
|
),
|
||||||
ScaffoldMessenger.of(context)
|
onDeleted: () {
|
||||||
..clearSnackBars()
|
if (isLast) {
|
||||||
..showSnackBar(
|
ScaffoldMessenger.of(context)
|
||||||
SnackBar(
|
..clearSnackBars()
|
||||||
behavior: SnackBarBehavior.floating,
|
..showSnackBar(
|
||||||
margin: const EdgeInsets.symmetric(
|
SnackBar(
|
||||||
horizontal: 20,
|
behavior:
|
||||||
vertical: 12,
|
SnackBarBehavior.floating,
|
||||||
),
|
margin:
|
||||||
backgroundColor: const Color(
|
const EdgeInsets.symmetric(
|
||||||
0xFFF68D2D,
|
horizontal: 20,
|
||||||
),
|
vertical: 12,
|
||||||
shape: RoundedRectangleBorder(
|
),
|
||||||
borderRadius:
|
backgroundColor: const Color(
|
||||||
BorderRadius.circular(12),
|
0xFFF68D2D,
|
||||||
),
|
),
|
||||||
duration: const Duration(
|
shape: RoundedRectangleBorder(
|
||||||
seconds: 2,
|
borderRadius:
|
||||||
),
|
BorderRadius.circular(12),
|
||||||
content: const Row(
|
),
|
||||||
children: [
|
duration: const Duration(
|
||||||
Icon(
|
seconds: 2,
|
||||||
Icons.warning_amber_rounded,
|
),
|
||||||
color: Colors.white,
|
content: const Row(
|
||||||
size: 20,
|
children: [
|
||||||
),
|
Icon(
|
||||||
SizedBox(width: 10),
|
Icons.warning_amber_rounded,
|
||||||
Expanded(
|
color: Colors.white,
|
||||||
child: Text(
|
size: 20,
|
||||||
'Tens de manter pelo menos um material selecionado.',
|
),
|
||||||
style: TextStyle(
|
SizedBox(width: 10),
|
||||||
color: Colors.white,
|
Expanded(
|
||||||
fontSize: 13,
|
child: Text(
|
||||||
fontWeight:
|
'Tens de manter pelo menos um material selecionado.',
|
||||||
FontWeight.w500,
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight:
|
||||||
|
FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
} else {
|
||||||
|
setState(
|
||||||
|
() => _selectedMaterialIds.remove(id),
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
setState(
|
},
|
||||||
() => _selectedMaterialIds.remove(id),
|
materialTapTargetSize:
|
||||||
);
|
MaterialTapTargetSize.shrinkWrap,
|
||||||
}
|
padding: const EdgeInsets.symmetric(
|
||||||
},
|
horizontal: 4,
|
||||||
materialTapTargetSize:
|
),
|
||||||
MaterialTapTargetSize.shrinkWrap,
|
visualDensity: VisualDensity.compact,
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4,
|
|
||||||
),
|
),
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@@ -785,6 +810,8 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _messageController,
|
controller: _messageController,
|
||||||
|
focusNode: _messageFocusNode,
|
||||||
|
autofocus: false,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
@@ -1184,6 +1211,7 @@ class _TutorChatPageSimpleState extends State<TutorChatPageSimple>
|
|||||||
RAGAIService.clearLastContext();
|
RAGAIService.clearLastContext();
|
||||||
}
|
}
|
||||||
Navigator.of(dialogContext).pop();
|
Navigator.of(dialogContext).pop();
|
||||||
|
_messageFocusNode.unfocus();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|||||||
@@ -2119,16 +2119,99 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final groups = _groupHistoryByDiscipline();
|
// Handle selected discipline for AI-generated quizzes
|
||||||
final filteredItems = items.where((item) {
|
if (_selectedHistoryDisciplineId != null) {
|
||||||
final cid = item['classId'] as String?;
|
final groups = _groupHistoryByDiscipline();
|
||||||
return groups.containsKey(cid) && cid != null;
|
final disciplineItems = groups[_selectedHistoryDisciplineId] ?? [];
|
||||||
}).toList();
|
// Filter only AI-generated quizzes (teacherQuizId == null)
|
||||||
|
final aiDisciplineItems = disciplineItems
|
||||||
|
.where((q) => q['teacherQuizId'] == null)
|
||||||
|
.toList();
|
||||||
|
final disciplineName =
|
||||||
|
_historyClassNames[_selectedHistoryDisciplineId] ??
|
||||||
|
_selectedHistoryDisciplineId!;
|
||||||
|
|
||||||
if (filteredItems.isEmpty) {
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back, color: cs.onSurface),
|
||||||
|
onPressed: () =>
|
||||||
|
setState(() => _selectedHistoryDisciplineId = null),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
disciplineName,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: cs.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${aiDisciplineItems.length} quiz${aiDisciplineItems.length != 1 ? 'zes' : ''}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: cs.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
_buildHistoryList(
|
||||||
|
cs,
|
||||||
|
aiDisciplineItems,
|
||||||
|
isTeacherQuizzes: false,
|
||||||
|
),
|
||||||
|
if (_isSelectionMode && _selectedQuizIds.isNotEmpty)
|
||||||
|
Positioned(
|
||||||
|
bottom: 16,
|
||||||
|
right: 16,
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
|
onPressed: _deleteSelectedQuizzes,
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
icon: const Icon(Icons.delete),
|
||||||
|
label: Text('Eliminar (${_selectedQuizIds.length})'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group only AI-generated quizzes by discipline
|
||||||
|
final aiItems = items.where((q) => q['teacherQuizId'] == null).toList();
|
||||||
|
|
||||||
|
// Filter groups to only include disciplines with AI-generated quizzes
|
||||||
|
final aiGroups = <String, List<Map<String, dynamic>>>{};
|
||||||
|
for (final item in aiItems) {
|
||||||
|
final cid = item['classId'] as String?;
|
||||||
|
final groupId = (cid != null && _historyClassNames.containsKey(cid))
|
||||||
|
? cid
|
||||||
|
: '__geral__';
|
||||||
|
aiGroups.putIfAbsent(groupId, () => []).add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiGroups.isEmpty) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
_buildHistoryList(cs, items, isTeacherQuizzes: isTeacherQuizzes),
|
_buildHistoryList(cs, aiItems, isTeacherQuizzes: isTeacherQuizzes),
|
||||||
if (_isSelectionMode && _selectedQuizIds.isNotEmpty)
|
if (_isSelectionMode && _selectedQuizIds.isNotEmpty)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 16,
|
bottom: 16,
|
||||||
@@ -2144,7 +2227,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final realDisciplineIds = groups.keys
|
final realDisciplineIds = aiGroups.keys
|
||||||
.where((k) => k != '__geral__' && _historyClassNames.containsKey(k))
|
.where((k) => k != '__geral__' && _historyClassNames.containsKey(k))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@@ -2176,7 +2259,7 @@ class _QuizListPageState extends State<QuizListPage>
|
|||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
final dId = realDisciplineIds[i];
|
final dId = realDisciplineIds[i];
|
||||||
final dName = _historyClassNames[dId] ?? dId;
|
final dName = _historyClassNames[dId] ?? dId;
|
||||||
final count = groups[dId]!.length;
|
final count = aiGroups[dId]!.length;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
onTap: () => setState(() => _selectedHistoryDisciplineId = dId),
|
onTap: () => setState(() => _selectedHistoryDisciplineId = dId),
|
||||||
|
|||||||
Reference in New Issue
Block a user