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
@@ -79,6 +79,7 @@ class SocraticOptionModel {
/// Model for Lesson Playback Data (Streams, Versions, Chapters, Checkpoints)
class LessonPlaybackData {
final int lessonId;
final String? videoVersionId;
final String title;
final int durationSeconds;
final String videoUrl;
@@ -89,6 +90,7 @@ class LessonPlaybackData {
const LessonPlaybackData({
required this.lessonId,
this.videoVersionId,
required this.title,
required this.durationSeconds,
required this.videoUrl,
@@ -129,9 +131,10 @@ class LessonPlaybackData {
final lastPos = (json['last_position_seconds'] ?? playback['last_position_seconds'] ?? json['progress']?['position_seconds']) as num?;
return LessonPlaybackData(
lessonId: (lesson['id'] as num?)?.toInt() ?? 0,
title: lesson['title']?.toString() ?? 'الدرس التفاعلي',
durationSeconds: (lesson['duration_seconds'] as num?)?.toInt() ?? 0,
lessonId: (lesson['id'] as num?)?.toInt() ?? (json['lesson_id'] as num?)?.toInt() ?? 0,
videoVersionId: json['video_version_id']?.toString(),
title: lesson['title']?.toString() ?? json['title']?.toString() ?? 'الدرس التفاعلي',
durationSeconds: (lesson['duration_seconds'] as num?)?.toInt() ?? (json['duration_seconds'] as num?)?.toInt() ?? 0,
videoUrl: vidUrl,
storageType: playback['storage_type']?.toString() ?? 'api_upload',
availableVersions: versions,
@@ -142,6 +145,7 @@ class LessonPlaybackData {
LessonPlaybackData copyWith({
int? lessonId,
String? videoVersionId,
String? title,
int? durationSeconds,
String? videoUrl,
@@ -152,6 +156,7 @@ class LessonPlaybackData {
}) {
return LessonPlaybackData(
lessonId: lessonId ?? this.lessonId,
videoVersionId: videoVersionId ?? this.videoVersionId,
title: title ?? this.title,
durationSeconds: durationSeconds ?? this.durationSeconds,
videoUrl: videoUrl ?? this.videoUrl,
@@ -30,7 +30,7 @@ class SubjectModel {
this.stream = 'scientific',
this.totalUnits = 0,
this.totalLessons = 0,
this.masteryScore = 85.0,
this.masteryScore = 0,
this.units = const [],
this.textbooks = const [],
this.worksheets = const [],
@@ -95,7 +95,7 @@ class SubjectModel {
stream: json['stream']?.toString() ?? 'scientific',
totalUnits: parsedUnits.isNotEmpty ? parsedUnits.length : styling.defaultUnits,
totalLessons: parsedUnits.fold(0, (sum, u) => sum + u.lessons.length),
masteryScore: (json['mastery_score'] as num?)?.toDouble() ?? 88.0,
masteryScore: (json['mastery_score'] as num?)?.toDouble() ?? 0,
units: parsedUnits,
textbooks: parsedTextbooks,
worksheets: parsedWorksheets,
@@ -198,6 +198,7 @@ class CurriculumLessonItemModel {
final String title;
final List<String> outcomes;
final String? markdownFilePath;
final String? curriculumLessonId;
final int durationSeconds;
final int checkpointsCount;
final bool isCompleted;
@@ -208,8 +209,9 @@ class CurriculumLessonItemModel {
required this.title,
this.outcomes = const [],
this.markdownFilePath,
this.durationSeconds = 1200, // 20 mins default
this.checkpointsCount = 3,
this.curriculumLessonId,
this.durationSeconds = 0,
this.checkpointsCount = 0,
this.isCompleted = false,
this.hasVideo = false,
});
@@ -233,24 +235,17 @@ class CurriculumLessonItemModel {
? filePath.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_')
: 'lesson_${(json['title']?.toString() ?? 'default').hashCode.abs()}');
// Video availability:
// 1. Live server enriched: json['has_video'] == true or json['video_url'] is present
// 2. Exact uploaded lessons in Grade 10 Math Unit 1 (Intro, Lesson 1, Lesson 2)
final bool hasVideoExplicit = (json['has_video'] == true) ||
(json['video_url'] != null && json['video_url'].toString().isNotEmpty) ||
(filePath != null && (
filePath.contains('math_10/semester_1/unit_01/intro_and_project') ||
filePath.contains('math_10/semester_1/unit_01/lesson_01') ||
filePath.contains('math_10/semester_1/unit_01/lesson_02')
));
// Only the server may declare a published lesson playable.
final bool hasVideoExplicit = json['has_video'] == true;
return CurriculumLessonItemModel(
id: stableId,
title: json['title']?.toString() ?? 'درس بدون عنوان',
outcomes: outs,
markdownFilePath: filePath,
durationSeconds: (json['duration_seconds'] as num?)?.toInt() ?? 1200,
checkpointsCount: (json['checkpoints_count'] as num?)?.toInt() ?? 3,
curriculumLessonId: json['curriculum_lesson_id']?.toString(),
durationSeconds: (json['duration_seconds'] as num?)?.toInt() ?? 0,
checkpointsCount: (json['checkpoints_count'] as num?)?.toInt() ?? 0,
isCompleted: (json['is_completed'] as bool?) ?? false,
hasVideo: hasVideoExplicit,
);