fix(playback): isolate curriculum keys and prevent non-uploaded lessons from falling back to previous video

This commit is contained in:
Hamza-Ayed
2026-09-09 01:33:56 +03:00
parent a4d6e34a40
commit 78ad245ee5
4 changed files with 196 additions and 77 deletions
@@ -235,17 +235,14 @@ class CurriculumLessonItemModel {
// Video availability:
// 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
// 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') ||
filePath.contains('intro_and_project') ||
filePath.contains('lesson_01') ||
filePath.contains('lesson_02')
)) ||
((json['title']?.toString() ?? '').contains('معادلات')) ||
((json['title']?.toString() ?? '').contains('مقدمة ومشروع'));
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')
));
return CurriculumLessonItemModel(
id: stableId,
@@ -262,12 +262,17 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
),
children: unit.lessons.map((lesson) {
final hasVid = lesson.hasVideo;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFF09111E),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.darkCardBorder.withAlpha(80)),
border: Border.all(
color: hasVid
? AppColors.darkCardBorder.withAlpha(80)
: Colors.white.withAlpha(15),
),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
@@ -275,16 +280,28 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
width: 38,
height: 38,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [widget.subject.primaryColor, widget.subject.secondaryColor],
),
gradient: hasVid
? LinearGradient(
colors: [widget.subject.primaryColor, widget.subject.secondaryColor],
)
: null,
color: hasVid ? null : Colors.white.withAlpha(10),
borderRadius: BorderRadius.circular(10),
border: hasVid ? null : Border.all(color: Colors.white12),
),
child: Icon(
hasVid ? CupertinoIcons.play_fill : CupertinoIcons.film,
color: hasVid ? Colors.black : AppColors.textSecondaryDark,
size: 18,
),
child: const Icon(CupertinoIcons.play_fill, color: Colors.black, size: 18),
),
title: Text(
lesson.title,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600, fontSize: 13),
style: TextStyle(
color: hasVid ? Colors.white : Colors.white70,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
subtitle: Row(
children: [
@@ -296,17 +313,27 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(25),
color: hasVid
? AppColors.saqelCyan.withAlpha(25)
: Colors.white.withAlpha(12),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'${lesson.checkpointsCount} فحوصات',
style: const TextStyle(color: AppColors.saqelCyan, fontSize: 10, fontWeight: FontWeight.w700),
hasVid ? '${lesson.checkpointsCount} فحوصات' : 'قريباً',
style: TextStyle(
color: hasVid ? AppColors.saqelCyan : AppColors.textSecondaryDark,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
],
),
trailing: const Icon(CupertinoIcons.chevron_back, color: AppColors.textSecondaryDark, size: 16),
trailing: Icon(
hasVid ? CupertinoIcons.chevron_back : CupertinoIcons.clock,
color: hasVid ? AppColors.textSecondaryDark : Colors.white24,
size: 16,
),
onTap: () {
_showLessonVideoSelector(context, lesson);
},
@@ -814,8 +841,13 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
);
}
/// Direct Launch of Socratic Video Player
/// Direct Launch of Socratic Video Player or Unavailable Notice
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(
@@ -828,4 +860,94 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
),
);
}
/// Luxury Modal Sheet when a lesson has no uploaded video yet
void _showNoVideoAvailableSheet(BuildContext context, CurriculumLessonItemModel lesson) {
showCupertinoModalPopup(
context: context,
builder: (ctx) => Container(
padding: const EdgeInsets.fromLTRB(24, 16, 24, 32),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: const BorderRadius.vertical(top: Radius.circular(28)),
border: Border.all(color: Colors.white12),
),
child: SafeArea(
top: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag Indicator
Container(
width: 40,
height: 4,
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.white24,
borderRadius: BorderRadius.circular(2),
),
),
// Icon
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(20),
shape: BoxShape.circle,
border: Border.all(color: AppColors.saqelCyan.withAlpha(60)),
),
child: const Icon(
CupertinoIcons.film,
color: AppColors.saqelCyan,
size: 32,
),
),
const SizedBox(height: 16),
// Title
const Text(
'فيديو الشرح قيد الإنتاج والمراجعة',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
// Lesson title
Text(
lesson.title,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.saqelCyan,
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 12),
// Explanation
const Text(
'لم يقم المعلم برفع ونشر فيديو الشرح لهذا الدرس بعد.\nسيتاح فور اعتماده من الإشراف الأكاديمي. يمكنك حالياً الاستفادة من أوراق العمل والملخصات والأنشطة التفاعلية في التبويبات المجاورة.',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.textSecondaryDark,
fontSize: 13,
height: 1.5,
),
),
const SizedBox(height: 24),
// Action Button
SizedBox(
width: double.infinity,
child: LuxuryButton(
text: 'حسناً، فهمت ذلك',
onPressed: () => Navigator.of(ctx).pop(),
),
),
],
),
),
),
);
}
}