Update Saqel Platform: 2026-09-13 16:31:17

This commit is contained in:
Hamza-Ayed
2026-09-13 16:31:18 +03:00
parent 81fa8c5dc8
commit 72c6575765
25 changed files with 6997 additions and 3852 deletions
@@ -37,8 +37,18 @@ class SubjectModel {
this.exams = const [],
});
List<CurriculumUnitModel> get semester1Units => units.where((u) => u.semester == 'semester_1').toList();
List<CurriculumUnitModel> get semester2Units => units.where((u) => u.semester == 'semester_2').toList();
List<CurriculumUnitModel> unitsForSemester(String semesterKey) {
final filtered = units.where((u) => u.semester == semesterKey).toList();
return filtered.isNotEmpty ? filtered : units;
}
factory SubjectModel.fromJson(String id, Map<String, dynamic> json) {
final title = json['name']?.toString() ?? json['title']?.toString() ?? id;
var title = json['name']?.toString() ?? json['title']?.toString() ?? id;
if (id == 'history_10' || title.contains('تاريخ الأردن')) {
title = 'التاريخ (History 10)';
}
final englishTitle = json['english_name']?.toString() ?? '';
// Parse units if available
@@ -148,32 +148,57 @@ class CurriculumRepository {
}
Future<List<PublishedLessonVideoModel>> getPublishedLessonVideos(String curriculumLessonId) async {
AppLogger.event('FetchPublishedVideosRequested', details: {'curriculumLessonId': curriculumLessonId}, tag: 'CURRICULUM_REPO');
final res = await _api.get('/api/curriculum/lessons/$curriculumLessonId/videos');
final data = res is Map ? res['data'] : null;
final raw = data is Map ? data['items'] : null;
if (raw is! List) return const [];
return raw.whereType<Map>().map((item) => PublishedLessonVideoModel.fromJson(Map<String, dynamic>.from(item))).where((item) => item.videoVersionId.isNotEmpty).toList();
final list = raw.whereType<Map>().map((item) => PublishedLessonVideoModel.fromJson(Map<String, dynamic>.from(item))).where((item) => item.videoVersionId.isNotEmpty).toList();
AppLogger.event('FetchPublishedVideosSuccess', details: {'count': list.length, 'teachers': list.map((v) => v.teacherName).toList()}, tag: 'CURRICULUM_REPO');
return list;
}
Future<LessonPlaybackData> getVideoVersionPlayback(String videoVersionId) async {
AppLogger.event('FetchVideoPlaybackRequested', details: {'videoVersionId': videoVersionId}, tag: 'CURRICULUM_REPO');
final res = await _api.get('/api/video-versions/$videoVersionId/playback');
if (res is Map && res['data'] is Map) return LessonPlaybackData.fromJson(Map<String, dynamic>.from(res['data']));
if (res is Map && res['data'] is Map) {
final pb = LessonPlaybackData.fromJson(Map<String, dynamic>.from(res['data']));
AppLogger.event('FetchVideoPlaybackSuccess', details: {
'videoVersionId': pb.videoVersionId,
'videoUrl': pb.videoUrl,
'duration': pb.durationSeconds,
}, tag: 'CURRICULUM_REPO');
return pb;
}
throw ApiException('فشل جلب تشغيل الحصة المنشورة من الخادم');
}
Future<String> startWatchSession(String videoVersionId) async {
AppLogger.event('StartWatchSessionRequested', details: {'videoVersionId': videoVersionId}, tag: 'CURRICULUM_REPO');
final res = await _api.post('/api/video-versions/$videoVersionId/watch-sessions', body: const {});
final data = res is Map ? res['data'] : null;
final source = data is Map ? data : res;
final id = source is Map ? source['watch_session_id']?.toString() : null;
if (id == null || id.isEmpty) throw ApiException('لم ينشئ الخادم جلسة مشاهدة.');
AppLogger.event('StartWatchSessionSuccess', details: {'sessionId': id}, tag: 'CURRICULUM_REPO');
return id;
}
Future<void> recordWatchEvent(String sessionId, int sequenceNo, String eventType, int positionMs) async {
AppLogger.event('RecordWatchEvent', details: {
'sessionId': sessionId,
'seq': sequenceNo,
'type': eventType,
'positionMs': positionMs,
}, tag: 'CURRICULUM_REPO');
await _api.post('/api/watch-sessions/$sessionId/events', body: {'sequence_no': sequenceNo, 'event_type': eventType, 'position_ms': positionMs});
}
Future<void> saveProgress({required int lessonId, required int positionSeconds, required int watchedSeconds}) async {
AppLogger.event('SaveProgressRequested', details: {
'lessonId': lessonId,
'position': positionSeconds,
'watched': watchedSeconds,
}, tag: 'CURRICULUM_REPO');
await _api.post('/api/student/lessons/$lessonId/progress', body: {
'position_seconds': positionSeconds,
'watched_seconds': watchedSeconds,
@@ -181,6 +206,11 @@ class CurriculumRepository {
}
Future<void> submitCheckpoint({required int examId, required int questionId, required int optionId}) async {
AppLogger.event('SubmitCheckpointRequested', details: {
'examId': examId,
'questionId': questionId,
'optionId': optionId,
}, tag: 'CURRICULUM_REPO');
await _api.post('/api/exams/$examId/submit', body: {
'answers': [
{'question_id': questionId, 'selected_option_id': optionId}