fix(playback): isolate curriculum keys and prevent non-uploaded lessons from falling back to previous video
This commit is contained in:
@@ -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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user