Update Saqel Platform: 2026-09-12 08:50:01

This commit is contained in:
Hamza-Ayed
2026-09-12 08:50:02 +03:00
parent cb4f2a3159
commit 1f31369893
414 changed files with 70042 additions and 113 deletions
@@ -30,6 +30,9 @@ import 'islamic_interactive_lab_view.dart';
import 'history_interactive_timeline_view.dart';
import 'math_interactive_lab_view.dart';
import 'english_interactive_lab_view.dart';
import '../virtual_labs/labs_gallery_screen.dart';
import '../virtual_labs/labs_registry.dart';
import '../virtual_labs/subject_virtual_labs_view.dart';
import '../../../data/models/exam_model.dart';
import '../../../data/repositories/app_repositories.dart';
import '../../../data/repositories/curriculum_repository.dart';
@@ -98,6 +101,7 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
widget.subject.title.contains('دراسات');
bool get _hasLab =>
Grade10LabsRegistry.bySubjectNormalized(widget.subject.title).isNotEmpty ||
_isMathSubject ||
_isEnglishSubject ||
_isChemistrySubject ||
@@ -109,46 +113,57 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
_isHistorySubject;
Tab get _dynamicLabTab {
if (_isMathSubject) {
final norm = Grade10LabsRegistry.normalizeSubject(widget.subject.title);
if (_isMathSubject || norm == 'الرياضيات') {
return const Tab(
icon: Icon(CupertinoIcons.function, size: 18),
text: 'معمل جيوجبرا للرياضيات 📐',
text: 'مختبر الرياضيات والهندسة 📐',
);
} else if (_isEnglishSubject) {
} else if (_isEnglishSubject || norm == 'اللغة الإنجليزية') {
return const Tab(
icon: Icon(CupertinoIcons.headphones, size: 18),
text: 'مختبر الصوتيات والقواعد 🎧',
);
} else if (_isArabicSubject) {
} else if (_isArabicSubject || norm == 'العربية لغتي') {
return const Tab(
icon: Icon(CupertinoIcons.arrow_branch, size: 18),
text: 'مختبر الإعراب والعروض 📜',
);
} else if (_isIslamicSubject) {
} else if (_isIslamicSubject || norm == 'التربية الإسلامية') {
return const Tab(
icon: Icon(CupertinoIcons.money_dollar_circle_fill, size: 18),
text: 'حاسبة المواريث والتجويد ⚖️',
);
} else if (_isHistorySubject) {
} else if (_isHistorySubject || norm == 'تاريخ الأردن') {
return const Tab(
icon: Icon(CupertinoIcons.time_solid, size: 18),
text: 'الخط الزمني لتاريخ الأردن 🏛️',
);
} else if (_isChemistrySubject) {
} else if (_isChemistrySubject || norm == 'الكيمياء') {
return const Tab(
icon: Icon(CupertinoIcons.flame_fill, size: 18),
text: 'مختبر الكيمياء والطيوف ⚗️',
);
} else if (_isBiologySubject) {
} else if (_isBiologySubject || norm == 'العلوم الحياتية') {
return const Tab(
icon: Icon(CupertinoIcons.viewfinder, size: 18),
text: 'مختبر الأحياء والمجهر 🔬',
);
} else if (_isEarthSciencesSubject) {
} else if (_isEarthSciencesSubject || norm == 'علوم الأرض والبيئة') {
return const Tab(
icon: Icon(CupertinoIcons.globe, size: 18),
text: 'مختبر علوم الأرض والصخور 🌍',
);
} else if (norm == 'الثقافة المالية') {
return const Tab(
icon: Icon(CupertinoIcons.chart_bar_square_fill, size: 18),
text: 'مختبر الثقافة المالية والجدوى 📊',
);
} else if (norm == 'المهارات الرقمية') {
return const Tab(
icon: Icon(CupertinoIcons.desktopcomputer, size: 18),
text: 'مختبر الخوارزميات والبرمجة 💻',
);
} else {
return const Tab(
icon: Icon(CupertinoIcons.bolt_fill, size: 18),
@@ -158,25 +173,44 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
}
Widget get _dynamicLabWidget {
Widget? specializedTool;
String? specializedTitle;
if (_isMathSubject) {
return const MathInteractiveLabView();
specializedTool = const MathInteractiveLabView();
specializedTitle = 'معمل جيوجبرا 📐';
} else if (_isEnglishSubject) {
return const EnglishInteractiveLabView();
specializedTool = const EnglishInteractiveLabView();
specializedTitle = 'مدرب الصوتيات 🎙️';
} else if (_isArabicSubject) {
return const ArabicInteractiveLabView();
specializedTool = const ArabicInteractiveLabView();
specializedTitle = 'شجرة الإعراب 📜';
} else if (_isIslamicSubject) {
return const IslamicInteractiveLabView();
specializedTool = const IslamicInteractiveLabView();
specializedTitle = 'حاسبة المواريث ⚖️';
} else if (_isHistorySubject) {
return const HistoryInteractiveTimelineView();
specializedTool = const HistoryInteractiveTimelineView();
specializedTitle = 'الخط الزمني الشامل 🏛️';
} else if (_isChemistrySubject) {
return const ChemistryInteractiveLabView();
specializedTool = const ChemistryInteractiveLabView();
specializedTitle = 'طيف ذرة بور ⚗️';
} else if (_isBiologySubject) {
return const BiologyInteractiveLabView();
specializedTool = const BiologyInteractiveLabView();
specializedTitle = 'المجهر الرقمي 🔬';
} else if (_isEarthSciencesSubject) {
return const EarthSciencesInteractiveLabView();
} else {
return const PhysicsInteractiveLabView();
specializedTool = const EarthSciencesInteractiveLabView();
specializedTitle = 'دورة الصخور والطبقات 🌍';
} else if (_isPhysicsSubject) {
specializedTool = const PhysicsInteractiveLabView();
specializedTitle = 'محاكاة المتجهات ⚡';
}
return SubjectVirtualLabsView(
subjectTitle: widget.subject.title,
primaryColor: widget.subject.primaryColor,
specializedToolWidget: specializedTool,
specializedToolTitle: specializedTitle,
);
}
@override
@@ -207,6 +241,23 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
widget.subject.title,
style: AppTypography.titleLarge.copyWith(color: Colors.white, fontSize: 18),
),
actions: [
IconButton(
tooltip: 'المختبرات الافتراضية لكل درس',
icon: const Icon(CupertinoIcons.lab_flask_solid,
color: AppColors.saqelCyan, size: 22),
onPressed: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (_) => const Scaffold(
backgroundColor: AppColors.darkBackground,
body: SafeArea(child: VirtualLabsGalleryScreen()),
),
),
);
},
),
],
bottom: TabBar(
controller: _tabController,
indicatorColor: widget.subject.primaryColor,
@@ -297,11 +348,17 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 15),
),
subtitle: Text(
'${unit.lessons.length} دروس تفاعلية • فحص فهم سقراطي',
'${unit.lessons.length} دروس تفاعلية • فحص فهم مستمر',
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
),
children: unit.lessons.map((lesson) {
final hasVid = lesson.hasVideo;
final lab = Grade10LabsRegistry.findForLesson(
subjectTitle: widget.subject.title,
lessonTitle: lesson.title,
lessonId: lesson.id,
curriculumLessonId: lesson.curriculumLessonId,
);
return Container(
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
decoration: BoxDecoration(
@@ -313,69 +370,128 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
: Colors.white.withAlpha(15),
),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
leading: Container(
width: 38,
height: 38,
decoration: BoxDecoration(
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,
),
),
title: Text(
lesson.title,
style: TextStyle(
color: hasVid ? Colors.white : Colors.white70,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
subtitle: Row(
children: [
Text(
'${lesson.durationSeconds ~/ 60} دقيقة',
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
child: Column(
children: [
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
leading: Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: hasVid
? AppColors.saqelCyan.withAlpha(25)
: Colors.white.withAlpha(12),
borderRadius: BorderRadius.circular(6),
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: Text(
hasVid ? '${lesson.checkpointsCount} فحوصات' : 'قريباً',
style: TextStyle(
color: hasVid ? AppColors.saqelCyan : AppColors.textSecondaryDark,
fontSize: 10,
fontWeight: FontWeight.w700,
child: Icon(
hasVid ? CupertinoIcons.play_fill : CupertinoIcons.film,
color: hasVid ? Colors.black : AppColors.textSecondaryDark,
size: 18,
),
),
title: Text(
lesson.title,
style: TextStyle(
color: hasVid ? Colors.white : Colors.white70,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
subtitle: Row(
children: [
Text(
'${lesson.durationSeconds ~/ 60} دقيقة',
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: hasVid
? AppColors.saqelCyan.withAlpha(25)
: Colors.white.withAlpha(12),
borderRadius: BorderRadius.circular(6),
),
child: Text(
hasVid ? '${lesson.checkpointsCount} فحوصات' : 'قريباً',
style: TextStyle(
color: hasVid ? AppColors.saqelCyan : AppColors.textSecondaryDark,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
],
),
trailing: Icon(
hasVid ? CupertinoIcons.chevron_back : CupertinoIcons.clock,
color: hasVid ? AppColors.textSecondaryDark : Colors.white24,
size: 16,
),
onTap: () {
_showLessonVideoSelector(context, lesson);
},
),
if (lab != null)
Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => Grade10LabsRegistry.openLab(context, lab),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.saqelCyan.withValues(alpha: 0.25)),
),
child: Row(
children: [
const Icon(CupertinoIcons.lab_flask_solid, color: AppColors.saqelCyan, size: 14),
const SizedBox(width: 6),
Expanded(
child: Text(
'المختبر الافتراضي: ${lab.lessonAr}',
style: const TextStyle(
color: Colors.white,
fontSize: 11.5,
fontWeight: FontWeight.w700,
),
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(5),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'بدء التجربة',
style: TextStyle(
color: AppColors.saqelCyan,
fontSize: 10.5,
fontWeight: FontWeight.w800,
),
),
SizedBox(width: 3),
Icon(CupertinoIcons.arrow_left, color: AppColors.saqelCyan, size: 10),
],
),
),
],
),
),
),
),
],
),
trailing: Icon(
hasVid ? CupertinoIcons.chevron_back : CupertinoIcons.clock,
color: hasVid ? AppColors.textSecondaryDark : Colors.white24,
size: 16,
),
onTap: () {
_showLessonVideoSelector(context, lesson);
},
],
),
);
}).toList(),
@@ -804,6 +920,13 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
/// Empty state when no video exists yet for this lesson (Zero Mock Videos)
void _showNoVideoAvailableSheet(BuildContext context, CurriculumLessonItemModel lesson) {
final lab = Grade10LabsRegistry.findForLesson(
subjectTitle: widget.subject.title,
lessonTitle: lesson.title,
lessonId: lesson.id,
curriculumLessonId: lesson.curriculumLessonId,
);
showModalBottomSheet(
context: context,
backgroundColor: AppColors.darkSurface,
@@ -848,16 +971,37 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
),
const SizedBox(height: 16),
const Text(
'لا يوجد شرح مصور منشور لهذا الدرس بعد. يمكنك قراءة شروحات الدرس المعتمدة أو الانتقال للمختبر التفاعلي.',
'لا يوجد شرح مصور منشور لهذا الدرس بعد. يمكنك خوض التجربة الافتراضية للدرس مباشرة أو قراءة الشروحات المعتمدة.',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 13, height: 1.5),
),
const SizedBox(height: 20),
if (lab != null) ...[
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.saqelCyan,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () {
Navigator.of(ctx).pop();
Grade10LabsRegistry.openLab(context, lab);
},
icon: const Icon(CupertinoIcons.lab_flask_solid, size: 18, color: Colors.black),
label: Text('فتح المختبر الافتراضي: ${lab.lessonAr} 🧪',
style: const TextStyle(fontWeight: FontWeight.w900, fontSize: 13.5)),
),
),
const SizedBox(height: 10),
],
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.appleBlue,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: Colors.white,
side: const BorderSide(color: AppColors.darkCardBorder),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
@@ -883,19 +1027,18 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
child: TextButton.icon(
style: TextButton.styleFrom(
foregroundColor: AppColors.saqelCyan,
side: BorderSide(color: AppColors.saqelCyan.withOpacity(0.5)),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 10),
),
onPressed: () {
Navigator.of(ctx).pop();
_tabController.animateTo(4);
_tabController.animateTo(1);
},
icon: const Icon(CupertinoIcons.lab_flask_solid, size: 18),
label: const Text('الانتقال إلى المختبر التفاعلي للمادة 🧪', style: TextStyle(fontWeight: FontWeight.w700)),
icon: const Icon(CupertinoIcons.arrow_left_circle, size: 16),
label: const Text('استعراض كافة تجارب المادة (تبويب المختبر) 🧪',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5)),
),
),
],