fix(playback): enable authentic video playback for Intro and Lesson 2 across student app and backend

This commit is contained in:
Hamza-Ayed
2026-09-09 01:23:51 +03:00
parent 0a2ca71cb4
commit a8e895d5da
7 changed files with 179 additions and 60 deletions
@@ -234,24 +234,19 @@ class CurriculumLessonItemModel {
: 'lesson_${(json['title']?.toString() ?? 'default').hashCode.abs()}');
// Video availability:
// 1. Explicitly false for introductory/project overviews
// 2. Physics & other subjects currently have no video -> false
// 3. Math 10 Lesson 1 ("حل معادلات خاصة") has the authentic lesson video -> true
final bool isIntroOrProject = (jsonId == 'intro_and_project') ||
(filePath?.contains('intro_and_project') ?? false) ||
// 1. Live server enriched: json['has_video'] == true or json['video_url'] is present
// 2. Unit 1 Math 10 lessons (Intro, Lesson 1, Lesson 2) uploaded by teacher are fully available
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') ||
filePath.contains('intro_and_project') ||
filePath.contains('lesson_01') ||
filePath.contains('lesson_02')
)) ||
((json['title']?.toString() ?? '').contains('معادلات')) ||
((json['title']?.toString() ?? '').contains('مقدمة ومشروع'));
final bool isMath10Lesson1 = !isIntroOrProject && (
(filePath != null && filePath.contains('math_10/semester_1/unit_01/lesson_01')) ||
((json['title']?.toString() ?? '').contains('معادلاتٍ خاصّةٍ'))
);
final bool hasVideoExplicit = !isIntroOrProject && (
(json['has_video'] == true) ||
(json['video_url'] != null && json['video_url'].toString().isNotEmpty) ||
isMath10Lesson1
);
return CurriculumLessonItemModel(
id: stableId,
title: json['title']?.toString() ?? 'درس بدون عنوان',
@@ -121,10 +121,14 @@ class CurriculumRepository {
}
/// Fetch lesson playback details (streams, checkpoints, and versions) from Live API
Future<LessonPlaybackData> getLessonPlayback(String curriculumKey, {String? subjectId, String? unitId}) async {
AppLogger.log('Fetching live playback data for curriculum key $curriculumKey...', tag: 'CURRICULUM_REPO');
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 res = await _api.get('/api/lessons/playback', queryParams: {'curriculum_key': curriculumKey});
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']));
}
@@ -94,7 +94,7 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
try {
final cleanKey = (lesson.markdownFilePath ?? lesson.id).replaceAll(RegExp(r'\.md$'), '');
var playback = await _repo.getLessonPlayback(cleanKey, subjectId: subject?.id);
var playback = await _repo.getLessonPlayback(cleanKey, subjectId: subject?.id, title: lesson.title);
if (playback.videoUrl.isEmpty) {
throw StateError('لم يربط الخادم فيديو R2 بهذا الدرس بعد.');
}
@@ -814,13 +814,8 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
);
}
/// Direct Launch of Socratic Video Player or Transparent No-Video Sheet
/// Direct Launch of Socratic Video Player
void _showLessonVideoSelector(BuildContext context, CurriculumLessonItemModel lesson) {
if (!lesson.hasVideo) {
_showNoVideoAvailableSheet(context, lesson);
return;
}
// Direct authentic launch: zero mockups, zero fake teacher names
Navigator.of(context).push(
CupertinoPageRoute(