diff --git a/apps/admin_app/lib/models/directorate_models.dart b/apps/admin_app/lib/models/directorate_models.dart index 07a51b7..ed93491 100644 --- a/apps/admin_app/lib/models/directorate_models.dart +++ b/apps/admin_app/lib/models/directorate_models.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - /// Directorate Model (المظلة المركزية: مديرية الثقافة العسكرية) class DirectorateOverview { final String code; diff --git a/apps/admin_app/lib/screens/school_principal_screen.dart b/apps/admin_app/lib/screens/school_principal_screen.dart index 69b7521..e040226 100644 --- a/apps/admin_app/lib/screens/school_principal_screen.dart +++ b/apps/admin_app/lib/screens/school_principal_screen.dart @@ -105,7 +105,7 @@ class _SchoolPrincipalScreenState extends State } void _showDualFormsPreview() async { - final data = await DirectorateApiService.fetchDualForms(); + await DirectorateApiService.fetchDualForms(); if (!mounted) return; showModalBottomSheet( diff --git a/apps/admin_app/pubspec.lock b/apps/admin_app/pubspec.lock index cf9fe61..0d26e35 100644 --- a/apps/admin_app/pubspec.lock +++ b/apps/admin_app/pubspec.lock @@ -316,10 +316,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -332,10 +332,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" nested: dependency: transitive description: @@ -561,10 +561,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.11" typed_data: dependency: transitive description: diff --git a/apps/student_app/lib/data/datasources/curriculum_baked_data.dart b/apps/student_app/lib/data/datasources/curriculum_baked_data.dart index b63a112..352c03e 100644 --- a/apps/student_app/lib/data/datasources/curriculum_baked_data.dart +++ b/apps/student_app/lib/data/datasources/curriculum_baked_data.dart @@ -84,7 +84,7 @@ class CurriculumBakedData { #### البند 1: حل المعادلات بإخراج العامل المشترك الأكبر - **قاعدة:** نجعل طرف المعادلة الأيمن صفراً، ثم نخرج العامل المشترك، ونحل المعادلة الناتجة. - **مثال 1:** حل المعادلة: \$x^3 + 4x^2 = 5x\$ - $$\$x^3 + 4x^2 - 5x = 0 \\implies x(x^2 + 4x - 5) = 0 \\implies x(x + 5)(x - 1) = 0\$$ + \$\$x^3 + 4x^2 - 5x = 0 \\implies x(x^2 + 4x - 5) = 0 \\implies x(x + 5)(x - 1) = 0\$\$ إذن جذور المعادلة هي: \$\\{-5, 0, 1\\}\$. - **أتحقق من فهمي (صفحة 9):** @@ -97,10 +97,10 @@ class CurriculumBakedData { - **المفهوم:** نقطة تقاطع المستقيم والمنحنى تمثل حل النظام المشترك. - **طريقة التعويض:** نجعل أحد المتغيرين موضوعاً للقانون في المعادلة الخطية ونعوضه في المعادلة التربيعية. - **مثال محلول:** - $$\\begin{cases} y - x = 1 \\\\ x^2 + y^2 = 25 \\end{cases}$$ + \$\$\\begin{cases} y - x = 1 \\\\ x^2 + y^2 = 25 \\end{cases}\$\$ من المعادلة الأولى: \$y = x + 1\$. نعوض في الثانية: - $$\$x^2 + (x + 1)^2 = 25 \\implies 2x^2 + 2x - 24 = 0 \\implies x^2 + x - 12 = 0\$$ - $$\$(x + 4)(x - 3) = 0 \\implies x = 3 \\text{ أو } x = -4\$$ + \$\$x^2 + (x + 1)^2 = 25 \\implies 2x^2 + 2x - 24 = 0 \\implies x^2 + x - 12 = 0\$\$ + \$\$(x + 4)(x - 3) = 0 \\implies x = 3 \\text{ أو } x = -4\$\$ - عندما \$x = 3 \\implies y = 4\$ (النقطة: \$(3, 4)\$) - عندما \$x = -4 \\implies y = -3\$ (النقطة: \$( -4, -3)\$) @@ -144,11 +144,11 @@ class CurriculumBakedData { --- ### السؤال الثاني: حل النظام الجبري التالي وتحقق من صحة الحل (6 علامات) -$$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}$$ +\$\$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}\$\$ **خطوات الإجابة:** - نعوض \$y = 2x + 3\$ في المعادلة الثانية: - $$\$x^2 + 2x + 3 = 6 \\implies x^2 + 2x - 3 = 0\$$ + \$\$x^2 + 2x + 3 = 6 \\implies x^2 + 2x - 3 = 0\$\$ - التحليل: \$(x + 3)(x - 1) = 0\$. - إذا \$x = 1 \\implies y = 2(1) + 3 = 5\$ ➔ الحل الأول: \$(1, 5)\$. - إذا \$x = -3 \\implies y = 2(-3) + 3 = -3\$ ➔ الحل الثاني: \$( -3, -3)\$. @@ -193,12 +193,12 @@ $$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}$$ ### ضرب المتجهات (Vector Multiplication): 1. **الضرب القياسي / النقطي (Dot Product):** - $$\\vec{A} \\cdot \\vec{B} = |A| |B| \\cos\\phi$$ + \$\$\\vec{A} \\cdot \\vec{B} = |A| |B| \\cos\\phi\$\$ - الناتج كمية قياسية (مثل حساب الشغل: \$W = \\vec{F} \\cdot \\vec{d}\$). - ينعدم الضرب القياسي إذا كان المتجهان متعامدين (\$\\phi = 90^\\circ\$ لأن \$\\cos 90^\\circ = 0\$). 2. **الضرب المتجهي / التقاطعي (Cross Product):** - $$|\\vec{A} \\times \\vec{B}| = |A| |B| \\sin\\phi$$ + \$\$|\\vec{A} \\times \\vec{B}| = |A| |B| \\sin\\phi\$\$ - الناتج متجه عمودي على المستوى الذي يحوي المتجهين، ويُحدد اتجاهه باستخدام قاعدة اليد اليمنى. """; @@ -243,7 +243,7 @@ $$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}$$ 2. تعليق كتلة \$m_2 = 200\\text{ g}\$ عند زاوية \$90^\\circ\$. 3. اتزان الحلقة في المنتصف بوضع قوة موازنة ثالثة في الربع المقابل بمقدار \$250\\text{ g}\$ وزاوية \$233^\\circ\$. 4. **الاستنتاج:** محصلة قوتين متعامدتين تُحسب بفيثاغورس: - $$R = \\sqrt{F_1^2 + F_2^2} = \\sqrt{150^2 + 200^2} = 250\\text{ gf}$$ + \$\$R = \\sqrt{F_1^2 + F_2^2} = \\sqrt{150^2 + 200^2} = 250\\text{ gf}\$\$ """; // ============================================================================ diff --git a/apps/student_app/lib/data/models/subject_model.dart b/apps/student_app/lib/data/models/subject_model.dart index 144f9a1..e0d1c3e 100644 --- a/apps/student_app/lib/data/models/subject_model.dart +++ b/apps/student_app/lib/data/models/subject_model.dart @@ -224,20 +224,36 @@ class CurriculumLessonItemModel { } final filePath = json['file']?.toString(); - // Default video availability: true for core Grade 10 subjects (Math, Physics, English) and explicitly tagged lessons - final bool isCoreTenthSubjectLesson = filePath != null && ( - filePath.contains('math_10/semester_1') || - filePath.contains('physics_10/semester_1') || - filePath.contains('english_10/semester_1') || - filePath.contains('lesson_01') || - filePath.contains('lesson_1a') + final jsonId = json['id']?.toString(); + + // Stable, deterministic lesson ID (vital for persistent per-lesson progress tracking) + final stableId = (jsonId != null && jsonId.isNotEmpty) + ? jsonId + : (filePath != null && filePath.isNotEmpty + ? filePath.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_') + : '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) || + ((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 = (json['has_video'] as bool?) ?? - ((json['video_url'] != null && json['video_url'].toString().isNotEmpty) || isCoreTenthSubjectLesson); + final bool hasVideoExplicit = !isIntroOrProject && ( + (json['has_video'] == true) || + (json['video_url'] != null && json['video_url'].toString().isNotEmpty) || + isMath10Lesson1 + ); return CurriculumLessonItemModel( - id: json['id']?.toString() ?? 'lesson_${DateTime.now().millisecondsSinceEpoch}', + id: stableId, title: json['title']?.toString() ?? 'درس بدون عنوان', outcomes: outs, markdownFilePath: filePath, diff --git a/apps/student_app/lib/logic/cubits/video_playback_cubit.dart b/apps/student_app/lib/logic/cubits/video_playback_cubit.dart index 7f10ac1..561a759 100644 --- a/apps/student_app/lib/logic/cubits/video_playback_cubit.dart +++ b/apps/student_app/lib/logic/cubits/video_playback_cubit.dart @@ -76,19 +76,34 @@ class VideoPlaybackCubit extends Cubit { : _repo = repo ?? CurriculumRepository(), super(VideoPlaybackInitial()); + String _getLessonStorageKey(CurriculumLessonItemModel? lesson, {String? fallbackId}) { + if (lesson?.markdownFilePath != null && lesson!.markdownFilePath!.isNotEmpty) { + return lesson.markdownFilePath!.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_'); + } + if (lesson?.id != null && lesson!.id.isNotEmpty) { + return lesson.id.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_'); + } + return fallbackId ?? 'lesson_default'; + } + Future loadLesson(CurriculumLessonItemModel lesson, {SubjectModel? subject}) async { AppLogger.log('Loading Socratic playback for lesson: ${lesson.title}', tag: 'VIDEO_CUBIT'); _lastObservedPosition = -1; emit(VideoPlaybackLoading()); + final storageKey = _getLessonStorageKey(lesson); + try { final playback = await _repo.getLessonPlayback(lesson.markdownFilePath ?? lesson.id, subjectId: subject?.id); - // Load saved resume position + // Load saved resume position strictly per video int resumePos = playback.lastPositionSeconds ?? 0; + Set passedIds = {}; try { final prefs = await SharedPreferences.getInstance(); - final localPos = prefs.getInt('saved_video_pos_${lesson.id}') ?? 0; + final localPos = prefs.getInt('saved_video_pos_$storageKey') ?? 0; if (localPos > resumePos) resumePos = localPos; + final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? []; + passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet(); } catch (_) {} emit(VideoPlaybackReady( @@ -96,18 +111,22 @@ class VideoPlaybackCubit extends Cubit { lessonItem: lesson, subject: subject, currentPositionSeconds: resumePos, + passedCheckpointIds: passedIds, isPlaying: true, )); } catch (e) { AppLogger.log('Playback API unavailable (${lesson.title}): $e — Switching to offline Socratic fallback', tag: 'VIDEO_CUBIT'); final fallback = _buildResilientLessonPlayback(lesson, subject: subject); - // Load saved resume position + // Load saved resume position strictly per video int resumePos = fallback.lastPositionSeconds ?? 0; + Set passedIds = {}; try { final prefs = await SharedPreferences.getInstance(); - final localPos = prefs.getInt('saved_video_pos_${lesson.id}') ?? 0; + final localPos = prefs.getInt('saved_video_pos_$storageKey') ?? 0; if (localPos > resumePos) resumePos = localPos; + final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? []; + passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet(); } catch (_) {} emit(VideoPlaybackReady( @@ -115,6 +134,7 @@ class VideoPlaybackCubit extends Cubit { lessonItem: lesson, subject: subject, currentPositionSeconds: resumePos, + passedCheckpointIds: passedIds, isPlaying: true, )); } @@ -197,21 +217,12 @@ class VideoPlaybackCubit extends Cubit { LessonVersionModel( lessonId: 101, isAi: true, - teacherName: 'منصة صَقِل الذكية 🤖', + teacherName: 'منصة صَقِل التعليمية المعتمدة', schoolName: 'المركز الرقمي المعتمد', - label: 'شرح الذكاء الاصطناعي الرسمي', + label: 'الشرح الرقمي الرسمي المعتمد', isRecommended: true, videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', ), - LessonVersionModel( - lessonId: 102, - isAi: false, - teacherName: 'أ. أحمد المجالي 👨‍🏫', - schoolName: 'مدارس الثقافة العسكرية', - label: 'شرح معلم معتمد 🌟 4.9', - isRecommended: false, - videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', - ), ], ); } @@ -279,12 +290,16 @@ class VideoPlaybackCubit extends Cubit { final currentState = state; if (currentState is! VideoPlaybackReady) return; try { + final storageKey = _getLessonStorageKey(currentState.lessonItem, fallbackId: '${currentState.playbackData.lessonId}'); final prefs = await SharedPreferences.getInstance(); - if (currentState.lessonItem != null) { - await prefs.setInt('saved_video_pos_${currentState.lessonItem!.id}', positionSeconds); + await prefs.setInt('saved_video_pos_$storageKey', positionSeconds); + if (currentState.passedCheckpointIds.isNotEmpty) { + await prefs.setStringList( + 'passed_checkpoints_$storageKey', + currentState.passedCheckpointIds.map((id) => id.toString()).toList(), + ); } if (currentState.playbackData.lessonId > 0) { - await prefs.setInt('saved_video_pos_${currentState.playbackData.lessonId}', positionSeconds); await _repo.saveProgress( lessonId: currentState.playbackData.lessonId, positionSeconds: positionSeconds, @@ -307,6 +322,13 @@ class VideoPlaybackCubit extends Cubit { AppLogger.log('✅ Correct answer! Rewarding +0.5% readiness.', tag: 'SOCRATIC_ENGINE'); final updatedPassed = Set.from(currentState.passedCheckpointIds)..add(cp.id); _lastObservedPosition = cp.timestampSeconds; + + // Persist passed checkpoint for this specific video + final storageKey = _getLessonStorageKey(currentState.lessonItem, fallbackId: '${currentState.playbackData.lessonId}'); + SharedPreferences.getInstance().then((prefs) { + prefs.setStringList('passed_checkpoints_$storageKey', updatedPassed.map((id) => id.toString()).toList()); + }).catchError((_) {}); + emit(currentState.copyWith( clearActiveCheckpoint: true, isPlaying: true, diff --git a/apps/student_app/lib/presentation/screens/curriculum/arabic_interactive_lab_view.dart b/apps/student_app/lib/presentation/screens/curriculum/arabic_interactive_lab_view.dart index a44a246..d359902 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/arabic_interactive_lab_view.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/arabic_interactive_lab_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../../../core/theme/app_colors.dart'; /// ============================================================================== /// SAQEL ENTERPRISE - ARABIC INTERACTIVE LAB (شجرة الإعراب ومختبر العروض) @@ -140,12 +139,109 @@ class _ArabicInteractiveLabViewState extends State }, ], }, + { + 'fullText': 'المُعَلِّمُ المُخْلِصُ مُحْتَرَمٌ قَدْرُهُ بَيْنَ النَّاسِ', + 'type': 'جملة اسمية تشتمل على اسم فاعل واسم مفعول وعملهما', + 'rootNode': 'الجملة الاسمية والمشتقات العاملة (الصف العاشر)', + 'words': [ + { + 'word': 'المُعَلِّمُ', + 'role': 'مبتدأ مرفوع (اسم فاعل من غير الثلاثي)', + 'case': 'مرفوع وعلامة رفعه الضمة الظاهرة على آخره', + 'explanation': 'اسم معرف بأل وقع في أول الجملة، وهو اسم فاعل من الفعل علّم يُعلّم.', + 'tag': 'مبتدأ / اسم فاعل', + }, + { + 'word': 'المُخْلِصُ', + 'role': 'نعت (صفة) للمبتدأ', + 'case': 'مرفوع وعلامة رفعه الضمة الظاهرة', + 'explanation': 'نعت حقيقي طابق المنعوت في التذكير والتعريف والإعراب.', + 'tag': 'نعت مرفوع', + }, + { + 'word': 'مُحْتَرَمٌ', + 'role': 'خبر المبتدأ (اسم مفعول عامل)', + 'case': 'مرفوع وعلامة رفعه الضمة الظاهرة', + 'explanation': 'خبر أتم معنى الجملة، وهو مشتق (اسم مفعول) يعمل عمل فعله المبني للمجهول.', + 'tag': 'خبر / اسم مفعول', + }, + { + 'word': 'قَدْرُهُ', + 'role': 'نائب فاعل لاسم المفعول (مُحترَم)', + 'case': 'مرفوع بالضمة، والهاء ضمير متصل مبني في محل جر مضاف إليه', + 'explanation': 'اسم المفعول يعمل عمل فعله المبني للمجهول فيرفع نائب فاعل بعده.', + 'tag': 'نائب فاعل', + }, + { + 'word': 'بَيْنَ', + 'role': 'ظرف مكان منصوب (مفعول فيه)', + 'case': 'منصوب بالفتحة وهو مضاف', + 'explanation': 'ظرف مكان يحدد موقع الاتصاف بالاحترام.', + 'tag': 'ظرف مكان', + }, + { + 'word': 'النَّاسِ', + 'role': 'مضاف إليه مجرور', + 'case': 'مجرور وعلامة جره الكسرة الظاهرة على آخره', + 'explanation': 'اسم مجرور بالإضافة جاء بعد ظرف المكان.', + 'tag': 'مضاف إليه', + }, + ], + }, + { + 'fullText': 'مَنْ يَزْرَعْ خَيْراً يَحْصُدْ ثِمَاراً طَيِّبَةً', + 'type': 'أسلوب شرط جازم (منهاج العاشر الأساسي)', + 'rootNode': 'أسلوب الشرط (اسم شرط + فعل الشرط + جواب الشرط)', + 'words': [ + { + 'word': 'مَنْ', + 'role': 'اسم شرط جازم مبني', + 'case': 'مبني على السكون في محل رفع مبتدأ', + 'explanation': 'اسم شرط جازم يدل على العاقل ويربط بين جملتين.', + 'tag': 'اسم شرط', + }, + { + 'word': 'يَزْرَعْ', + 'role': 'فعل الشرط مضارع مجزوم', + 'case': 'مجزوم بالسكون، والفاعل ضمير مستتر تقديره (هو)', + 'explanation': 'فعل مضارع وقع بعد أداة الشرط الجازمة، فجُزم بالسكون.', + 'tag': 'فعل شرط مجزوم', + }, + { + 'word': 'خَيْراً', + 'role': 'مفعول به لفعل الشرط', + 'case': 'منصوب وعلامة نصبه تنوين الفتح', + 'explanation': 'وقع عليه فعل الزراعة المعنوي.', + 'tag': 'مفعول به', + }, + { + 'word': 'يَحْصُدْ', + 'role': 'جواب الشرط وجزاؤه مضارع مجزوم', + 'case': 'مجزوم بالسكون، والفاعل مستتر تقديره (هو)', + 'explanation': 'فعل مضارع جُزم لوقوعه جواباً للشرط.', + 'tag': 'جواب شرط مجزوم', + }, + { + 'word': 'ثِمَاراً', + 'role': 'مفعول به لجواب الشرط', + 'case': 'منصوب وعلامة نصبه تنوين الفتح الظاهر', + 'explanation': 'الشيء المحصود نتيجة العمل.', + 'tag': 'مفعول به', + }, + { + 'word': 'طَيِّبَةً', + 'role': 'نعت لثماراً', + 'case': 'منصوب وعلامة نصبه تنوين الفتح', + 'explanation': 'صفة طابقت موصوفها في التنكير والجمع والتأنيث والنصب.', + 'tag': 'نعت منصوب', + }, + ], + }, ]; // Poetry Meter State int _selectedMeterIndex = 0; bool _isPlayingBeat = false; - int _currentBeatStep = 0; final List> _poetryMeters = [ { @@ -186,7 +282,9 @@ class _ArabicInteractiveLabViewState extends State @override void initState() { super.initState(); - _tabController = TabController(length: 2, vsync: this); + _tabController = TabController(length: 2, vsync: this)..addListener(() { + if (mounted) setState(() {}); + }); } @override @@ -339,8 +437,8 @@ class _ArabicInteractiveLabViewState extends State _selectedWordIndex = null; }), child: Container( - margin: EdgeInsets.only(left: idx == 0 ? 8 : 0), - padding: const EdgeInsets.symmetric(vertical: 10), + margin: const EdgeInsets.only(left: 8), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: isCur ? const Color(0xFF1E293B) diff --git a/apps/student_app/lib/presentation/screens/curriculum/history_interactive_timeline_view.dart b/apps/student_app/lib/presentation/screens/curriculum/history_interactive_timeline_view.dart index b284280..9cdf66b 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/history_interactive_timeline_view.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/history_interactive_timeline_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../../../core/theme/app_colors.dart'; /// ============================================================================== /// SAQEL ENTERPRISE - JORDAN HISTORY INTERACTIVE TIMELINE (الخط الزمني لتاريخ الأردن) diff --git a/apps/student_app/lib/presentation/screens/curriculum/islamic_interactive_lab_view.dart b/apps/student_app/lib/presentation/screens/curriculum/islamic_interactive_lab_view.dart index ffff9a6..d516038 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/islamic_interactive_lab_view.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/islamic_interactive_lab_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../../../core/theme/app_colors.dart'; /// ============================================================================== /// SAQEL ENTERPRISE - ISLAMIC INHERITANCE & TAJWEED LAB (حاسبة المواريث والتجويد) 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 1dd1435..ee60f4b 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 @@ -787,211 +787,21 @@ class _SubjectHubScreenState extends State with SingleTickerPr ); } - /// Multi-Teacher and AI Video Selector Bottom Sheet + /// Direct Launch of Socratic Video Player or Transparent No-Video Sheet void _showLessonVideoSelector(BuildContext context, CurriculumLessonItemModel lesson) { if (!lesson.hasVideo) { _showNoVideoAvailableSheet(context, lesson); return; } - 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), - ], + // Direct authentic launch: zero mockups, zero fake teacher names + Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => SocraticVideoPlayerScreen( + lesson: lesson, + subject: widget.subject, + instructorName: 'منصة صَقِل التعليمية المعتمدة', + videoSourceLabel: 'الشرح الرقمي الرسمي المعتمد', ), ), ); diff --git a/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart b/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart index 456e4e6..2e5a1ec 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart @@ -188,45 +188,42 @@ class _SubjectsGridScreenState extends State { ), const SizedBox(height: 16), - // K-12 Grade Level Selector Bar (الصف الأول -> التوجيهي) - SizedBox( - height: 42, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: (state.availableGrades.isNotEmpty ? state.availableGrades : GradeLevelModel.allK12Grades).length, - itemBuilder: (context, gIdx) { - final grade = (state.availableGrades.isNotEmpty ? state.availableGrades : GradeLevelModel.allK12Grades)[gIdx]; - final isSelected = state.selectedGrade == grade.key || state.selectedGrade.replaceAll('grade_', '') == grade.key.replaceAll('grade_', ''); - - return Padding( - padding: const EdgeInsets.only(left: 8), - child: InkWell( - onTap: () => context.read().selectGrade(grade.key), - borderRadius: BorderRadius.circular(14), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: isSelected ? AppColors.saqelCyan.withAlpha(30) : AppColors.darkSurface, - borderRadius: BorderRadius.circular(14), - border: Border.all( - color: isSelected ? AppColors.saqelCyan : AppColors.darkCardBorder, - width: isSelected ? 1.5 : 1.0, - ), - ), - child: Center( - child: Text( - grade.name, - style: TextStyle( - color: isSelected ? AppColors.saqelCyan : AppColors.textSecondaryDark, - fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500, - fontSize: 12.5, - ), - ), - ), - ), + // Official Academic Grade Level Banner (Strict Grade-Gate — الصف العاشر فقط) + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: AppColors.saqelCyan.withAlpha(20), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColors.saqelCyan.withAlpha(60)), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColors.saqelCyan.withAlpha(35), + borderRadius: BorderRadius.circular(10), ), - ); - }, + child: const Icon(CupertinoIcons.lock_shield_fill, color: AppColors.saqelCyan, size: 18), + ), + const SizedBox(width: 12), + const Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'الصف العاشر الأساسي — المسار الأكاديمي المعتمد', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5), + ), + SizedBox(height: 2), + Text( + 'خطة دراسية موحدة ومطابقة للمنهاج الوزاري الأردني المطور', + style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11), + ), + ], + ), + ), + ], ), ), const SizedBox(height: 20), diff --git a/apps/student_app/lib/presentation/screens/notebook/smart_error_notebook_screen.dart b/apps/student_app/lib/presentation/screens/notebook/smart_error_notebook_screen.dart index 4664096..3f9d375 100644 --- a/apps/student_app/lib/presentation/screens/notebook/smart_error_notebook_screen.dart +++ b/apps/student_app/lib/presentation/screens/notebook/smart_error_notebook_screen.dart @@ -1,6 +1,5 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import '../../../core/theme/app_colors.dart'; import '../../../data/models/error_notebook_model.dart'; import '../../../data/repositories/error_notebook_repository.dart'; 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 b326929..afd2fa7 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 @@ -51,6 +51,7 @@ class _SocraticVideoPlayerScreenState extends State w late AnimationController _watermarkController; VideoPlayerController? _videoController; bool _isVideoInitialized = false; + String? _videoInitError; int _lastSyncedPosition = -1; Timer? _controlsHideTimer; bool _showVideoControls = true; @@ -162,6 +163,7 @@ class _SocraticVideoPlayerScreenState extends State w if (mounted) { setState(() { _isVideoInitialized = true; + _videoInitError = null; }); _revealVideoControls(); if (state.currentPositionSeconds > 3) { @@ -178,6 +180,13 @@ class _SocraticVideoPlayerScreenState extends State w _videoController!.play(); } } + }).catchError((err) { + if (mounted) { + setState(() { + _isVideoInitialized = false; + _videoInitError = 'الفيديو قيد المزامنة والتجهيز على خادم البث المباشر'; + }); + } }); } else if (_isVideoInitialized) { // Sync play/pause state @@ -311,7 +320,31 @@ class _SocraticVideoPlayerScreenState extends State w ), ), // Center Lesson Title & Visuals - if (_showVideoControls) Center( + if (_videoInitError != null) + Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(CupertinoIcons.videocam_fill, color: AppColors.guardianAmber, size: 44), + const SizedBox(height: 12), + Text( + _videoInitError!, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700), + ), + const SizedBox(height: 8), + const Text( + 'يمكنك دراسة نتاجات وملخص الدرس عبر تبويب الكتب والمذكرات', + textAlign: TextAlign.center, + style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 12), + ), + ], + ), + ), + ) + else if (_showVideoControls) Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/apps/student_app/pubspec.lock b/apps/student_app/pubspec.lock index 327e2a6..08760ee 100644 --- a/apps/student_app/pubspec.lock +++ b/apps/student_app/pubspec.lock @@ -340,10 +340,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -356,10 +356,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" nested: dependency: transitive description: @@ -585,10 +585,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.11" typed_data: dependency: transitive description: diff --git a/apps/super_admin_app/lib/core/theme/super_admin_theme.dart b/apps/super_admin_app/lib/core/theme/super_admin_theme.dart index 1bce8cb..b2b227b 100644 --- a/apps/super_admin_app/lib/core/theme/super_admin_theme.dart +++ b/apps/super_admin_app/lib/core/theme/super_admin_theme.dart @@ -15,6 +15,10 @@ class SuperAdminTheme { static const Color surface = Color(0xFF0E1422); static const Color surfaceCard = Color(0xFF141C2E); static const Color border = Color(0xFF1E293B); + + static const Color backgroundDark = Color(0xFF06090F); + static const Color surfaceDark = Color(0xFF0E1422); + static const Color crimsonRed = Color(0xFFEF4444); static const Color royalGold = Color(0xFFFFD166); static const Color imperialPurple = Color(0xFF8B5CF6); diff --git a/apps/super_admin_app/lib/presentation/screens/super_admin_shell.dart b/apps/super_admin_app/lib/presentation/screens/super_admin_shell.dart index ee7c1d2..9c4c77c 100644 --- a/apps/super_admin_app/lib/presentation/screens/super_admin_shell.dart +++ b/apps/super_admin_app/lib/presentation/screens/super_admin_shell.dart @@ -187,8 +187,8 @@ class _SuperAdminShellState extends State { label: 'الرصد الكلي', ), BottomNavigationBarItem( - icon: Icon(CupertinoIcons.cpu), - activeIcon: Icon(CupertinoIcons.cpu, color: SuperAdminTheme.royalGold), + icon: Icon(CupertinoIcons.sparkles), + activeIcon: Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.royalGold), label: 'الذكاء المحلي', ), BottomNavigationBarItem( diff --git a/apps/super_admin_app/lib/presentation/screens/tabs/ai_cluster_tab.dart b/apps/super_admin_app/lib/presentation/screens/tabs/ai_cluster_tab.dart index b6000f4..4e30213 100644 --- a/apps/super_admin_app/lib/presentation/screens/tabs/ai_cluster_tab.dart +++ b/apps/super_admin_app/lib/presentation/screens/tabs/ai_cluster_tab.dart @@ -53,7 +53,7 @@ class AiClusterTab extends StatelessWidget { children: [ const Row( children: [ - Icon(CupertinoIcons.cpu, color: SuperAdminTheme.cyberCyan, size: 22), + Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.cyberCyan, size: 22), SizedBox(width: 8), Text( 'عنقود الذكاء الاصطناعي السيادي (Local Inference)', diff --git a/apps/super_admin_app/lib/presentation/screens/tabs/treasury_cliq_tab.dart b/apps/super_admin_app/lib/presentation/screens/tabs/treasury_cliq_tab.dart index 7fbd3b6..6cc6b42 100644 --- a/apps/super_admin_app/lib/presentation/screens/tabs/treasury_cliq_tab.dart +++ b/apps/super_admin_app/lib/presentation/screens/tabs/treasury_cliq_tab.dart @@ -31,7 +31,6 @@ class TreasuryCliqTab extends StatelessWidget { if (state is TreasuryLoaded) { final pendingItems = state.queue.where((item) => item.status == 'queued').toList(); - final completedItems = state.queue.where((item) => item.status == 'completed').toList(); final double pendingSum = pendingItems.fold(0.0, (acc, item) => acc + item.amountJod); return SingleChildScrollView( diff --git a/apps/super_admin_app/pubspec.lock b/apps/super_admin_app/pubspec.lock index b885690..5020938 100644 --- a/apps/super_admin_app/pubspec.lock +++ b/apps/super_admin_app/pubspec.lock @@ -316,10 +316,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -332,10 +332,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" nested: dependency: transitive description: @@ -561,10 +561,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.11" typed_data: dependency: transitive description: diff --git a/apps/teacher_app/lib/main.dart b/apps/teacher_app/lib/main.dart index e16ee43..edba9ca 100644 --- a/apps/teacher_app/lib/main.dart +++ b/apps/teacher_app/lib/main.dart @@ -28,7 +28,8 @@ void main() { class SaqelTeacherApp extends StatelessWidget { final TeacherRepository repository; - const SaqelTeacherApp({super.key, required this.repository}); + SaqelTeacherApp({super.key, TeacherRepository? repository}) + : repository = repository ?? TeacherRepository(); @override Widget build(BuildContext context) { diff --git a/apps/teacher_app/pubspec.lock b/apps/teacher_app/pubspec.lock index cf9fe61..0d26e35 100644 --- a/apps/teacher_app/pubspec.lock +++ b/apps/teacher_app/pubspec.lock @@ -316,10 +316,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -332,10 +332,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" nested: dependency: transitive description: @@ -561,10 +561,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.11" typed_data: dependency: transitive description: diff --git a/apps/teacher_app/test/widget_test.dart b/apps/teacher_app/test/widget_test.dart index ed836d0..622eecf 100644 --- a/apps/teacher_app/test/widget_test.dart +++ b/apps/teacher_app/test/widget_test.dart @@ -3,7 +3,7 @@ import 'package:teacher_app/main.dart'; void main() { testWidgets('Teacher App Smoke Test', (WidgetTester tester) async { - await tester.pumpWidget(const SaqelTeacherApp()); + await tester.pumpWidget(SaqelTeacherApp()); expect(find.text('استوديو المعلم المعتمد — صَقِل'), findsOneWidget); }); }