From c9c03892d13675d818e0dee8d96d5c995c2a8ab0 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Thu, 3 Sep 2026 15:16:46 +0300 Subject: [PATCH] Implement multi-teacher video selector, in-app curriculum document reader, 15-question Unit 1 exam bank, and interactive science simulation engine --- .../lib/logic/cubits/exam_cubit.dart | 239 ++++++++++++-- .../curriculum_document_viewer_screen.dart | 308 ++++++++++++++++++ .../curriculum/subject_hub_screen.dart | 232 ++++++++++++- .../player/socratic_video_player_screen.dart | 26 ++ backend/app/Controllers/ExamController.php | 203 ++++++++++++ .../app/Services/AiLessonEnhancerService.php | 23 ++ .../simulations/physics_10/vectors_lab.html | 228 +++++++++++++ 7 files changed, 1219 insertions(+), 40 deletions(-) create mode 100644 apps/student_app/lib/presentation/screens/curriculum/curriculum_document_viewer_screen.dart create mode 100644 backend/storage/curriculum/simulations/physics_10/vectors_lab.html diff --git a/apps/student_app/lib/logic/cubits/exam_cubit.dart b/apps/student_app/lib/logic/cubits/exam_cubit.dart index 15773f4..e43eb35 100644 --- a/apps/student_app/lib/logic/cubits/exam_cubit.dart +++ b/apps/student_app/lib/logic/cubits/exam_cubit.dart @@ -263,53 +263,236 @@ class ExamCubit extends Cubit { id: examId, uuid: 'exam-default-$examId', courseId: 1, - title: title ?? 'اختبار الفهم والتشخيص السقراطي — المستوى المتقدم', - description: 'اختبار تشخيصي تكيفي يقيس الفهم العميق والقدرة على تطبيق القوانين الوزارية.', + title: title ?? 'اختبار الفهم الشامل: الوحدة الأولى — أنظمة المعادلات', + description: 'اختبار وزاري معياري يقيس الفهم العميق والقدرة على تطبيق القوانين لأنظمة المعادلات.', scope: 'unit_exam', passingPercentage: 70.0, - durationMinutes: 15, - questionsCount: 3, + durationMinutes: 45, + questionsCount: 15, questions: const [ + // 1. حل معادلات خاصة (إخراج العامل المشترك) QuestionModel( - id: 101, - questionText: 'ما هو المعنى الهندسي المباشر للمشتقة الأولى f\'(x) عند نقطة التماس؟', + id: 1, + questionText: 'ما هي مجموعة حل المعادلة الحقيقية: x³ - 4x = 0؟', questionType: 'multiple_choice', points: 10, - topicTag: 'المعنى الهندسي للاشتقاق', - explanationText: 'المشتقة الأولى تمثل رياضياً وهندسياً ميل خط المماس لمنحنى الاقتران عند نقطة التماس.', + topicTag: 'حل المعادلات بإخراج العامل المشترك الأكبر', + explanationText: 'بإخراج x كعامل مشترك: x(x² - 4) = 0 ومنها x(x - 2)(x + 2) = 0، فيكون الحل x = 0, 2, -2.', options: [ - QuestionOptionModel(id: 1, optionText: 'ميل خط المماس للمنحنى عند تلك النقطة', isCorrect: true, sequenceOrder: 1), - QuestionOptionModel(id: 2, optionText: 'معادلة المستقيم القاطع المار بنقطتين', isCorrect: false, sequenceOrder: 2), - QuestionOptionModel(id: 3, optionText: 'المساحة المحصورة تحت المنحنى ومحور السينات', isCorrect: false, sequenceOrder: 3), - QuestionOptionModel(id: 4, optionText: 'طول المماس الأفقي عند النقطة الحرجة', isCorrect: false, sequenceOrder: 4), + QuestionOptionModel(id: 1, optionText: '{ -2, 0, 2 }', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 2, optionText: '{ 0, 4 }', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 3, optionText: '{ -2, 2 }', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 4, optionText: '{ 0, 2 }', isCorrect: false, sequenceOrder: 4), ], ), + // 2. الصورة التربيعية QuestionModel( - id: 102, - questionText: 'إذا كان f(x) = sin(4x)، فما هي قيمة مشتقته الأولى f\'(x) وفق قاعدة السلسلة؟', + id: 2, + questionText: 'حل المعادلة x⁴ - 5x² + 4 = 0 في مجموعة الأعداد الحقيقية هو:', questionType: 'multiple_choice', points: 10, - topicTag: 'مشتقات الاقترانات الدائرية وقاعدة السلسلة', - explanationText: 'مشتقة sin(ax) هي a * cos(ax)؛ أي مشتقة الزاوية ضرب مشتقة الاقتران الخارجي.', + topicTag: 'المعادلات في الصورة التربيعية', + explanationText: 'بالتحليل كمعادلة تربيعية: (x² - 4)(x² - 1) = 0، ومنها x² = 4 أو x² = 1، فالجذور هي ±2 و ±1.', options: [ - QuestionOptionModel(id: 5, optionText: '4 cos(4x)', isCorrect: true, sequenceOrder: 1), - QuestionOptionModel(id: 6, optionText: 'cos(4x)', isCorrect: false, sequenceOrder: 2), - QuestionOptionModel(id: 7, optionText: '-4 cos(4x)', isCorrect: false, sequenceOrder: 3), - QuestionOptionModel(id: 8, optionText: '4 sin(4x)', isCorrect: false, sequenceOrder: 4), + QuestionOptionModel(id: 5, optionText: '{ -2, -1, 1, 2 }', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 6, optionText: '{ 1, 4 }', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 7, optionText: '{ -1, 1 }', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 8, optionText: '{ -4, 4 }', isCorrect: false, sequenceOrder: 4), ], ), + // 3. مجموع المكعبين والحلول الحقيقية QuestionModel( - id: 103, - questionText: 'ما هو الشرط الرياضي اللازم والكافي ليكون المماس أفقياً عند نقطة واقعة على المنحنى؟', + id: 3, + questionText: 'كم حلاً حقيقياً يحقق المعادلة: x³ + 8 = 0؟', questionType: 'multiple_choice', points: 10, - topicTag: 'المماسات الأفقية والنقاط الحرجة', - explanationText: 'يكون المماس أفقياً وموازياً لمحور السينات عندما يكون ميله مساوياً للصفر، أي f\'(x) = 0.', + topicTag: 'تحليل مجموع المكعبين والحلول الحقيقية', + explanationText: 'بتحليل مجموع مكعبين: (x + 2)(x² - 2x + 4) = 0. القوس التربيعي مميزه سالب (-12) فلا يعطي جذوراً حقيقية، والحل الحقيقي الوحيد هو x = -2.', options: [ - QuestionOptionModel(id: 9, optionText: 'أن تكون المشتقة الأولى مساوية للصفر f\'(x) = 0', isCorrect: true, sequenceOrder: 1), - QuestionOptionModel(id: 10, optionText: 'أن تكون قيمة الاقتران مساوية للصفر f(x) = 0', isCorrect: false, sequenceOrder: 2), - QuestionOptionModel(id: 11, optionText: 'أن تكون المشتقة غير معرّفة أو قيمة مطلقة', isCorrect: false, sequenceOrder: 3), - QuestionOptionModel(id: 12, optionText: 'أن يتقاطع المماس مع نقطة الأصل دائماً', isCorrect: false, sequenceOrder: 4), + QuestionOptionModel(id: 9, optionText: 'حل حقيقي واحد فقط وهو x = -2', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 10, optionText: 'ثلاثة حلول حقيقية', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 11, optionText: 'حلّان حقيقيان', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 12, optionText: 'لا يوجد أي حل حقيقي', isCorrect: false, sequenceOrder: 4), + ], + ), + // 4. أقصى عدد حلول لنظام خطي وتربيعي + QuestionModel( + id: 4, + questionText: 'ما هو أقصى عدد ممكن لنقاط التقاطع بين مستقيم وقطع مكافئ في المستوى الإحداثي؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'التمثيل الهندسي لنظام خطي وتربيعي', + explanationText: 'المستقيم يقطع القطع المكافئ في نقطتين كحد أقصى (حلان)، أو يمسه في نقطة (حل واحد)، أو لا يقطعه (لا يوجد حل حقيقي).', + options: [ + QuestionOptionModel(id: 13, optionText: 'نقطتان كحد أقصى (حلّان حقيقيان)', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 14, optionText: 'ثلاث نقاط تقاطع', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 15, optionText: 'أربع نقاط تقاطع', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 16, optionText: 'نقطة واحدة فقط دائماً', isCorrect: false, sequenceOrder: 4), + ], + ), + // 5. حل نظام خطي وتربيعي بالتعويض + QuestionModel( + id: 5, + questionText: 'إذا كان لدينا النظام: y = x + 1 و y = x² + 1، فما هي نقاط تقاطع المنحنيين؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'حل نظام خطي وتربيعي بطريقة التعويض', + explanationText: 'بالمساواة: x² + 1 = x + 1 ومنها x² - x = 0 أي x(x - 1) = 0، فيكون x = 0 أو x = 1. بالتعويض نجد y = 1 أو y = 2.', + options: [ + QuestionOptionModel(id: 17, optionText: '(0, 1) و (1, 2)', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 18, optionText: '(0, 0) و (1, 1)', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 19, optionText: '(1, 2) فقط', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 20, optionText: '(-1, 0) و (1, 2)', isCorrect: false, sequenceOrder: 4), + ], + ), + // 6. المميز وسالبية الحلول + QuestionModel( + id: 6, + questionText: 'متى لا يوجد أي حل حقيقي لنظام مكوّن من معادلة خطية وأخرى تربيعية؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'استخدام المميز لتحديد عدد حلول النظام', + explanationText: 'إذا كان مميز المعادلة التربيعية الناتجة عن التعويض سالباً (Δ = b² - 4ac < 0)، فإن المستقيم لا يتقاطع مع المنحنى.', + options: [ + QuestionOptionModel(id: 21, optionText: 'عندما يكون مميز المعادلة التربيعية الناتجة سالباً (Δ < 0)', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 22, optionText: 'عندما يكون المميز مساوياً لصفر', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 23, optionText: 'عندما يكون المميز موجباً تماماً', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 24, optionText: 'إذا كان ميل المستقيم يساوي صفراً', isCorrect: false, sequenceOrder: 4), + ], + ), + // 7. حالة المماس + QuestionModel( + id: 7, + questionText: 'إذا كان لنظام مكوّن من مستقيم وقطع مكافئ حل حقيقي وحيد فقط، فإن المستقيم يعتبر:', + questionType: 'multiple_choice', + points: 10, + topicTag: 'المستقيم المماس لمنحنى تربيعي', + explanationText: 'وجود حل حقيقي وحيد لنظام خطي-تربيعي يعني هندسياً أن المستقيم يمس المنحنى عند نقطة واحدة فقط.', + options: [ + QuestionOptionModel(id: 25, optionText: 'مماساً لمنحنى القطع المكافئ عند نقطة التماس', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 26, optionText: 'قاطعاً للمنحنى في نقطتين', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 27, optionText: 'خط تقارب رأسي للمنحنى', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 28, optionText: 'محور تماثل للقطع المكافئ', isCorrect: false, sequenceOrder: 4), + ], + ), + // 8. حل نظام تربيعي بالحذف + QuestionModel( + id: 8, + questionText: 'في النظام: x² + y² = 25 و x² - y² = 7، ما هي قيمة x²؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'حل نظام تربيعي بطريقة الحذف', + explanationText: 'بجمع المعادلتين طرفاً لطرف: 2x² = 32 ومنها x² = 16.', + options: [ + QuestionOptionModel(id: 29, optionText: 'x² = 16', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 30, optionText: 'x² = 9', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 31, optionText: 'x² = 32', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 32, optionText: 'x² = 18', isCorrect: false, sequenceOrder: 4), + ], + ), + // 9. أقصى عدد حلول بين دائرة وقطع مكافئ + QuestionModel( + id: 9, + questionText: 'ما هو أقصى عدد ممكن من نقاط التقاطع بين دائرة وقطع مكافئ في المستوى الإحداثي؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'التقاطع الهندسي بين منحنيين تربيعيين', + explanationText: 'يمكن لدائرة وقطع مكافئ أن يتقاطعا في 0، أو 1، أو 2، أو 3، أو 4 نقاط كحد أقصى.', + options: [ + QuestionOptionModel(id: 33, optionText: '4 نقاط تقاطع (أربعة حلول)', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 34, optionText: 'حلان فقط', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 35, optionText: '6 نقاط تقاطع', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 36, optionText: 'حل وحيد فقط دائماً', isCorrect: false, sequenceOrder: 4), + ], + ), + // 10. التعويض بين معادلتين تربيعيتين + QuestionModel( + id: 10, + questionText: 'النظام: x² + y² = 13 و y = x² + 1 يتقاطع في النقطتين:', + questionType: 'multiple_choice', + points: 10, + topicTag: 'التعويض بين معادلتين تربيعيتين', + explanationText: 'بتعويض x² = y - 1 في الأولى: y - 1 + y² = 13 أي y² + y - 14 = 0 أو نجد بالنظام y=3 و x=±2.', + options: [ + QuestionOptionModel(id: 37, optionText: '(-2, 3) و (2, 3)', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 38, optionText: '(3, -2) و (3, 2)', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 39, optionText: '(0, 1) فقط', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 40, optionText: '(2, 5) و (-2, 5)', isCorrect: false, sequenceOrder: 4), + ], + ), + // 11. التناظر في أنظمة المعادلات + QuestionModel( + id: 11, + questionText: 'إذا كانت النقطة (a, b) حلاً لنظام يتكون من دائرة مركزها نقطة الأصل ومعادلة متماثلة حول المحور الصادي، فإن:', + questionType: 'multiple_choice', + points: 10, + topicTag: 'خصائص التناظر في أنظمة المعادلات', + explanationText: 'التماثل حول محور الصادات يعني أن استبدال x بـ (-x) يعطي نفس النتيجة، فالنقطة (-a, b) تكون أيضاً حلاً للنظام.', + options: [ + QuestionOptionModel(id: 41, optionText: '(-a, b) تكون أيضاً حلاً للنظام', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 42, optionText: '(a, -b) هو الحل الوحيد دائماً', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 43, optionText: 'لا يوجد أي تناظر هندسي', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 44, optionText: 'الحل سالب دائماً', isCorrect: false, sequenceOrder: 4), + ], + ), + // 12. برمجية جيوجبرا وأداة التقاطع + QuestionModel( + id: 12, + questionText: 'في برمجية جيوجبرا (GeoGebra)، ما هي الأداة المخصصة لإيجاد حل نظام المعادلات بيانياً؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'برمجية جيوجبرا وحل الأنظمة بيانياً', + explanationText: 'أداة التقاطع (Intersect Tool) في جيوجبرا تُحدد إحداثيات نقاط تقاطع المنحنيات التي تمثل حلول النظام مباشرة.', + options: [ + QuestionOptionModel(id: 45, optionText: 'أداة التقاطع (Intersect Tool)', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 46, optionText: 'أداة القياس (Measure)', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 47, optionText: 'أداة الانعكاس (Reflect)', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 48, optionText: 'أداة المماس (Tangent)', isCorrect: false, sequenceOrder: 4), + ], + ), + // 13. قراءة الإحداثيات في جيوجبرا + QuestionModel( + id: 13, + questionText: 'عند تمثيل معادلتين في جيوجبرا وظهور نقطة التقاطع A = (3, -2)، فهذا يعني هندسياً وجبرياً أن:', + questionType: 'multiple_choice', + points: 10, + topicTag: 'تفسير مخرجات برمجية جيوجبرا', + explanationText: 'إحداثيات نقطة التقاطع (x, y) تعني أن الزوج المرتب يحقق كلتا المعادلتين معاً في آن واحد، وهو حل النظام.', + options: [ + QuestionOptionModel(id: 49, optionText: 'x = 3 و y = -2 هو حل يحقق كلتا المعادلتين معاً', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 50, optionText: 'x = -2 و y = 3 هو الحل', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 51, optionText: 'المنحنيان متباعدان ولا حل لهما', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 52, optionText: 'النظام له حلول غير منتهية', isCorrect: false, sequenceOrder: 4), + ], + ), + // 14. النماذج الحياتية والأرصاد + QuestionModel( + id: 14, + questionText: 'لماذا يستخدم خبراء الأرصاد الجوية أنظمة معادلات غير خطية في التنبؤ بالطقس كما ورد في كتاب الطالب؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'التطبيقات الحياتية لأنظمة المعادلات', + explanationText: 'لأن أي تغير في أحد العوامل (كالضغط ودرجة الحرارة وسرعة الرياح) يؤدي إلى تغير غير خطي في العوامل الأخرى.', + options: [ + QuestionOptionModel(id: 53, optionText: 'لأن أي تغير في أحد العوامل يؤدي إلى تغير غير خطي في العوامل الأخرى', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 54, optionText: 'لأن درجة الحرارة ثابتة دائماً على مدار السنة', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 55, optionText: 'للتخلص من قياس الضغط الجوي', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 56, optionText: 'لأن سرعة الرياح لا ترتبط بحركة الغلاف الجوي', isCorrect: false, sequenceOrder: 4), + ], + ), + // 15. مسألة هندسية وتطبيقية + QuestionModel( + id: 15, + questionText: 'سياج مستطيل الشكل محيطه 20 متراً ومساحته 24 متراً مربعاً. ما هما بعدا المستطيل؟', + questionType: 'multiple_choice', + points: 10, + topicTag: 'حل المسائل الهندسية الحياتية باستخدام الأنظمة', + explanationText: 'النظام: 2(x + y) = 20 ومنها x + y = 10، والمساحة x * y = 24. العددان اللذان مجموعهما 10 وحاصل ضربهما 24 هما 6 و 4.', + options: [ + QuestionOptionModel(id: 57, optionText: 'الطول 6 m والعرض 4 m', isCorrect: true, sequenceOrder: 1), + QuestionOptionModel(id: 58, optionText: 'الطول 8 m والعرض 2 m', isCorrect: false, sequenceOrder: 2), + QuestionOptionModel(id: 59, optionText: 'الطول 10 m والعرض 2.4 m', isCorrect: false, sequenceOrder: 3), + QuestionOptionModel(id: 60, optionText: 'الطول 5 m والعرض 5 m', isCorrect: false, sequenceOrder: 4), ], ), ], diff --git a/apps/student_app/lib/presentation/screens/curriculum/curriculum_document_viewer_screen.dart b/apps/student_app/lib/presentation/screens/curriculum/curriculum_document_viewer_screen.dart new file mode 100644 index 0000000..53efff5 --- /dev/null +++ b/apps/student_app/lib/presentation/screens/curriculum/curriculum_document_viewer_screen.dart @@ -0,0 +1,308 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import '../../../core/theme/app_colors.dart'; +import '../../../core/utils/saqel_toast.dart'; +import '../../widgets/luxury_widgets.dart'; + +class CurriculumDocumentViewerScreen extends StatefulWidget { + final String title; + final String documentType; // 'worksheet', 'summary', 'textbook' + final String subjectTitle; + final String? customContent; + + const CurriculumDocumentViewerScreen({ + super.key, + required this.title, + required this.documentType, + required this.subjectTitle, + this.customContent, + }); + + @override + State createState() => _CurriculumDocumentViewerScreenState(); +} + +class _CurriculumDocumentViewerScreenState extends State { + double _fontSize = 14.5; + bool _showAnswer = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.darkBackground, + appBar: AppBar( + backgroundColor: AppColors.darkSurface, + elevation: 0, + title: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + widget.title, + style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700), + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + '${widget.subjectTitle} • تصفح وقراءة ذكية', + style: const TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontWeight: FontWeight.w600), + ), + ], + ), + centerTitle: true, + actions: [ + IconButton( + icon: const Icon(CupertinoIcons.textformat_size, color: Colors.white70, size: 20), + onPressed: () { + setState(() { + _fontSize = _fontSize >= 18.0 ? 13.0 : _fontSize + 1.5; + }); + SaqelToast.showInfo(context, 'تم ضبط حجم الخط (${_fontSize.toInt()}pt)', title: 'حجم الخط'); + }, + ), + IconButton( + icon: const Icon(CupertinoIcons.share, color: Colors.white70, size: 20), + onPressed: () { + SaqelToast.showSuccess(context, 'تم نسخ رابط المستند لمشاركته مع الزملاء', title: 'مشاركة الوثيقة 🔗'); + }, + ), + ], + ), + body: Directionality( + textDirection: TextDirection.rtl, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Document Status Banner + Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + AppColors.saqelCyan.withAlpha(25), + AppColors.appleBlue.withAlpha(20), + ], + ), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: AppColors.saqelCyan.withAlpha(40)), + ), + child: Row( + children: [ + const Icon(CupertinoIcons.doc_checkmark_fill, color: AppColors.saqelCyan, size: 22), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.documentType == 'textbook' ? 'الكتاب المدرسي المعتمد — وزارة التربية والتعليم' : 'ورقة عمل ومذكرة مراجعة معيارية', + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13), + ), + const SizedBox(height: 2), + const Text( + 'تمت مراجعة المحتوى وفهرسته ومطابقته لنتاجات الفصل الدراسي الأول', + style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 20), + + // Section 1: Objectives & Learning Outcomes + _buildSectionCard( + title: 'الأهداف والنتاجات المستهدفة 🎯', + content: [ + '1. حل المعادلات الخاصة بتحليل المقادير الجبرية وإخراج العامل المشترك الأكبر.', + '2. إيجاد نقاط التقاطع بين مستقيم وقطع مكافئ هندسياً وجبرياً بطريقة التعويض.', + '3. استخدام المميز (Δ) لتحديد عدد الحلول الحقيقية للنظام (حلان، حل وحيد، أو لا يوجد حل).', + '4. حل أنظمة المعادلات المكوّنة من معادلتيْن تربيعيتيْن بطريقة الحذف أو التعويض.', + '5. توظيف برمجية جيوجبرا في تمثيل المنحنيات وإيجاد نقط التقاطع بدقة.', + ], + ), + const SizedBox(height: 16), + + // Section 2: Core Theorems and Golden Rules + _buildSectionCard( + title: 'القواعد الذهبية والقوانين الوزارية 💡', + content: [ + '• قاعدة العامل الصفري: إذا كان A × B = 0 فإن A = 0 أو B = 0.', + '• الصورة التربيعية: المعادلة ax⁴ + bx² + c = 0 تُحل بالتعويض u = x² لتصبح au² + bu + c = 0.', + '• مميز المعادلة التربيعية: Δ = b² - 4ac:\n - إذا كان Δ > 0: للنظام حلّان حقيقيان مختلفان (يتقاطع المستقيم مع المنحنى في نقطتين).\n - إذا كان Δ = 0: للنظام حل حقيقي وحيد (المستقيم مماس للمنحنى).\n - إذا كان Δ < 0: لا يوجد أي حل حقيقي للنظام (المستقيم لا يتقاطع مع المنحنى).', + '• تقاطع دائرة وقطع مكافئ: أقصى عدد ممكن لنقاط التقاطع الحقيقية هو 4 نقاط.', + ], + ), + const SizedBox(height: 16), + + // Section 3: Solved Examples Step-by-Step + LuxuryCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'مثال نموذجي محلول خطوة بخطوة ✍️', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 15), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: AppColors.emeraldGreen.withAlpha(30), + borderRadius: BorderRadius.circular(8), + ), + child: const Text( + 'سؤال وزاري متوقع', + style: TextStyle(color: AppColors.emeraldGreen, fontSize: 11, fontWeight: FontWeight.w700), + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + 'المسألة: حُلّ نظام المعادلات التالي في مجموعة الأعداد الحقيقية:\n(1) y = x + 1\n(2) y = x² + 1', + style: TextStyle( + color: Colors.white, + fontSize: _fontSize, + fontWeight: FontWeight.w600, + height: 1.6, + ), + ), + const SizedBox(height: 14), + Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: Colors.black26, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: AppColors.darkCardBorder), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'خطوات الحل النموذجي:', + style: TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.w700, fontSize: 13), + ), + const SizedBox(height: 8), + Text( + '1. بمساواة المعادلتين بالتعويض عن قيمة y:\n x² + 1 = x + 1\n\n' + '2. بطرح (x + 1) من الطرفين لجعل المعادلة صفرية:\n x² - x = 0\n\n' + '3. بإخراج x كعامل مشترك أكبر:\n x(x - 1) = 0\n\n' + '4. إما x = 0 أو x = 1.\n\n' + '5. إيجاد قيمة y بالتعويض في المعادلة الخطية:\n' + ' - عندما x = 0 => y = 0 + 1 = 1 => نقطة التقاطع الأولى (0, 1)\n' + ' - عندما x = 1 => y = 1 + 1 = 2 => نقطة التقاطع الثانية (1, 2)\n\n' + 'مجموعة حل النظام هي: { (0, 1), (1, 2) }', + style: TextStyle( + color: AppColors.textSecondaryDark, + fontSize: _fontSize - 0.5, + height: 1.6, + fontFamily: 'SF Pro Text', + ), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 16), + + // Section 4: Self-Test Exercise with Reveal Answer + LuxuryCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Icon(CupertinoIcons.pencil_ellipsis_rectangle, color: AppColors.guardianAmber, size: 20), + SizedBox(width: 8), + Text( + 'تمرين تدريبي للتقييم الذاتي 📝', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 15), + ), + ], + ), + const SizedBox(height: 10), + Text( + 'حُلّ النظام التربيعي التالي بطريقة الحذف:\n(1) x² + y² = 25\n(2) x² - y² = 7', + style: TextStyle(color: Colors.white, fontSize: _fontSize, height: 1.6), + ), + const SizedBox(height: 14), + Center( + child: TextButton.icon( + style: TextButton.styleFrom( + foregroundColor: AppColors.saqelCyan, + backgroundColor: AppColors.saqelCyan.withAlpha(20), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + onPressed: () { + setState(() => _showAnswer = !_showAnswer); + }, + icon: Icon(_showAnswer ? CupertinoIcons.eye_slash : CupertinoIcons.eye, size: 18), + label: Text(_showAnswer ? 'إخفاء الحل النموذجي' : 'إظهار الحل النموذجي للتحقق 🔍'), + ), + ), + if (_showAnswer) ...[ + const SizedBox(height: 12), + Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColors.emeraldGreen.withAlpha(15), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppColors.emeraldGreen.withAlpha(50)), + ), + child: Text( + 'الحل:\nبجمع المعادلتين: 2x² = 32 => x² = 16 => x = ±4\n' + 'بالتعويض في الأولى: 16 + y² = 25 => y² = 9 => y = ±3\n' + 'حلول النظام الأربعة هي: (4, 3), (4, -3), (-4, 3), (-4, -3).', + style: TextStyle(color: Colors.white, fontSize: _fontSize - 1, height: 1.5), + ), + ), + ], + ], + ), + ), + const SizedBox(height: 30), + ], + ), + ), + ), + ); + } + + Widget _buildSectionCard({required String title, required List content}) { + return LuxuryCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 15), + ), + const SizedBox(height: 12), + ...content.map((line) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + line, + style: TextStyle( + color: AppColors.textSecondaryDark, + fontSize: _fontSize, + height: 1.55, + ), + ), + ); + }), + ], + ), + ); + } +} diff --git a/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart b/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart index 7054201..6778dac 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart @@ -7,6 +7,7 @@ import '../../../data/models/subject_model.dart'; import '../../widgets/luxury_widgets.dart'; import '../player/socratic_video_player_screen.dart'; import '../exams/adaptive_exam_screen.dart'; +import 'curriculum_document_viewer_screen.dart'; class SubjectHubScreen extends StatefulWidget { final SubjectModel subject; @@ -184,14 +185,7 @@ class _SubjectHubScreenState extends State with SingleTickerPr ), trailing: const Icon(CupertinoIcons.chevron_back, color: AppColors.textSecondaryDark, size: 16), onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => SocraticVideoPlayerScreen( - lesson: lesson, - subject: widget.subject, - ), - ), - ); + _showLessonVideoSelector(context, lesson); }, ), ); @@ -265,9 +259,9 @@ class _SubjectHubScreenState extends State with SingleTickerPr /// Tab 3: Question Bank & Adaptive Unit Exams Widget _buildExamsTab(BuildContext context) { final exams = [ - const SubjectExamModel(id: 'ex1', title: 'اختبار الفهم الشامل: الوحدة الأولى', questionsCount: 25, durationMinutes: 45, targetScore: 100), - const SubjectExamModel(id: 'ex2', title: 'نماذج أسئلة الوزارة للسنوات السابقة', questionsCount: 30, durationMinutes: 60, targetScore: 100), - const SubjectExamModel(id: 'ex3', title: 'اختبار تشخيص الثغرات التكيفي بالذكاء الاصطناعي', questionsCount: 15, durationMinutes: 25, targetScore: 100), + const SubjectExamModel(id: 'ex1', title: 'اختبار الفهم الشامل: الوحدة الأولى (أنظمة المعادلات)', questionsCount: 15, durationMinutes: 45, targetScore: 100), + const SubjectExamModel(id: 'ex2', title: 'نماذج أسئلة الوزارة للسنوات السابقة', questionsCount: 15, durationMinutes: 45, targetScore: 100), + const SubjectExamModel(id: 'ex3', title: 'اختبار تشخيص الثغرات التكيفي بالذكاء الاصطناعي', questionsCount: 15, durationMinutes: 30, targetScore: 100), ]; return ListView.builder( @@ -483,7 +477,15 @@ class _SubjectHubScreenState extends State with SingleTickerPr ), onPressed: () { Navigator.of(ctx).pop(); - SaqelToast.showInfo(context, 'جاري عرض محتوى $title بالكامل عبر عارض الكتب الذكي', title: 'فتح الوثيقة 📑'); + Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => CurriculumDocumentViewerScreen( + title: title, + documentType: title.contains('كتاب') ? 'textbook' : 'worksheet', + subjectTitle: widget.subject.title, + ), + ), + ); }, icon: const Icon(CupertinoIcons.book, size: 18), label: const Text('قراءة وتصفح', style: TextStyle(fontWeight: FontWeight.w800)), @@ -497,4 +499,210 @@ class _SubjectHubScreenState extends State with SingleTickerPr ), ); } + + /// Multi-Teacher and AI Video Selector Bottom Sheet + void _showLessonVideoSelector(BuildContext context, CurriculumLessonItemModel lesson) { + showModalBottomSheet( + context: context, + backgroundColor: AppColors.darkSurface, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + builder: (ctx) => SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.white24, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: AppColors.saqelCyan.withAlpha(25), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon(CupertinoIcons.play_circle_fill, color: AppColors.saqelCyan, size: 24), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'اختر أسلوب الشرح المناسب لك 🎥', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 16), + ), + const SizedBox(height: 3), + Text( + lesson.title, + style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 18), + + // Option 1: Official Saqel AI Master Lesson + _buildVideoOptionCard( + title: 'شرح الذكاء الاصطناعي لمنصة صَقِل 🤖', + subtitle: 'الشرح الرسمي المعتمد وتفكيك المفاهيم خطوة بخطوة', + tag: 'المنهاج المعتمد + سقراطي', + tagColor: AppColors.saqelCyan, + icon: CupertinoIcons.sparkles, + iconColor: AppColors.saqelCyan, + onTap: () { + Navigator.of(ctx).pop(); + Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => SocraticVideoPlayerScreen( + lesson: lesson, + subject: widget.subject, + instructorName: 'منصة صَقِل الذكية 🤖', + videoSourceLabel: 'شرح الذكاء الاصطناعي الرسمي', + ), + ), + ); + }, + ), + const SizedBox(height: 12), + + // Option 2: Certified Teacher 1 + _buildVideoOptionCard( + title: 'شرح الأستاذ رعد الخمايسة 👨‍🏫', + subtitle: 'خبير التوجيهي — حل نماذج وأسئلة وزارية إضافية', + tag: 'معلم معتمد 🌟 4.9', + tagColor: AppColors.guardianAmber, + icon: CupertinoIcons.person_crop_circle_badge_checkmark, + iconColor: AppColors.guardianAmber, + onTap: () { + Navigator.of(ctx).pop(); + Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => SocraticVideoPlayerScreen( + lesson: lesson, + subject: widget.subject, + instructorName: 'أ. رعد الخمايسة (معلم معتمد)', + videoSourceLabel: 'شرح الأستاذ رعد الخمايسة', + ), + ), + ); + }, + ), + const SizedBox(height: 12), + + // Option 3: Certified Teacher 2 + _buildVideoOptionCard( + title: 'شرح الأستاذ إياد النجار 👨‍🏫', + subtitle: 'كبير المعلمين — تبسيط المفاهيم المعقدة والتحليل البياني', + tag: 'معلم معتمد 🌟 4.8', + tagColor: AppColors.emeraldGreen, + icon: CupertinoIcons.person_crop_circle_fill, + iconColor: AppColors.emeraldGreen, + onTap: () { + Navigator.of(ctx).pop(); + Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => SocraticVideoPlayerScreen( + lesson: lesson, + subject: widget.subject, + instructorName: 'أ. إياد النجار (كبير المعلمين)', + videoSourceLabel: 'شرح الأستاذ إياد النجار', + ), + ), + ); + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ), + ); + } + + Widget _buildVideoOptionCard({ + required String title, + required String subtitle, + required String tag, + required Color tagColor, + required IconData icon, + required Color iconColor, + required VoidCallback onTap, + }) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(14), + child: Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: AppColors.darkCard, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: AppColors.darkCardBorder), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: iconColor.withAlpha(25), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: iconColor, size: 22), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13.5), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), + decoration: BoxDecoration( + color: tagColor.withAlpha(25), + borderRadius: BorderRadius.circular(6), + ), + child: Text( + tag, + style: TextStyle(color: tagColor, fontSize: 10, fontWeight: FontWeight.w700), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + subtitle, + style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11.5), + ), + ], + ), + ), + const SizedBox(width: 8), + const Icon(CupertinoIcons.chevron_back, color: AppColors.textSecondaryDark, size: 14), + ], + ), + ), + ); + } } diff --git a/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart b/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart index a85f7a8..5b67c0b 100644 --- a/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart +++ b/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart @@ -15,11 +15,15 @@ import 'package:video_player/video_player.dart'; class SocraticVideoPlayerScreen extends StatefulWidget { final CurriculumLessonItemModel lesson; final SubjectModel? subject; + final String? instructorName; + final String? videoSourceLabel; const SocraticVideoPlayerScreen({ super.key, required this.lesson, this.subject, + this.instructorName, + this.videoSourceLabel, }); @override @@ -593,6 +597,28 @@ class _SocraticVideoPlayerScreenState extends State w ), ], ), + if (widget.instructorName != null || widget.videoSourceLabel != null) ...[ + const SizedBox(height: 10), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: AppColors.saqelCyan.withAlpha(25), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppColors.saqelCyan.withAlpha(60)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(CupertinoIcons.person_crop_circle_fill, color: AppColors.saqelCyan, size: 14), + const SizedBox(width: 6), + Text( + widget.instructorName ?? widget.videoSourceLabel ?? 'شرح منصة صَقِل الذكية 🤖', + style: const TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontWeight: FontWeight.w700), + ), + ], + ), + ), + ], const SizedBox(height: 14), if (widget.lesson.outcomes.isNotEmpty) ...widget.lesson.outcomes.map((out) { diff --git a/backend/app/Controllers/ExamController.php b/backend/app/Controllers/ExamController.php index e60dd0c..43edad5 100644 --- a/backend/app/Controllers/ExamController.php +++ b/backend/app/Controllers/ExamController.php @@ -55,10 +55,16 @@ class ExamController */ public function getExamDetails(Request $request, Response $response): void { + self::ensureSchema(); $examId = (int)$request->getParam('id'); $isTeacher = ($request->role === 'teacher' || $request->role === 'super_admin'); $exam = Database::selectOne("SELECT * FROM exams WHERE id = ? LIMIT 1", [$examId]); + if (!$exam || $examId === 1 || (str_contains($exam['title'] ?? '', 'الوحدة الأولى') && count(Database::select("SELECT id FROM questions WHERE exam_id = ?", [$examId])) < 15)) { + $examId = self::seedUnit1ComprehensiveExam(); + $exam = Database::selectOne("SELECT * FROM exams WHERE id = ? LIMIT 1", [$examId]); + } + if (!$exam) { $response->status(404)->json(['status' => 'error', 'message' => 'الامتحان غير موجود']); return; @@ -357,4 +363,201 @@ class ExamController error_log("ExamController ensureSchema notice: " . $e->getMessage()); } } + + public static function seedUnit1ComprehensiveExam(): int + { + self::ensureSchema(); + + $exam = Database::selectOne( + "SELECT id FROM exams WHERE (scope = 'unit_comprehensive' OR scope = 'unit_exam' OR id = 1) AND (title LIKE '%الوحدة الأولى%' OR id = 1) LIMIT 1" + ); + + $examId = $exam ? (int)$exam['id'] : 0; + if (!$examId) { + $examId = (int)Database::insert( + "INSERT INTO exams (uuid, course_id, lesson_id, creator_type, scope, title, passing_percentage, total_points, duration_minutes, is_mandatory, is_published) + VALUES (UUID(), 1, 0, 'ai_adaptive', 'unit_comprehensive', 'اختبار الفهم الشامل: الوحدة الأولى — أنظمة المعادلات', 70.00, 100, 45, 1, 1)" + ); + } else { + Database::query( + "UPDATE exams SET title = 'اختبار الفهم الشامل: الوحدة الأولى — أنظمة المعادلات', duration_minutes = 45, total_points = 100, passing_percentage = 70.00, is_published = 1 WHERE id = ?", + [$examId] + ); + } + + $existingQuestions = Database::select("SELECT id, question_text FROM questions WHERE exam_id = ?", [$examId]); + $hasCalculus = false; + foreach ($existingQuestions as $eq) { + if (str_contains($eq['question_text'], 'مشتق') || str_contains($eq['question_text'], "f'(x)")) { + $hasCalculus = true; + break; + } + } + + if (count($existingQuestions) < 15 || $hasCalculus) { + // Wipe legacy corrupt questions + foreach ($existingQuestions as $oq) { + Database::query("DELETE FROM question_options WHERE question_id = ?", [$oq['id']]); + } + Database::query("DELETE FROM questions WHERE exam_id = ?", [$examId]); + + $bank = self::getUnit1QuestionBank(); + foreach ($bank as $qData) { + $qId = (int)Database::insert( + "INSERT INTO questions (uuid, exam_id, question_text, question_type, bloom_taxonomy, topic_tag, explanation_text, points) + VALUES (UUID(), ?, ?, 'multiple_choice', ?, ?, ?, ?)", + [$examId, $qData['question_text'], $qData['bloom'], $qData['topic'], $qData['explanation'], $qData['points']] + ); + foreach ($qData['options'] as $idx => $optText) { + Database::insert( + "INSERT INTO question_options (question_id, option_text, is_correct) VALUES (?, ?, ?)", + [$qId, $optText, ($idx === $qData['correct_index']) ? 1 : 0] + ); + } + } + } + + return $examId; + } + + public static function getUnit1QuestionBank(): array + { + return [ + [ + 'question_text' => 'ما هي مجموعة حل المعادلة الحقيقية: x³ - 4x = 0؟', + 'bloom' => 'recall', + 'topic' => 'حل المعادلات بإخراج العامل المشترك الأكبر', + 'explanation' => 'بإخراج x كعامل مشترك: x(x² - 4) = 0 ومنها x(x - 2)(x + 2) = 0، فيكون الحل x = 0, 2, -2.', + 'points' => 6, + 'options' => ['{ -2, 0, 2 }', '{ 0, 4 }', '{ -2, 2 }', '{ 0, 2 }'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'حل المعادلة x⁴ - 5x² + 4 = 0 في مجموعة الأعداد الحقيقية هو:', + 'bloom' => 'comprehension', + 'topic' => 'المعادلات في الصورة التربيعية', + 'explanation' => 'بالتحليل كمعادلة تربيعية: (x² - 4)(x² - 1) = 0، ومنها x² = 4 أو x² = 1، فالجذور هي ±2 و ±1.', + 'points' => 7, + 'options' => ['{ -2, -1, 1, 2 }', '{ 1, 4 }', '{ -1, 1 }', '{ -4, 4 }'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'كم حلاً حقيقياً يحقق المعادلة: x³ + 8 = 0؟', + 'bloom' => 'comprehension', + 'topic' => 'تحليل مجموع المكعبين والحلول الحقيقية', + 'explanation' => 'بتحليل مجموع مكعبين: (x + 2)(x² - 2x + 4) = 0. القوس التربيعي مميزه سالب (-12) فلا يعطي جذوراً حقيقية، والحل الحقيقي الوحيد هو x = -2.', + 'points' => 6, + 'options' => ['حل حقيقي واحد فقط وهو x = -2', 'ثلاثة حلول حقيقية', 'حلّان حقيقيان', 'لا يوجد أي حل حقيقي'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'ما هو أقصى عدد ممكن لنقاط التقاطع بين مستقيم وقطع مكافئ في المستوى الإحداثي؟', + 'bloom' => 'recall', + 'topic' => 'التمثيل الهندسي لنظام خطي وتربيعي', + 'explanation' => 'المستقيم يقطع القطع المكافئ في نقطتين كحد أقصى (حلان)، أو يمسه في نقطة (حل واحد)، أو لا يقطعه (لا يوجد حل حقيقي).', + 'points' => 6, + 'options' => ['نقطتان كحد أقصى (حلّان حقيقيان)', 'ثلاث نقاط تقاطع', 'أربع نقاط تقاطع', 'نقطة واحدة فقط دائماً'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'إذا كان لدينا النظام: y = x + 1 و y = x² + 1، فما هي نقاط تقاطع المنحنيين؟', + 'bloom' => 'application', + 'topic' => 'حل نظام خطي وتربيعي بطريقة التعويض', + 'explanation' => 'بالمساواة: x² + 1 = x + 1 ومنها x² - x = 0 أي x(x - 1) = 0، فيكون x = 0 أو x = 1. بالتعويض نجد y = 1 أو y = 2.', + 'points' => 7, + 'options' => ['(0, 1) و (1, 2)', '(0, 0) و (1, 1)', '(1, 2) فقط', '(-1, 0) و (1, 2)'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'متى لا يوجد أي حل حقيقي لنظام مكوّن من معادلة خطية وأخرى تربيعية؟', + 'bloom' => 'analysis', + 'topic' => 'استخدام المميز لتحديد عدد حلول النظام', + 'explanation' => 'إذا كان مميز المعادلة التربيعية الناتجة عن التعويض سالباً (Δ = b² - 4ac < 0)، فإن المستقيم لا يتقاطع مع المنحنى.', + 'points' => 7, + 'options' => ['عندما يكون مميز المعادلة التربيعية الناتجة سالباً (Δ < 0)', 'عندما يكون المميز مساوياً لصفر', 'عندما يكون المميز موجباً تماماً', 'إذا كان ميل المستقيم يساوي صفراً'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'إذا كان لنظام مكوّن من مستقيم وقطع مكافئ حل حقيقي وحيد فقط، فإن المستقيم يعتبر:', + 'bloom' => 'comprehension', + 'topic' => 'المستقيم المماس لمنحنى تربيعي', + 'explanation' => 'وجود حل حقيقي وحيد لنظام خطي-تربيعي يعني هندسياً أن المستقيم يمس المنحنى عند نقطة واحدة فقط.', + 'points' => 6, + 'options' => ['مماساً لمنحنى القطع المكافئ عند نقطة التماس', 'قاطعاً للمنحنى في نقطتين', 'خط تقارب رأسي للمنحنى', 'محور تماثل للقطع المكافئ'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'في النظام: x² + y² = 25 و x² - y² = 7، ما هي قيمة x²؟', + 'bloom' => 'application', + 'topic' => 'حل نظام تربيعي بطريقة الحذف', + 'explanation' => 'بجمع المعادلتين طرفاً لطرف: 2x² = 32 ومنها x² = 16.', + 'points' => 7, + 'options' => ['x² = 16', 'x² = 9', 'x² = 32', 'x² = 18'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'ما هو أقصى عدد ممكن من نقاط التقاطع بين دائرة وقطع مكافئ في المستوى الإحداثي؟', + 'bloom' => 'comprehension', + 'topic' => 'التقاطع الهندسي بين منحنيين تربيعيين', + 'explanation' => 'يمكن لدائرة وقطع مكافئ أن يتقاطعا في 0، أو 1، أو 2، أو 3، أو 4 نقاط كحد أقصى.', + 'points' => 7, + 'options' => ['4 نقاط تقاطع (أربعة حلول)', 'حلان فقط', '6 نقاط تقاطع', 'حل وحيد فقط دائماً'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'النظام: x² + y² = 13 و y = x² + 1 يتقاطع في النقطتين:', + 'bloom' => 'application', + 'topic' => 'التعويض بين معادلتين تربيعيتين', + 'explanation' => 'بتعويض x² = y - 1 في الأولى: y - 1 + y² = 13 أي y² + y - 14 = 0 أو نجد بالنظام y=3 و x=±2.', + 'points' => 7, + 'options' => ['(-2, 3) و (2, 3)', '(3, -2) و (3, 2)', '(0, 1) فقط', '(2, 5) و (-2, 5)'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'إذا كانت النقطة (a, b) حلاً لنظام يتكون من دائرة مركزها نقطة الأصل ومعادلة متماثلة حول المحور الصادي، فإن:', + 'bloom' => 'analysis', + 'topic' => 'خصائص التناظر في أنظمة المعادلات', + 'explanation' => 'التماثل حول محور الصادات يعني أن استبدال x بـ (-x) يعطي نفس النتيجة، فالنقطة (-a, b) تكون أيضاً حلاً للنظام.', + 'points' => 7, + 'options' => ['(-a, b) تكون أيضاً حلاً للنظام', '(a, -b) هو الحل الوحيد دائماً', 'لا يوجد أي تناظر هندسي', 'الحل سالب دائماً'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'في برمجية جيوجبرا (GeoGebra)، ما هي الأداة المخصصة لإيجاد حل نظام المعادلات بيانياً؟', + 'bloom' => 'recall', + 'topic' => 'برمجية جيوجبرا وحل الأنظمة بيانياً', + 'explanation' => 'أداة التقاطع (Intersect Tool) في جيوجبرا تُحدد إحداثيات نقاط تقاطع المنحنيات التي تمثل حلول النظام مباشرة.', + 'points' => 6, + 'options' => ['أداة التقاطع (Intersect Tool)', 'أداة القياس (Measure)', 'أداة الانعكاس (Reflect)', 'أداة المماس (Tangent)'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'عند تمثيل معادلتين في جيوجبرا وظهور نقطة التقاطع A = (3, -2)، فهذا يعني هندسياً وجبرياً أن:', + 'bloom' => 'comprehension', + 'topic' => 'تفسير مخرجات برمجية جيوجبرا', + 'explanation' => 'إحداثيات نقطة التقاطع (x, y) تعني أن الزوج المرتب يحقق كلتا المعادلتين معاً في آن واحد، وهو حل النظام.', + 'points' => 6, + 'options' => ['x = 3 و y = -2 هو حل يحقق كلتا المعادلتين معاً', 'x = -2 و y = 3 هو الحل', 'المنحنيان متباعدان ولا حل لهما', 'النظام له حلول غير منتهية'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'لماذا يستخدم خبراء الأرصاد الجوية أنظمة معادلات غير خطية في التنبؤ بالطقس كما ورد في كتاب الطالب؟', + 'bloom' => 'comprehension', + 'topic' => 'التطبيقات الحياتية لأنظمة المعادلات', + 'explanation' => 'لأن أي تغير في أحد العوامل (كالضغط ودرجة الحرارة وسرعة الرياح) يؤدي إلى تغير غير خطي في العوامل الأخرى.', + 'points' => 7, + 'options' => ['لأن أي تغير في أحد العوامل يؤدي إلى تغير غير خطي في العوامل الأخرى', 'لأن درجة الحرارة ثابتة دائماً على مدار السنة', 'للتخلص من قياس الضغط الجوي', 'لأن سرعة الرياح لا ترتبط بحركة الغلاف الجوي'], + 'correct_index' => 0 + ], + [ + 'question_text' => 'سياج مستطيل الشكل محيطه 20 متراً ومساحته 24 متراً مربعاً. ما هما بعدا المستطيل؟', + 'bloom' => 'application', + 'topic' => 'حل المسائل الهندسية الحياتية باستخدام الأنظمة', + 'explanation' => 'النظام: 2(x + y) = 20 ومنها x + y = 10، والمساحة x * y = 24. العددان اللذان مجموعهما 10 وحاصل ضربهما 24 هما 6 و 4.', + 'points' => 8, + 'options' => ['الطول 6 m والعرض 4 m', 'الطول 8 m والعرض 2 m', 'الطول 10 m والعرض 2.4 m', 'الطول 5 m والعرض 5 m'], + 'correct_index' => 0 + ] + ]; + } } diff --git a/backend/app/Services/AiLessonEnhancerService.php b/backend/app/Services/AiLessonEnhancerService.php index 51d35aa..e8d03ae 100644 --- a/backend/app/Services/AiLessonEnhancerService.php +++ b/backend/app/Services/AiLessonEnhancerService.php @@ -69,6 +69,29 @@ class AiLessonEnhancerService محتوى الدرس: {$content}"; } + /** + * Builds an expert AI prompt to generate standalone HTML5/Canvas interactive lab simulations + * for science and physics lessons. + */ + public static function buildSimulationPrompt(string $title, string $subject, string $content): string + { + return "أنت مهندس برمجيات تعليمية ومطور محاكيات فيزيائية تفاعلية (HTML5 Canvas Interactive Simulation Specialist). +المطلوب: توليد كود صفحة HTML5 كاملة ومستقلة تماماً (تتضمن CSS مدمج وجافاسكربت نقي بدون أي مكتبات خارجية) لتمثيل تجربة معملية تفاعلية للموضوع التالي: + +الموضوع: {$title} +المادة: {$subject} +المحتوى والنتاجات العلمية: +{$content} + +المعايير الصارمة للتوليد: +1. التصميم: مظهر داكن فاخر (Dark OLED Theme: #06090f, #0d111a) بهوية أبل كوبرتينو وألوان صَقِل (Cyan #00f5d4, Amber #ffd166, Emerald #10b981). +2. التفاعلية: استخدام HTML5 Canvas مع متحكمات انزلاقية (Sliders) تتيح للطالب تغيير المتغيرات الفيزيائية (الكتلة، القوة، الزاوية، السرعة...). +3. الحسابات الحية: عرض النتائج الرياضية والمعادلات والقوانين فورياً مع تحديث الرسم البياني على الكانفاس في كل إطار. +4. التحدي السقراطي: صندوق تحدي سقراطي يطلب من الطالب تجربة حالة خاصة (مثل: ماذا يحدث عند مضاعفة القوة أو جعل الزاوية 180 درجة؟). +5. اللغة: واجهة عربية كاملة من اليمين إلى اليسار (dir='rtl'). +6. يجب أن يكون الناتج كود HTML خالص فقط صالح للتضمين الفوري داخل iframe أو WebView."; + } + private static function callGemini(string $prompt): ?array { $apiKey = Env::get('GEMINI_API_KEY') ?: getenv('GEMINI_API_KEY'); diff --git a/backend/storage/curriculum/simulations/physics_10/vectors_lab.html b/backend/storage/curriculum/simulations/physics_10/vectors_lab.html new file mode 100644 index 0000000..3e6947b --- /dev/null +++ b/backend/storage/curriculum/simulations/physics_10/vectors_lab.html @@ -0,0 +1,228 @@ + + + + + + مختبر صَقِل التفاعلي: جمع وتحليل المتجهات + + + + +
+

🧪 مختبر جمع وتحليل المتجهات التفاعلي

+ فيزياء 10 • الوحدة 1 +
+ +
+ +
+ +
+ + +
+ +
+
+ المتجه A (السماوي) + |A| = 100 N, θ = 30° +
+ + + + +
+ + +
+
+ المتجه B (الذهبي) + |B| = 80 N, θ = 120° +
+ + + + +
+ + +
+ + +
+ + +
+ حسابات التحليل المتعامد... +
+ + +
+ 💡 فحص سقراطي مباشر: + اجعل الزاوية بين المتجهين 180° وشاهد ماذا يحدث لقيمة المحصلة R! هل تكون قيمة صغرى أم عظمى؟ +
+
+
+ + + +