Fix Flutter exam parsing, make exams tab dynamic from API, and add intelligent exam resolution to submitExam
This commit is contained in:
@@ -21,6 +21,8 @@ import '../../widgets/luxury_widgets.dart';
|
||||
import '../player/socratic_video_player_screen.dart';
|
||||
import '../exams/adaptive_exam_screen.dart';
|
||||
import 'curriculum_document_viewer_screen.dart';
|
||||
import '../../../data/models/exam_model.dart';
|
||||
import '../../../data/repositories/app_repositories.dart';
|
||||
|
||||
/// الشاشة المركزية للمادة الدراسية وبوابات الدروس والامتحانات والمصادر
|
||||
class SubjectHubScreen extends StatefulWidget {
|
||||
@@ -34,11 +36,13 @@ class SubjectHubScreen extends StatefulWidget {
|
||||
|
||||
class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
late Future<List<ExamModel>> _examsFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 4, vsync: this);
|
||||
_examsFuture = ExamRepository().getExams(courseId: 1, scope: 'unit_exam');
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -272,88 +276,128 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
|
||||
|
||||
/// Tab 3: Question Bank & Adaptive Unit Exams
|
||||
Widget _buildExamsTab(BuildContext context) {
|
||||
final exams = [
|
||||
const SubjectExamModel(id: 'ex1', title: 'اختبار الفهم الشامل: الوحدة الأولى (أنظمة المعادلات)', questionsCount: 15, durationMinutes: 45, targetScore: 100),
|
||||
const SubjectExamModel(id: 'ex2', title: 'نماذج أسئلة الوزارة للسنوات السابقة', questionsCount: 15, durationMinutes: 45, targetScore: 100),
|
||||
const SubjectExamModel(id: 'ex3', title: 'اختبار تشخيص الثغرات التكيفي بالذكاء الاصطناعي', questionsCount: 15, durationMinutes: 30, targetScore: 100),
|
||||
];
|
||||
return FutureBuilder<List<ExamModel>>(
|
||||
future: _examsFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CupertinoActivityIndicator(color: AppColors.saqelCyan),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 20),
|
||||
itemCount: exams.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final exam = exams[idx];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: LuxuryCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
exam.title,
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 14),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.emeraldGreen.withAlpha(25),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'جاهز للتقديم ✍️',
|
||||
style: TextStyle(color: AppColors.emeraldGreen, fontSize: 11, fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(CupertinoIcons.question_circle, color: AppColors.textSecondaryDark, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text('${exam.questionsCount} أسئلة', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)),
|
||||
const SizedBox(width: 12),
|
||||
const Icon(CupertinoIcons.time, color: AppColors.textSecondaryDark, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text('${exam.durationMinutes} د', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.appleBlue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => AdaptiveExamScreen(
|
||||
examId: idx + 1,
|
||||
title: exam.title,
|
||||
subjectTitle: widget.subject.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('بدء الاختبار 🚀', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
final liveExams = snapshot.data ?? [];
|
||||
if (liveExams.isEmpty) {
|
||||
// Default to Primary Unit Exam if none fetched yet
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 20),
|
||||
children: [
|
||||
_buildExamCard(
|
||||
context,
|
||||
examId: 1,
|
||||
title: 'اختبار الفهم الشامل: الوحدة الأولى (أنظمة المعادلات)',
|
||||
questionsCount: 50,
|
||||
durationMinutes: 45,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 20),
|
||||
itemCount: liveExams.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final exam = liveExams[idx];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _buildExamCard(
|
||||
context,
|
||||
examId: exam.id > 0 ? exam.id : 1,
|
||||
title: exam.title,
|
||||
questionsCount: exam.questionsCount > 0 ? exam.questionsCount : 50,
|
||||
durationMinutes: exam.durationMinutes > 0 ? exam.durationMinutes : 45,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExamCard(
|
||||
BuildContext context, {
|
||||
required int examId,
|
||||
required String title,
|
||||
required int questionsCount,
|
||||
required int durationMinutes,
|
||||
}) {
|
||||
return LuxuryCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 14),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.emeraldGreen.withAlpha(25),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'جاهز للتقديم ✍️',
|
||||
style: TextStyle(color: AppColors.emeraldGreen, fontSize: 11, fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(CupertinoIcons.question_circle, color: AppColors.textSecondaryDark, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text('$questionsCount سؤالاً في البنك', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)),
|
||||
const SizedBox(width: 12),
|
||||
const Icon(CupertinoIcons.time, color: AppColors.textSecondaryDark, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text('$durationMinutes د', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.appleBlue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => AdaptiveExamScreen(
|
||||
examId: examId,
|
||||
title: title,
|
||||
subjectTitle: widget.subject.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('بدء الاختبار 🚀', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Tab 4: Official Ministry Textbooks
|
||||
Widget _buildTextbooksTab(BuildContext context) {
|
||||
final textbooks = widget.subject.textbooks.isNotEmpty
|
||||
|
||||
Reference in New Issue
Block a user