Implement verified curriculum, video review, watch sessions, ledger safeguards, and remove demo data

This commit is contained in:
Hamza-Ayed
2026-09-09 17:04:08 +03:00
parent 0dc5df7b5e
commit 5af684a892
49 changed files with 1809 additions and 3005 deletions
@@ -147,20 +147,30 @@ class CurriculumRepository {
return liveSubjects;
}
/// Fetch lesson playback details (streams, checkpoints, and versions) from Live API
Future<LessonPlaybackData> getLessonPlayback(String curriculumKey, {String? subjectId, String? unitId, String? title}) async {
AppLogger.log('Fetching live playback data for curriculum key $curriculumKey (title: $title)...', tag: 'CURRICULUM_REPO');
final Map<String, String> queryParams = {'curriculum_key': curriculumKey};
if (title != null && title.isNotEmpty) {
queryParams['title'] = title;
}
final res = await _api.get('/api/lessons/playback', queryParams: queryParams);
if (res is Map && res['data'] != null) {
return LessonPlaybackData.fromJson(Map<String, dynamic>.from(res['data']));
}
throw ApiException('فشل جلب بيانات تشغيل الدرس من الخادم');
Future<List<PublishedLessonVideoModel>> getPublishedLessonVideos(String curriculumLessonId) async {
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();
}
Future<LessonPlaybackData> getVideoVersionPlayback(String videoVersionId) async {
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']));
throw ApiException('فشل جلب تشغيل الحصة المنشورة من الخادم');
}
Future<String> startWatchSession(String videoVersionId) async {
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('لم ينشئ الخادم جلسة مشاهدة.');
return id;
}
Future<void> recordWatchEvent(String sessionId, int sequenceNo, String eventType, int positionMs) async {
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 {
@@ -178,3 +188,16 @@ class CurriculumRepository {
});
}
}
class PublishedLessonVideoModel {
final String videoVersionId;
final String submissionId;
final String teacherName;
final double rating;
final int ratingCount;
final bool isNew;
const PublishedLessonVideoModel({required this.videoVersionId, required this.submissionId, required this.teacherName, required this.rating, required this.ratingCount, required this.isNew});
factory PublishedLessonVideoModel.fromJson(Map<String, dynamic> json) => PublishedLessonVideoModel(
videoVersionId: json['video_version_id']?.toString() ?? '', submissionId: json['submission_id']?.toString() ?? '', teacherName: json['teacher_name']?.toString() ?? 'معلم معتمد', rating: (json['rating'] as num?)?.toDouble() ?? 0, ratingCount: (json['rating_count'] as num?)?.toInt() ?? 0, isNew: json['is_new'] == true,
);
}