Update Saqel Platform: 2026-09-12 13:38:49
This commit is contained in:
@@ -15,6 +15,7 @@ import 'dart:async';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../data/models/exam_model.dart';
|
||||
import '../../data/repositories/app_repositories.dart';
|
||||
import '../../data/repositories/curriculum_question_bank.dart';
|
||||
|
||||
/// الحالات العامة للامتحان
|
||||
abstract class ExamState {}
|
||||
@@ -100,18 +101,51 @@ class ExamCubit extends Cubit<ExamState> {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> loadExam({int examId = 1, ExamModel? initialExam}) async {
|
||||
Future<void> loadExam({
|
||||
int examId = 1,
|
||||
ExamModel? initialExam,
|
||||
String? subjectCode,
|
||||
String? unitKey,
|
||||
String? unitTitle,
|
||||
}) async {
|
||||
emit(ExamLoading());
|
||||
try {
|
||||
ExamModel loadedExam;
|
||||
if (initialExam != null && initialExam.questions.isNotEmpty) {
|
||||
loadedExam = initialExam;
|
||||
} else {
|
||||
loadedExam = await _repository.getExamDetails(examId);
|
||||
try {
|
||||
loadedExam = await _repository.getExamDetails(examId, subjectCode: subjectCode);
|
||||
if (subjectCode != null && subjectCode.isNotEmpty) {
|
||||
final norm = CurriculumQuestionBank.normalizeSubjectKey(subjectCode);
|
||||
final titleLower = loadedExam.title.toLowerCase();
|
||||
final isCrossContaminated = (norm != 'math_10' &&
|
||||
(titleLower.contains('معادلات') ||
|
||||
titleLower.contains('رياضيات') ||
|
||||
titleLower.contains('أسس')));
|
||||
if (isCrossContaminated) {
|
||||
loadedExam = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: subjectCode,
|
||||
unitKey: unitKey ?? 'unit_01',
|
||||
unitTitle: unitTitle,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
if (subjectCode != null && subjectCode.isNotEmpty) {
|
||||
loadedExam = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: subjectCode,
|
||||
unitKey: unitKey ?? 'unit_01',
|
||||
unitTitle: unitTitle,
|
||||
);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loadedExam.questions.isEmpty) {
|
||||
emit(ExamError('لم يتم العثور على أسئلة لهذا الامتحان في السيرفر. يرجى توليد بنك الأسئلة من لوحة المناهج.'));
|
||||
emit(ExamError('لم يتم العثور على أسئلة لهذا الامتحان. يرجى تجربة اختبار وحدة أخرى.'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,7 +157,7 @@ class ExamCubit extends Cubit<ExamState> {
|
||||
|
||||
_startTimer();
|
||||
} catch (e) {
|
||||
emit(ExamError('تعذر جلب الامتحان من السيرفر: ${e.toString()}'));
|
||||
emit(ExamError('تعذر جلب الامتحان: ${e.toString()}'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import '../../core/utils/app_logger.dart';
|
||||
import '../../data/models/socratic_checkpoint_model.dart';
|
||||
import '../../data/models/subject_model.dart';
|
||||
import '../../data/repositories/curriculum_repository.dart';
|
||||
import '../../data/repositories/error_notebook_repository.dart';
|
||||
|
||||
abstract class VideoPlaybackState {}
|
||||
|
||||
@@ -179,6 +180,20 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
final currentState = state;
|
||||
if (currentState is VideoPlaybackReady && currentState.isPlaying) {
|
||||
emit(currentState.copyWith(isPlaying: false));
|
||||
}
|
||||
}
|
||||
|
||||
void play() {
|
||||
final currentState = state;
|
||||
if (currentState is VideoPlaybackReady && !currentState.isPlaying && currentState.activeCheckpoint == null) {
|
||||
emit(currentState.copyWith(isPlaying: true));
|
||||
}
|
||||
}
|
||||
|
||||
void seekTo(int seconds) {
|
||||
final currentState = state;
|
||||
if (currentState is VideoPlaybackReady) {
|
||||
@@ -271,6 +286,26 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
isPlaying: true,
|
||||
remediationNotice: 'تعثرت في هذا المفهوم. تم إرجاع الفيديو ${cp.rewindSecondsOnFail} ثانية لإعادة الاستماع بتركيز 🔄',
|
||||
));
|
||||
|
||||
// Auto-record gap to Smart Error Notebook
|
||||
final correctOpt = cp.options.firstWhere(
|
||||
(o) => o.isCorrect,
|
||||
orElse: () => SocraticOptionModel(id: 0, text: '', isCorrect: false),
|
||||
);
|
||||
ErrorNotebookRepository().logError(
|
||||
subjectId: currentState.subject?.id ?? 'physics_10',
|
||||
subjectName: currentState.subject?.title ?? 'المادة الدراسية',
|
||||
topicName: currentState.lessonItem?.title ?? 'وقفة فحص تفاعلية',
|
||||
sourceType: 'socratic_checkpoint',
|
||||
questionText: cp.questionText,
|
||||
studentWrongAnswer: selectedOption.text,
|
||||
correctAnswer: correctOpt.text,
|
||||
hint: cp.hint,
|
||||
errorCategory: 'conceptual',
|
||||
lessonId: currentState.playbackData.lessonId > 0 ? currentState.playbackData.lessonId : null,
|
||||
options: cp.options.map((o) => o.text).toList(),
|
||||
);
|
||||
|
||||
if (cp.id > 0 && cp.questionId > 0) {
|
||||
_repo.submitCheckpoint(examId: cp.id, questionId: cp.questionId, optionId: selectedOption.id).catchError((e) {
|
||||
AppLogger.log('Checkpoint sync deferred: $e', tag: 'VIDEO_CUBIT');
|
||||
|
||||
Reference in New Issue
Block a user