Update Saqel Platform: 2026-09-05 00:11:09

This commit is contained in:
Hamza-Ayed
2026-09-05 00:11:09 +03:00
parent e7bc20bc98
commit b0c2bfc03b
8 changed files with 2312 additions and 68 deletions
@@ -1,15 +1,13 @@
/**
* ==============================================================================
* SAQEL EDTECH 2.0 - BAKED CURRICULUM DATA SOURCE
* ==============================================================================
*
* مستودع المحتوى الوزاري المخبوز والمدمج محلياً للمواد الثلاث الأساسية للصف العاشر:
* 1. الرياضيات (Mathematics 10) — منهاج وزارة التربية والتعليم الأردنية
* 2. الفيزياء (Physics 10) — المتجهات والحركة والتجارب المخبرية
* 3. اللغة الإنجليزية (English 10 — Action Pack) — المفردات والقراءة والقواعد
*
* يضمن تشغيل وتصفح الكتب وأوراق العمل والدروس بنسبة 100% حتى في حال غياب الاتصال.
*/
/// ==============================================================================
/// SAQEL EDTECH 2.0 - BAKED CURRICULUM DATA SOURCE
/// ==============================================================================
///
/// مستودع المحتوى الوزاري المخبوز والمدمج محلياً للمواد الثلاث الأساسية للصف العاشر:
/// 1. الرياضيات (Mathematics 10) — منهاج وزارة التربية والتعليم الأردنية
/// 2. الفيزياء (Physics 10) — المتجهات والحركة والتجارب المخبرية
/// 3. اللغة الإنجليزية (English 10 — Action Pack) — المفردات والقراءة والقواعد
///
/// يضمن تشغيل وتصفح الكتب وأوراق العمل والدروس بنسبة 100% حتى في حال غياب الاتصال.
class CurriculumBakedData {
/// Get baked curriculum document by subject, type, and file path
static String getDocument({
@@ -27,6 +25,12 @@ class CurriculumBakedData {
if (type == 'worksheet' || t.contains('ورقة عمل') || f.contains('worksheet')) {
return _math10Worksheet1;
}
if (f.contains('geogebra') || t.contains('جيوجبرا')) {
return _math10GeoGebraLab;
}
if (f.contains('intro') || t.contains('مقدمة') || t.contains('مشروع')) {
return _math10Unit1Intro;
}
if (f.contains('lesson_02') || t.contains('نظام') || t.contains('خطي')) {
return _math10Lesson2;
}
@@ -122,6 +126,53 @@ class CurriculumBakedData {
3. فك الأقواس والتبسيط وترتيب المعادلة على الصورة العامة \$ax^2 + bx + c = 0\$.
4. الحل بالتحليل إلى العوامل أو القانون العام.
5. إيجاد قيمة المتغير الآخر وكتابة مجموعة الحل على شكل أزواج مرتبة \$(x, y)\$.
""";
static const String _math10GeoGebraLab = """
## معملُ برمجيةِ جيوجبرا: حلُّ أنظمةِ المعادلاتِ بيانياً
*(الصفحات 16 - 17 — منهاج الرياضيات المعتمد)*
### النشاط:
\$\$\\begin{cases} x^2 + y^2 = 13 \\\\ x^2 - y = 7 \\end{cases}\$\$
نقاط التقاطع الأربع:
\$\$(3, 2), \\quad (-3, 2), \\quad (2, -3), \\quad (-2, -3)\$\$
### أتدرب (صفحة 17):
1) \$\\Delta = -144 < 0 \\implies \\varnothing\$
2) \$\\{(2, 4), (-2, 4)\\}\$
3) \$\\{(8.625, 7.375)\\}\$
4) \$\\Delta = -295 < 0 \\implies \\varnothing\$
5) \$\\left\\{\\left(\\frac{3}{\\sqrt{37}}, \\frac{18}{\\sqrt{37}}\\right), \\left(-\\frac{3}{\\sqrt{37}}, -\\frac{18}{\\sqrt{37}}\\right)\\right\\}\$
6) \$\\Delta = -59 < 0 \\implies \\varnothing\$
---
""";
static const String _math10Unit1Intro = """
## مقدمة ومشروع الوحدة: أنظمة المعادلات في حياتنا
*(الوحدة الأولى — الصف العاشر الأساسي)*
### ما أهمية هذه الوحدة؟
تُستخدَم أنظمةُ المعادلاتِ في كثيرٍ من مجالاتِ الحياةِ. فخبراءُ الأرصادِ الجويّةِ -مثلاً- يُعبِّرون عنِ العلاقةِ بينَ درجةِ الحرارةِ، وسرعةِ الرّياحِ، والضغطِ الجويِّ، ومعدلِ الهطلِ، باستخدامِ نظامِ معادلاتٍ غيرِ خطيٍّ؛ ذلكَ أنَّ أيَّ تغيُّرٍ في أحدِ هذهِ العواملِ يؤدّي إلى تغيُّرٍ في العواملِ الأخرى.
### تعلَّمْتُ سابقاً:
- حلَّ معادلاتٍ تربيعيّةٍ باستعمالِ التحليلِ.
- حلَّ معادلاتٍ تربيعيّةٍ باستعمالِ القانونِ العامِّ.
- حلَّ أنظمةِ معادلاتٍ تتضمَّنُ معادلتيْنِ خطيّتيْنِ بمتغيِّريْنِ.
### سأتعلَّمُ في هذهِ الوحدةِ:
- حلَّ معادلاتٍ خاصّةٍ أُسُّ المتغيّرِ فيها عددٌ صحيحٌ موجبٌ أكبرُ من \$2\$.
- حلَّ نظامٍ مُكوَّنٍ من معادلةٍ خطيّةٍ، وأُخرى تربيعيّةٍ.
- حلَّ نظامٍ مُكوَّنٍ من معادلتيْنِ تربيعيّتيْنِ.
### مشروع الوحدة: أنظمةُ المعادلاتِ في حياتِنا (صفحة 7)
- **فكرة المشروع:** البحثُ عن أنظمةِ معادلاتٍ في نماذجَ حياتيّةٍ.
- **المواد والأدوات:** شبكةُ الإنترنت، برمجيّةُ جيوجبرا (GeoGebra).
- **الصيغة المستخدمة في جيوجبرا:**
\$\$\\text{FitPoly}(\\{C, D, E, F, G, H, I, J, K, L\\}, n)\$\$
---
""";
static const String _math10Worksheet1 = """
@@ -8,21 +8,22 @@ import '../../../data/datasources/curriculum_baked_data.dart';
import '../../widgets/luxury_widgets.dart';
import '../../widgets/english_tts_player_widget.dart';
import 'physics_interactive_lab_view.dart';
import 'math_interactive_lab_view.dart';
import 'english_interactive_lab_view.dart';
/**
* ==============================================================================
* SAQEL ENTERPRISE (EDTECH 2.0) - CURRICULUM DOCUMENT & TEXTBOOK VIEWER
* ==============================================================================
*
* ملف: curriculum_document_viewer_screen.dart
* الهدف المعماري:
* استعراض الكتب المدرسية المقررة والمذكرات الوزارية وأوراق العمل محلياً وسحابياً:
* 1. دعم التخزين المحلي (Offline Storage): قراءة وتصفح دائم دون اتصال بعد الحفظ.
* 2. محتوى وزاري مخبوز وأصيل (CurriculumBakedData) للمواد الثلاث (رياضيات 10، فيزياء 10، إنجليزي 10).
* 3. دعم الناطق الصوتي الذكي (English TTS) المدمج لقراءة النصوص والمفردات.
* 4. ربط فوري بالمختبر التفاعلي (Virtual Lab) لمفاهيم المتجهات والفيزياء.
* 5. التحكم بحجم الخط وزر الحفظ في الجهاز.
*/
/// ==============================================================================
/// SAQEL ENTERPRISE (EDTECH 2.0) - CURRICULUM DOCUMENT & TEXTBOOK VIEWER
/// ==============================================================================
///
/// ملف: curriculum_document_viewer_screen.dart
/// الهدف المعماري:
/// استعراض الكتب المدرسية المقررة والمذكرات الوزارية وأوراق العمل محلياً وسحابياً:
/// 1. دعم التخزين المحلي (Offline Storage): قراءة وتصفح دائم دون اتصال بعد الحفظ.
/// 2. محتوى وزاري مخبوز وأصيل (CurriculumBakedData) للمواد الثلاث (رياضيات 10، فيزياء 10، إنجليزي 10).
/// 3. دعم الناطق الصوتي الذكي (English TTS) المدمج لقراءة النصوص والمفردات.
/// 4. ربط فوري بالمختبر التفاعلي (Virtual Lab) لمفاهيم المتجهات والفيزياء.
/// 5. التحكم بحجم الخط وزر الحفظ في الجهاز.
///
class CurriculumDocumentViewerScreen extends StatefulWidget {
final String title;
final String documentType; // 'worksheet', 'summary', 'textbook', 'lesson'
@@ -63,6 +64,13 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
widget.subjectTitle.contains('فيزياء') ||
(widget.subjectId ?? '').toLowerCase().contains('physic');
bool get _isMath =>
widget.subjectTitle.contains('رياضيات') ||
(widget.subjectId ?? '').toLowerCase().contains('math') ||
widget.title.contains('معادلات') ||
widget.title.contains('جيوجبرا') ||
(widget.filePath ?? '').toLowerCase().contains('geogebra');
String get _effectiveSubjectId => widget.subjectId ?? (_isEnglish ? 'english_10' : (_isPhysics ? 'physics_10' : 'math_10'));
String get _effectiveFilePath => widget.filePath ?? widget.title;
@@ -266,6 +274,130 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
const SizedBox(height: 16),
],
// Direct Math GeoGebra Lab Cross-Link
if (_isMath) ...[
GestureDetector(
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (_) => Scaffold(
backgroundColor: AppColors.darkBackground,
appBar: AppBar(
backgroundColor: AppColors.darkSurface,
title: const Text('معمل برمجية جيوجبرا 📐', style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold)),
centerTitle: true,
),
body: const MathInteractiveLabView(),
),
),
);
},
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF0F2027), Color(0xFF203A43), Color(0xFF2C5364)],
),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.saqelCyan.withAlpha(80)),
boxShadow: [
BoxShadow(
color: AppColors.saqelCyan.withAlpha(40),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: const Row(
children: [
Icon(CupertinoIcons.function, color: AppColors.saqelCyan, size: 26),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'معمل برمجية جيوجبرا التفاعلي (GeoGebra Lab) 📐',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 14),
),
SizedBox(height: 2),
Text(
'حل أنظمة المعادلات بيانياً وكشف نقاط التقاطع وحساب المميز (تطبيق نشاط ص 16-17)',
style: TextStyle(color: Colors.white70, fontSize: 11.5),
),
],
),
),
Icon(CupertinoIcons.arrow_left, color: AppColors.saqelCyan, size: 16),
],
),
),
),
const SizedBox(height: 16),
],
// Direct English Interactive Lab Cross-Link
if (_isEnglish) ...[
GestureDetector(
onTap: () {
Navigator.of(context).push(
CupertinoPageRoute(
builder: (_) => Scaffold(
backgroundColor: AppColors.darkBackground,
appBar: AppBar(
backgroundColor: AppColors.darkSurface,
title: const Text('مختبر اللغة الإنجليزية (Action Pack 10) 🎧', style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.bold)),
centerTitle: true,
),
body: const EnglishInteractiveLabView(),
),
),
);
},
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF141E30), Color(0xFF243B55)],
),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.appleBlue.withAlpha(90)),
boxShadow: [
BoxShadow(
color: AppColors.appleBlue.withAlpha(40),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: const Row(
children: [
Icon(CupertinoIcons.headphones, color: AppColors.appleBlue, size: 26),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'مختبر الصوتيات والقواعد التفاعلي (English Lab) 🎧',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 14),
),
SizedBox(height: 2),
Text(
'استمع للنطق الصوتي النقي (IPA) وتدرب على محاكي القواعد والأزمنة وبنك المفردات الذكي',
style: TextStyle(color: Colors.white70, fontSize: 11.5),
),
],
),
),
Icon(CupertinoIcons.arrow_left, color: AppColors.appleBlue, size: 16),
],
),
),
),
const SizedBox(height: 16),
],
// Direct Physics Virtual Lab Cross-Link
if (_isPhysics) ...[
GestureDetector(
@@ -0,0 +1,974 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../../../core/theme/app_colors.dart';
import '../../widgets/luxury_widgets.dart';
/// ==============================================================================
/// SAQEL ENTERPRISE - ACTION PACK 10 ENGLISH INTERACTIVE LAB
/// ==============================================================================
///
/// مختبر اللغة الإنجليزية التفاعلي (منهاج Action Pack 10 الوزاري المعتمد):
/// 1. محاكي القواعد التفاعلي (Grammar & Tenses Engine: Present Perfect, Modals, Relative Clauses).
/// 2. استوديو الصوتيات والنطق الحي بالذكاء الاصطناعي مع رموز IPA ومحرك flutter_tts.
/// 3. بنك المفردات والمتلازمات اللفظية (Collocations & Vocabulary Bank) للوحدات 1 إلى 5.
class EnglishInteractiveLabView extends StatefulWidget {
final String? initialTopic;
const EnglishInteractiveLabView({
super.key,
this.initialTopic,
});
@override
State<EnglishInteractiveLabView> createState() =>
_EnglishInteractiveLabViewState();
}
class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final FlutterTts _tts = FlutterTts();
bool _isPlayingAudio = false;
// Grammar Simulator State
int _selectedGrammarTopicIndex = 0; // 0 = Present Perfect vs Past Simple, 1 = Modals, 2 = Relative Clauses
// Phonetics & Vocabulary State
int _selectedVocabUnitIndex = 0;
int _selectedWordIndex = 0;
final List<Map<String, dynamic>> _grammarTopics = [
{
'title': 'Present Perfect vs. Past Simple (الوحدة 1 و 2)',
'badge': 'Tenses & Timeline',
'concept':
'Present Perfect connects a past action to the present, or expresses life experiences without a specified time. Past Simple describes finished actions at a definite past time.',
'formula_present_perfect': 'Subject + have / has + Past Participle (V3)',
'formula_past_simple': 'Subject + Past Form (V2)',
'time_markers_present': ['already', 'yet', 'just', 'ever', 'never', 'since 2018', 'for 5 years'],
'time_markers_past': ['yesterday', 'in 2021', 'last week', 'two days ago', 'when I was young'],
'pairs': [
{
'present': 'Scientists have developed autonomous exploration drones.',
'present_ar': 'طور العلماء طائرات استكشافية مسيّرة (والنتيجة قائمة ومستمرة الآن).',
'past': 'Voyager 1 was launched in 1977.',
'past_ar': 'أُطلق المسبار فوياجر 1 عام 1977 (حدث منتهٍ في زمن ماضٍ محدد).',
},
{
'present': 'He has lived in Amman for ten years.',
'present_ar': 'عاش في عمّان لمدة 10 سنوات (وما زال يسكن فيها حتى اليوم).',
'past': 'He lived in Irbid for two years when he was a child.',
'past_ar': 'عاش في إربد لسنتين عندما كان طفلاً (وانتهى ذلك الزمن).',
},
],
},
{
'title': 'Modals of Deduction & Obligation (الوحدة 4)',
'badge': 'Modals of Deduction',
'concept':
'We use "must" when we are almost 100% sure something is true. We use "can\'t" when we are sure it is impossible. We use "might / could" for possibility.',
'formula_present_perfect': 'must / might / can\'t + Base Verb (Infinitive)',
'formula_past_simple': 'must / might / can\'t + have + V3 (Past Deduction)',
'time_markers_present': ['must (sure it is true)', 'can\'t (sure it is impossible)', 'might / may (possible)'],
'time_markers_past': ['have to / must (obligation)', 'don\'t have to (lack of obligation)', 'mustn\'t (prohibition)'],
'pairs': [
{
'present': 'She has been traveling all night; she must be exhausted.',
'present_ar': 'سافرت طوال الليل؛ لا بد أنها مرهقة للغاية (استنتاج مؤكد 100%).',
'past': 'He can\'t be the pilot; he is too young.',
'past_ar': 'مستحيل أن يكون هو الطيار؛ فهو صغير جداً في السن (استنتاج مستحيل).',
},
],
},
{
'title': 'Defining & Non-Defining Relative Clauses (الوحدة 4 و 5)',
'badge': 'Relative Pronouns',
'concept':
'Relative clauses give extra information about a noun. Use "who" for people, "which/that" for things, "where" for places, and "whose" for possession.',
'formula_present_perfect': 'Noun + [who / which / where / whose] + Clause',
'formula_past_simple': 'Non-defining clauses require commas: , which ... ,',
'time_markers_present': ['who (people)', 'which / that (objects & ideas)', 'where (places)'],
'time_markers_past': ['whose (possession)', 'when (time)', 'why (reasons)'],
'pairs': [
{
'present': 'Petra is a historical city which attracts thousands of tourists every week.',
'present_ar': 'البتراء مدينة تاريخية تجذب آلاف السياح أسبوعياً.',
'past': 'An architect is a professional who designs eco-friendly buildings.',
'past_ar': 'المهندس المعماري هو المحترف الذي يصمم المباني الصديقة للبيئة.',
},
],
},
];
final List<Map<String, dynamic>> _vocabUnits = [
{
'unit': 'Unit 1: Looking Good',
'words': [
{
'word': 'Appearance',
'ipa': '/əˈpɪə.rəns/',
'pos': 'noun',
'ar': 'المظهر الخارجي / الهيئة',
'def': 'The way that someone or something looks.',
'sentence': 'First impressions are often influenced by personal appearance and body language.',
'collocations': 'Physical appearance, personal appearance, deceptive appearance',
},
{
'word': 'Accidental',
'ipa': '/ˌæk.sɪˈden.təl/',
'pos': 'adjective',
'ar': 'عَرَضي / غير مقصود / بالصدفة',
'def': 'Happening by chance, not planned or intended.',
'sentence': 'The discovery of the ancient mosaic in Madaba was completely accidental.',
'collocations': 'Accidental discovery, accidental damage',
},
{
'word': 'Confident',
'ipa': '/ˈkɒn.fɪ.dənt/',
'pos': 'adjective',
'ar': 'واثق من نفسه',
'def': 'Feeling sure about your abilities or qualities.',
'sentence': 'Wearing formal attire can make candidates feel more confident in job interviews.',
'collocations': 'Self-confident, feel confident, highly confident',
},
{
'word': 'Casual',
'ipa': '/ˈkæʒ.u.əl/',
'pos': 'adjective',
'ar': 'عفوي / غير رسمي (ملابس مريحة)',
'def': 'Relaxed and unconcerned; not formal.',
'sentence': 'On casual days, university students prefer lightweight cotton clothes.',
'collocations': 'Casual clothes, casual wear, casual observer',
},
],
},
{
'unit': 'Unit 2: The Digital Mind',
'words': [
{
'word': 'Artificial Intelligence',
'ipa': '/ˌɑː.tɪˈfɪʃ.əl ɪnˈtel.ɪ.dʒəns/',
'pos': 'noun phrase',
'ar': 'الذكاء الاصطناعي',
'def': 'The development of computer systems able to perform tasks that normally require human intelligence.',
'sentence': 'Saqel platform uses artificial intelligence to adapt learning roadmaps for each student.',
'collocations': 'Generative artificial intelligence, AI algorithms, AI ethics',
},
{
'word': 'Algorithm',
'ipa': '/ˈæl.ɡə.rɪ.ðəm/',
'pos': 'noun',
'ar': 'خوارزمية حسابية',
'def': 'A set of mathematical instructions used by computers to solve problems or make calculations.',
'sentence': 'Search engine algorithms rank web pages based on relevance and authority.',
'collocations': 'Complex algorithm, optimization algorithm, search algorithm',
},
{
'word': 'Voyager',
'ipa': '/ˈvɔɪ.ɪ.dʒər/',
'pos': 'noun',
'ar': 'مستكشف الفضاء / مسافر في رحلة طويلة',
'def': 'A person who goes on a long journey, or the legendary NASA interstellar spacecraft.',
'sentence': 'Voyager 1 carries a golden record containing sounds and images of planet Earth.',
'collocations': 'Space voyager, daring voyager',
},
{
'word': 'Breakthrough',
'ipa': '/ˈbreɪk.θruː/',
'pos': 'noun',
'ar': 'إنجاز تاريخي غير مسبوق / قفزة نوعية',
'def': 'An important discovery or event that helps improve a situation or provide an answer.',
'sentence': 'Quantum computing represents a major technological breakthrough in modern science.',
'collocations': 'Major breakthrough, scientific breakthrough, medical breakthrough',
},
],
},
{
'unit': 'Unit 3: Active and Healthy',
'words': [
{
'word': 'Endurance',
'ipa': '/ɪnˈdʒʊə.rəns/',
'pos': 'noun',
'ar': 'قوة التحمل والصبر البدني',
'def': 'The ability to keep doing something difficult, unpleasant, or painful for a long time.',
'sentence': 'Long-distance marathon runners need exceptional physical endurance and cardiovascular stamina.',
'collocations': 'Physical endurance, endurance training, test of endurance',
},
{
'word': 'Mediterranean Diet',
'ipa': '/ˌmed.ɪ.tərˈeɪ.ni.ən ˈdaɪ.ət/',
'pos': 'noun phrase',
'ar': 'حمية حوض البحر الأبيض المتوسط',
'def': 'A diet typical of Mediterranean countries, rich in olive oil, fish, grains, and vegetables.',
'sentence': 'Nutritionists recommend the Mediterranean diet for heart health and longevity.',
'collocations': 'Adopt a Mediterranean diet, heart-healthy diet',
},
{
'word': 'Stamina',
'ipa': '/ˈstæm.ɪ.nə/',
'pos': 'noun',
'ar': 'اللياقة العالية والجلد البدني',
'def': 'The physical or mental energy that allows you to do something for a long time.',
'sentence': 'Regular aerobic exercises build stamina and reduce fatigue.',
'collocations': 'Build stamina, athletic stamina, mental stamina',
},
],
},
{
'unit': 'Unit 4: Time to Move',
'words': [
{
'word': 'Destination',
'ipa': '/ˌdes.tɪˈneɪ.ʃən/',
'pos': 'noun',
'ar': 'الوجهة السياحية / المقصد',
'def': 'The place where someone is going or where something is being sent or taken.',
'sentence': 'Wadi Rum is one of the most famous tourist destinations in Jordan.',
'collocations': 'Holiday destination, popular destination, final destination',
},
{
'word': 'Locomotive',
'ipa': '/ˌləʊ.kəˈməʊ.tɪv/',
'pos': 'noun',
'ar': 'قاطرة قطار سريعة',
'def': 'The engine of a train that pulls it along railway tracks.',
'sentence': 'High-speed electric locomotives produce fewer carbon emissions than traditional diesel engines.',
'collocations': 'Steam locomotive, electric locomotive, modern locomotive',
},
{
'word': 'Emission',
'ipa': '/iˈmɪʃ.ən/',
'pos': 'noun',
'ar': 'انبعاثات غازية وكربونية',
'def': 'An amount of gas, heat, or light that is sent out.',
'sentence': 'Public transport networks help reduce harmful greenhouse gas emissions in crowded cities.',
'collocations': 'Zero emissions, carbon emissions, harmful emissions',
},
],
},
{
'unit': 'Unit 5: The Next Step',
'words': [
{
'word': 'Apprenticeship',
'ipa': '/əˈpren.tɪs.ʃɪp/',
'pos': 'noun',
'ar': 'برنامج التدريب المهني والعملي',
'def': 'A period of time working for an employer in return for instruction in a trade or craft.',
'sentence': 'Vocational students gain hands-on engineering skills through a three-year apprenticeship.',
'collocations': 'Serve an apprenticeship, vocational apprenticeship, completed apprenticeship',
},
{
'word': 'Ambitious',
'ipa': '/æmˈbɪʃ.əs/',
'pos': 'adjective',
'ar': 'طموح / متطلع للإنجاز العالي',
'def': 'Having a strong wish to be successful, powerful, or rich.',
'sentence': 'Tawjihi students have ambitious goals to specialize in modern tech disciplines.',
'collocations': 'Ambitious young professional, highly ambitious, ambitious project',
},
{
'word': 'Internship',
'ipa': '/ˈɪn.tɜːn.ʃɪp/',
'pos': 'noun',
'ar': 'فترة تدريب وظيفي ميداني',
'def': 'A period of time during which a student or new graduate works to get experience in a profession.',
'sentence': 'She completed a summer internship at a leading financial firm in Amman.',
'collocations': 'Paid internship, summer internship, clinical internship',
},
],
},
];
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_initTts();
}
Future<void> _initTts() async {
try {
await _tts.setLanguage('en-GB');
await _tts.setSpeechRate(0.45); // Clear, articulate educational pace
await _tts.setPitch(1.0);
_tts.setCompletionHandler(() {
if (mounted) setState(() => _isPlayingAudio = false);
});
_tts.setErrorHandler((_) {
if (mounted) setState(() => _isPlayingAudio = false);
});
} catch (_) {}
}
Future<void> _speakWord(String text) async {
if (text.isEmpty) return;
setState(() {
_isPlayingAudio = true;
});
try {
await _tts.stop();
await _tts.speak(text);
} catch (_) {
if (mounted) setState(() => _isPlayingAudio = false);
}
}
@override
void dispose() {
_tts.stop();
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Column(
children: [
// Sub-navigation bar
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: const BoxDecoration(
color: AppColors.darkSurface,
border: Border(bottom: BorderSide(color: AppColors.darkCardBorder)),
),
child: Row(
children: [
const Icon(CupertinoIcons.headphones, color: AppColors.saqelCyan, size: 20),
const SizedBox(width: 8),
const Text(
'مختبر اللغة الإنجليزية (Action Pack 10) — الصوتيات والقواعد والمفردات',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.appleBlue.withAlpha(30),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.appleBlue.withAlpha(90)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(CupertinoIcons.speaker_2_fill, color: AppColors.appleBlue, size: 12),
SizedBox(width: 4),
Text(
'صوتيات ذكية (IPA & TTS)',
style: TextStyle(color: AppColors.appleBlue, fontSize: 11, fontWeight: FontWeight.w700),
),
],
),
),
],
),
),
// Main Tab selector
Container(
color: AppColors.darkSurface,
child: TabBar(
controller: _tabController,
indicatorColor: AppColors.saqelCyan,
indicatorWeight: 3,
labelColor: Colors.white,
unselectedLabelColor: AppColors.textSecondaryDark,
labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
tabs: const [
Tab(icon: Icon(CupertinoIcons.wand_rays_inverse, size: 16), text: 'محاكي القواعد والأزمنة'),
Tab(icon: Icon(CupertinoIcons.waveform, size: 16), text: 'استوديو الصوتيات والنطق'),
Tab(icon: Icon(CupertinoIcons.rectangle_stack_fill, size: 16), text: 'بنك المفردات التفاعلي'),
],
),
),
// Tab Content
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildGrammarSimulatorView(),
_buildPhoneticsStudioView(),
_buildVocabularyBankView(),
],
),
),
],
),
);
}
// ============================================================================
// TAB 1: GRAMMAR & TENSES SIMULATOR
// ============================================================================
Widget _buildGrammarSimulatorView() {
final activeTopic = _grammarTopics[_selectedGrammarTopicIndex];
return Row(
children: [
// Left: Topics Menu
Container(
width: 260,
decoration: const BoxDecoration(
color: AppColors.darkSurface,
border: Border(left: BorderSide(color: AppColors.darkCardBorder)),
),
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
itemCount: _grammarTopics.length,
itemBuilder: (context, index) {
final topic = _grammarTopics[index];
final isSelected = _selectedGrammarTopicIndex == index;
return Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: isSelected ? AppColors.saqelCyan.withAlpha(20) : Colors.transparent,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected ? AppColors.saqelCyan : Colors.transparent,
),
),
child: ListTile(
dense: true,
title: Text(
topic['title'] as String,
style: TextStyle(
color: isSelected ? AppColors.saqelCyan : Colors.white,
fontSize: 12,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
),
),
subtitle: Text(
topic['badge'] as String,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 10),
),
onTap: () => setState(() => _selectedGrammarTopicIndex = index),
),
);
},
),
),
// Right: Interactive Grammar Canvas
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
LuxuryCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(8),
),
child: Text(
activeTopic['badge'] as String,
style: const TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontWeight: FontWeight.bold),
),
),
const Spacer(),
IconButton(
icon: const Icon(CupertinoIcons.speaker_2, color: AppColors.saqelCyan, size: 20),
onPressed: () => _speakWord(activeTopic['concept'] as String),
tooltip: 'استمع للشرح باللغة الإنجليزية',
),
],
),
const SizedBox(height: 12),
Text(
activeTopic['concept'] as String,
style: const TextStyle(color: Colors.white, fontSize: 13, height: 1.6),
),
const Divider(color: AppColors.darkCardBorder, height: 28),
// Grammar Formulas
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black45,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.saqelCyan.withAlpha(50)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('صيغة الإثبات الأولى:', style: TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontWeight: FontWeight.bold)),
const SizedBox(height: 6),
Text(
activeTopic['formula_present_perfect'] as String,
style: const TextStyle(color: Colors.white, fontFamily: 'Courier', fontSize: 12, fontWeight: FontWeight.w700),
),
],
),
),
),
const SizedBox(width: 12),
Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black45,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.guardianAmber.withAlpha(50)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('صيغة المقارنة المقابلة:', style: TextStyle(color: AppColors.guardianAmber, fontSize: 11, fontWeight: FontWeight.bold)),
const SizedBox(height: 6),
Text(
activeTopic['formula_past_simple'] as String,
style: const TextStyle(color: Colors.white, fontFamily: 'Courier', fontSize: 12, fontWeight: FontWeight.w700),
),
],
),
),
),
],
),
],
),
),
const SizedBox(height: 18),
const Text(
'أمثلة تفاعلية ومقارنة دلالية (Action Pack 10 Context):',
style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700),
),
const SizedBox(height: 10),
// Comparison Cards
...((activeTopic['pairs'] as List).map((pair) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.darkCardBorder),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(20),
borderRadius: BorderRadius.circular(6),
),
child: const Text('المثال الأول', style: TextStyle(color: AppColors.saqelCyan, fontSize: 10, fontWeight: FontWeight.bold)),
),
const Spacer(),
IconButton(
icon: const Icon(CupertinoIcons.volume_up, color: AppColors.saqelCyan, size: 16),
onPressed: () => _speakWord(pair['present'] as String),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
const SizedBox(height: 6),
Text(
pair['present'] as String,
style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w700),
),
Text(
pair['present_ar'] as String,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
const Divider(color: AppColors.darkCardBorder, height: 18),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColors.guardianAmber.withAlpha(20),
borderRadius: BorderRadius.circular(6),
),
child: const Text('المثال المقابل', style: TextStyle(color: AppColors.guardianAmber, fontSize: 10, fontWeight: FontWeight.bold)),
),
const Spacer(),
IconButton(
icon: const Icon(CupertinoIcons.volume_up, color: AppColors.guardianAmber, size: 16),
onPressed: () => _speakWord(pair['past'] as String),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
const SizedBox(height: 6),
Text(
pair['past'] as String,
style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w700),
),
Text(
pair['past_ar'] as String,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
],
),
);
})),
],
),
),
),
],
);
}
// ============================================================================
// TAB 2: PHONETICS & PRONUNCIATION STUDIO (IPA & TTS)
// ============================================================================
Widget _buildPhoneticsStudioView() {
final activeUnit = _vocabUnits[_selectedVocabUnitIndex];
final words = activeUnit['words'] as List;
final activeWord = words[_selectedWordIndex] as Map<String, dynamic>;
return Row(
children: [
// Left: Units Selector & Words List
Container(
width: 280,
decoration: const BoxDecoration(
color: AppColors.darkSurface,
border: Border(left: BorderSide(color: AppColors.darkCardBorder)),
),
child: Column(
children: [
// Unit Dropdown
Container(
padding: const EdgeInsets.all(12),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: AppColors.darkCardBorder)),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _selectedVocabUnitIndex,
isExpanded: true,
dropdownColor: AppColors.darkSurface,
items: List.generate(_vocabUnits.length, (i) {
return DropdownMenuItem(
value: i,
child: Text(
_vocabUnits[i]['unit'] as String,
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
),
);
}),
onChanged: (val) {
if (val != null) {
setState(() {
_selectedVocabUnitIndex = val;
_selectedWordIndex = 0;
});
}
},
),
),
),
// Words List
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: words.length,
itemBuilder: (context, index) {
final item = words[index];
final isSelected = _selectedWordIndex == index;
return Container(
margin: const EdgeInsets.only(bottom: 6),
decoration: BoxDecoration(
color: isSelected ? AppColors.saqelCyan.withAlpha(25) : Colors.transparent,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSelected ? AppColors.saqelCyan : Colors.transparent,
),
),
child: ListTile(
dense: true,
title: Text(
item['word'] as String,
style: TextStyle(
color: isSelected ? AppColors.saqelCyan : Colors.white,
fontSize: 13,
fontWeight: isSelected ? FontWeight.bold : FontWeight.w600,
),
),
subtitle: Text(
item['ar'] as String,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
trailing: IconButton(
icon: const Icon(CupertinoIcons.volume_up, size: 16, color: AppColors.saqelCyan),
onPressed: () => _speakWord(item['word'] as String),
),
onTap: () => setState(() => _selectedWordIndex = index),
),
);
},
),
),
],
),
),
// Right: Phonetics Audio Workbench
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Big Pronunciation Header Card
Container(
padding: const EdgeInsets.all(28),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF0D1B2A), Color(0xFF1B263B)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.saqelCyan.withAlpha(80)),
boxShadow: const [
BoxShadow(color: Colors.black54, blurRadius: 20, offset: Offset(0, 10)),
],
),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(16),
),
child: Text(
activeWord['pos'] as String,
style: const TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 14),
Text(
activeWord['word'] as String,
style: const TextStyle(
color: Colors.white,
fontSize: 32,
fontWeight: FontWeight.w900,
letterSpacing: 0.5,
),
),
const SizedBox(height: 6),
Text(
activeWord['ipa'] as String,
style: const TextStyle(
color: AppColors.saqelCyan,
fontSize: 18,
fontFamily: 'Courier',
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 10),
Text(
activeWord['ar'] as String,
style: const TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
// Audio Play Button
ElevatedButton.icon(
icon: Icon(
_isPlayingAudio ? CupertinoIcons.waveform : CupertinoIcons.volume_up,
size: 20,
),
label: Text(
_isPlayingAudio ? 'جاري النطق...' : 'استمع للنطق الصوتي النقي (Audio Pronunciation)',
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.saqelCyan,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
onPressed: () => _speakWord(activeWord['word'] as String),
),
],
),
),
const SizedBox(height: 20),
// Detailed Definition & Collocations
LuxuryCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('التعريف المعتمد (Official Definition):', style: TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.bold, fontSize: 12)),
const SizedBox(height: 6),
Text(activeWord['def'] as String, style: const TextStyle(color: Colors.white, fontSize: 13, height: 1.5)),
const Divider(color: AppColors.darkCardBorder, height: 24),
const Text('جملة المنهاج الوزاري (Textbook Context):', style: TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.bold, fontSize: 12)),
const SizedBox(height: 6),
Row(
children: [
Expanded(
child: Text(activeWord['sentence'] as String, style: const TextStyle(color: Colors.white, fontSize: 13, height: 1.5, fontStyle: FontStyle.italic)),
),
IconButton(
icon: const Icon(CupertinoIcons.speaker_1, color: AppColors.guardianAmber, size: 18),
onPressed: () => _speakWord(activeWord['sentence'] as String),
),
],
),
const Divider(color: AppColors.darkCardBorder, height: 24),
const Text('المتلازمات اللفظية (Collocations):', style: TextStyle(color: Color(0xFF34C759), fontWeight: FontWeight.bold, fontSize: 12)),
const SizedBox(height: 6),
Text(activeWord['collocations'] as String, style: const TextStyle(color: Colors.white70, fontSize: 12)),
],
),
),
],
),
),
),
],
);
}
// ============================================================================
// TAB 3: VOCABULARY FLASHCARDS BANK
// ============================================================================
Widget _buildVocabularyBankView() {
final activeUnit = _vocabUnits[_selectedVocabUnitIndex];
final words = activeUnit['words'] as List;
return Column(
children: [
// Unit Filter Bar
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
color: AppColors.darkSurface,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(_vocabUnits.length, (i) {
final isSelected = _selectedVocabUnitIndex == i;
return Padding(
padding: const EdgeInsets.only(left: 8),
child: ChoiceChip(
label: Text(
_vocabUnits[i]['unit'] as String,
style: TextStyle(
color: isSelected ? Colors.black : Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
selected: isSelected,
selectedColor: AppColors.saqelCyan,
backgroundColor: AppColors.darkCard,
onSelected: (_) => setState(() => _selectedVocabUnitIndex = i),
),
);
}),
),
),
),
// Flashcards Grid
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(20),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 1.35,
),
itemCount: words.length,
itemBuilder: (context, index) {
final w = words[index];
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.darkCardBorder),
boxShadow: const [
BoxShadow(color: Colors.black26, blurRadius: 10, offset: Offset(0, 4)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
w['word'] as String,
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(width: 8),
Text(
w['ipa'] as String,
style: const TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontFamily: 'Courier'),
),
const Spacer(),
IconButton(
icon: const Icon(CupertinoIcons.volume_up, color: AppColors.saqelCyan, size: 18),
onPressed: () => _speakWord(w['word'] as String),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
),
],
),
const SizedBox(height: 6),
Text(
w['ar'] as String,
style: const TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.bold, fontSize: 13),
),
const SizedBox(height: 8),
Expanded(
child: Text(
w['def'] as String,
style: const TextStyle(color: Colors.white70, fontSize: 11, height: 1.4),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
const Divider(color: AppColors.darkCardBorder, height: 12),
Text(
'Collocations: ${w['collocations']}',
style: const TextStyle(color: Color(0xFF34C759), fontSize: 10, fontWeight: FontWeight.w600),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
},
),
),
],
);
}
}
@@ -0,0 +1,883 @@
import 'dart:math' as math;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../widgets/luxury_widgets.dart';
/// ==============================================================================
/// SAQEL ENTERPRISE - GEOGEBRA & MATH SYSTEMS INTERACTIVE LAB (120 FPS)
/// ==============================================================================
///
/// معمل برمجية جيوجبرا التفاعلي لحل أنظمة المعادلات بيانياً وجبرياً (الصف العاشر الأساسي):
/// 1. رسم المنحنيات الديكارتية (خطوط مستقيمة، قطع مكافئ، دوائر) بدقة متناهية.
/// 2. كشف نقاط التقاطع الآنية وحساب المميز (Discriminant: Δ = b² - 4ac).
/// 3. محاكاة نشاط الكتاب الوزاري (صفحة 16 - 17): x² + y² = 13 مع x² - y = 7.
/// 4. شروحات جبرية سقراطية خطوة بخطوة لكل تدريب.
class MathInteractiveLabView extends StatefulWidget {
final String? initialPreset;
const MathInteractiveLabView({
super.key,
this.initialPreset,
});
@override
State<MathInteractiveLabView> createState() => _MathInteractiveLabViewState();
}
class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
with SingleTickerProviderStateMixin {
late TabController _tabController;
// Grapher Parameters
// Curve 1: Circle (x² + y² = r²) or Parabola (y = a1*x² + c1)
int _curve1Type = 0; // 0 = Circle (x² + y² = r²), 1 = Parabola (y = a*x² + c)
double _radiusSquared = 13.0;
double _a1 = 1.0;
double _c1 = -7.0;
// Curve 2: Parabola (y = x² - 7) or Line (y = m*x + b)
int _curve2Type = 1; // 0 = Line (y = m*x + b), 1 = Parabola (y = x² + c2)
double _m = 1.0;
double _b = 1.0;
double _c2 = -7.0;
// Zoom scale
double _scale = 22.0;
// Selected Textbook Exercise Index (0 to 5)
int _selectedExerciseIndex = 0;
final List<Map<String, dynamic>> _textbookExercises = [
{
'title': 'نشاط معمل جيوجبرا (صفحة 16)',
'system': 'x² + y² = 13 \n x² - y = 7',
'eq1': 'x² + y² = 13',
'eq2': 'y = x² - 7',
'r2': 13.0,
'c2': -7.0,
'type1': 0, // circle
'type2': 1, // parabola
'delta': 144.0,
'points': [
const Offset(3.0, 2.0),
const Offset(-3.0, 2.0),
const Offset(2.0, -3.0),
const Offset(-2.0, -3.0),
],
'explanation':
'بالتعويض عن x² = y + 7 في معادلة الدائرة: (y + 7) + y² = 13 ⟹ y² + y - 6 = 0 ⟹ (y + 3)(y - 2) = 0. ينتج قيمتان لـ y ومنهما 4 نقاط تقاطع.',
},
{
'title': 'تدريب 1 (صفحة 17 - مميز سالب)',
'system': 'x² + y² = 9 \n y = 2x + 10',
'eq1': 'x² + y² = 9',
'eq2': 'y = 2x + 10',
'r2': 9.0,
'm': 2.0,
'b': 10.0,
'type1': 0, // circle
'type2': 0, // line
'delta': -144.0,
'points': <Offset>[],
'explanation':
'المميز Δ = -144 < 0؛ المستقيم يقع تماماً خارج الدائرة ولا يقطعها في أي نقطة حقيقية. مجموعة الحل = ∅.',
},
{
'title': 'تدريب 2 (صفحة 17 - تقاطع مزدوج)',
'system': 'x² + y² = 20 \n x = 4',
'eq1': 'x² + y² = 20',
'eq2': 'x = 4 (y² = 4)',
'r2': 20.0,
'c2': 0.0,
'type1': 0,
'type2': 0,
'delta': 16.0,
'points': [const Offset(4.0, 2.0), const Offset(4.0, -2.0)],
'explanation':
'عند تعويض x = 4 في معادلة الدائرة: 16 + y² = 20 ⟹ y² = 4 ⟹ y = ±2. إذن نقطتا التقاطع هما: (4, 2) و (4, -2).',
},
{
'title': 'تدريب 3 (صفحة 17 - نقطة تقاطع تقريبية)',
'system': 'y = x² - 1 \n y = 0.5x + 7',
'eq1': 'y = x² - 1',
'eq2': 'y = 0.5x + 7',
'a1': 1.0,
'c1': -1.0,
'm': 0.5,
'b': 7.0,
'type1': 1, // parabola
'type2': 0, // line
'delta': 32.25,
'points': [const Offset(3.09, 8.54), const Offset(-2.59, 5.70)],
'explanation':
'بمساواة المعادلتين: x² - 0.5x - 8 = 0. المميز موجب والمستقيم قاطع للقطع المكافئ في نقطتين.',
},
{
'title': 'تدريب 4 (صفحة 17 - لا تقاطع)',
'system': 'x² + y² = 4 \n y = -x² - 5',
'eq1': 'x² + y² = 4',
'eq2': 'y = -x² - 5',
'r2': 4.0,
'c2': -5.0,
'type1': 0,
'type2': 1,
'delta': -295.0,
'points': <Offset>[],
'explanation':
'المميز Δ = -295 < 0؛ المنحنيان متباعدان هندسياً ولا يتقاطعان. مجموعة الحل خالية ∅.',
},
{
'title': 'تدريب 5 (صفحة 17 - مماسان دقيقان)',
'system': 'x² + y² = 1 \n y = 6x',
'eq1': 'x² + y² = 1',
'eq2': 'y = 6x',
'r2': 1.0,
'm': 6.0,
'b': 0.0,
'type1': 0,
'type2': 0,
'delta': 148.0,
'points': [
const Offset(0.493, 2.959),
const Offset(-0.493, -2.959),
],
'explanation':
'المستقيم يمر بنقطة الأصل ويقطع الدائرة في نقطتين متناظرتين حول المركز: (±3/√37, ±18/√37).',
},
];
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_applyTextbookPreset(0);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _applyTextbookPreset(int index) {
final ex = _textbookExercises[index];
setState(() {
_selectedExerciseIndex = index;
_curve1Type = ex['type1'] as int;
_curve2Type = ex['type2'] as int;
if (ex.containsKey('r2')) _radiusSquared = (ex['r2'] as num).toDouble();
if (ex.containsKey('c1')) _c1 = (ex['c1'] as num).toDouble();
if (ex.containsKey('c2')) _c2 = (ex['c2'] as num).toDouble();
if (ex.containsKey('m')) _m = (ex['m'] as num).toDouble();
if (ex.containsKey('b')) _b = (ex['b'] as num).toDouble();
if (ex.containsKey('a1')) _a1 = (ex['a1'] as num).toDouble();
});
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Column(
children: [
// Sub-navigation bar
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: const BoxDecoration(
color: AppColors.darkSurface,
border: Border(bottom: BorderSide(color: AppColors.darkCardBorder)),
),
child: Row(
children: [
const Icon(CupertinoIcons.function, color: AppColors.saqelCyan, size: 20),
const SizedBox(width: 8),
const Text(
'معمل برمجية جيوجبرا (GeoGebra Lab) — الصف العاشر',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(25),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.saqelCyan.withAlpha(80)),
),
child: const Text(
'محاكي 120 FPS',
style: TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontWeight: FontWeight.w700),
),
),
],
),
),
// Main Tab selector
Container(
color: AppColors.darkSurface,
child: TabBar(
controller: _tabController,
indicatorColor: AppColors.saqelCyan,
indicatorWeight: 3,
labelColor: Colors.white,
unselectedLabelColor: AppColors.textSecondaryDark,
labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
tabs: const [
Tab(icon: Icon(CupertinoIcons.graph_circle_fill, size: 16), text: 'الراسم البياني التفاعلي'),
Tab(icon: Icon(CupertinoIcons.book_fill, size: 16), text: 'تطبيقات الكتاب (صفحة 16 - 17)'),
Tab(icon: Icon(CupertinoIcons.check_mark_circled_solid, size: 16), text: 'خطوات الحل والمميز'),
],
),
),
// Tab Content
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildInteractiveGrapherView(),
_buildTextbookActivitiesView(),
_buildStepByStepSolverView(),
],
),
),
],
),
);
}
// ============================================================================
// TAB 1: INTERACTIVE GRAPHING CANVAS
// ============================================================================
Widget _buildInteractiveGrapherView() {
final activeEx = _textbookExercises[_selectedExerciseIndex];
final points = activeEx['points'] as List<Offset>;
return Row(
children: [
// Left: Cartesian Graph Canvas
Expanded(
flex: 6,
child: Container(
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF070C16),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.darkCardBorder),
boxShadow: const [
BoxShadow(color: Colors.black54, blurRadius: 16, offset: Offset(0, 8)),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
Positioned.fill(
child: CustomPaint(
painter: _CartesianCanvasPainter(
curve1Type: _curve1Type,
radiusSquared: _radiusSquared,
a1: _a1,
c1: _c1,
curve2Type: _curve2Type,
m: _m,
b: _b,
c2: _c2,
scale: _scale,
intersectionPoints: points,
),
),
),
// Controls Overlay (Zoom)
Positioned(
top: 14,
left: 14,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.black.withAlpha(180),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white24),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(CupertinoIcons.zoom_in, color: Colors.white, size: 18),
onPressed: () => setState(() => _scale = (_scale + 4).clamp(12.0, 45.0)),
tooltip: 'تكبير',
),
IconButton(
icon: const Icon(CupertinoIcons.zoom_out, color: Colors.white, size: 18),
onPressed: () => setState(() => _scale = (_scale - 4).clamp(12.0, 45.0)),
tooltip: 'تصغير',
),
IconButton(
icon: const Icon(CupertinoIcons.refresh, color: AppColors.saqelCyan, size: 18),
onPressed: () => setState(() => _scale = 22.0),
tooltip: 'إعادة ضبط المحاور',
),
],
),
),
),
// Curves Legend Badge
Positioned(
bottom: 14,
right: 14,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: Colors.black.withAlpha(200),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Container(width: 12, height: 3, color: AppColors.saqelCyan),
const SizedBox(width: 8),
Text(
_curve1Type == 0
? 'الدائرة: x² + y² = ${_radiusSquared.toStringAsFixed(1)}'
: 'القطع المكافئ 1: y = ${_a1.toStringAsFixed(1)}x² + ${_c1.toStringAsFixed(1)}',
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 4),
Row(
children: [
Container(width: 12, height: 3, color: AppColors.guardianAmber),
const SizedBox(width: 8),
Text(
_curve2Type == 0
? 'المستقيم: y = ${_m.toStringAsFixed(1)}x + ${_b.toStringAsFixed(1)}'
: 'القطع المكافئ 2: y = x² + ${_c2.toStringAsFixed(1)}',
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 4),
Row(
children: [
Container(
width: 8,
height: 8,
decoration: const BoxDecoration(color: Color(0xFFFF2D55), shape: BoxShape.circle),
),
const SizedBox(width: 8),
Text(
'نقاط التقاطع: ${points.isEmpty ? "لا يوجد حل حقيقي (∅)" : points.map((p) => "(${p.dx.toStringAsFixed(1)}, ${p.dy.toStringAsFixed(1)})").join(" , ")}',
style: const TextStyle(color: Color(0xFFFF2D55), fontSize: 11, fontWeight: FontWeight.w700),
),
],
),
],
),
),
),
],
),
),
),
),
// Right: Sliders & Controls Panel
Expanded(
flex: 4,
child: Container(
margin: const EdgeInsets.only(top: 16, bottom: 16, left: 16),
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.darkCardBorder),
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'لوحة المعاملات الحية 🎛️',
style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w800),
),
const SizedBox(height: 4),
const Text(
'غيّر المعاملات وشاهد كيف تتحرك المنحنيات وتتغير نقاط التقاطع فورياً',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
const Divider(color: AppColors.darkCardBorder, height: 24),
// Curve 1 controls
Text(
_curve1Type == 0 ? 'معاملات الدائرة (r²)' : 'معاملات المنحنى الأول',
style: const TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.w700, fontSize: 12),
),
if (_curve1Type == 0) ...[
Row(
children: [
const Text('نصف القطر التربيعي (r²):', style: TextStyle(color: Colors.white70, fontSize: 11)),
const Spacer(),
Text(_radiusSquared.toStringAsFixed(1), style: const TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.bold)),
],
),
Slider(
value: _radiusSquared,
min: 1.0,
max: 25.0,
divisions: 24,
activeColor: AppColors.saqelCyan,
onChanged: (val) => setState(() => _radiusSquared = val),
),
],
const SizedBox(height: 12),
// Curve 2 controls
Text(
_curve2Type == 0 ? 'معاملات المستقيم (الميل والمقطع)' : 'معاملات القطع المكافئ (الإزاحة الرأسية)',
style: const TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.w700, fontSize: 12),
),
if (_curve2Type == 0) ...[
Row(
children: [
const Text('الميل (m):', style: TextStyle(color: Colors.white70, fontSize: 11)),
const Spacer(),
Text(_m.toStringAsFixed(1), style: const TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.bold)),
],
),
Slider(
value: _m,
min: -5.0,
max: 5.0,
divisions: 20,
activeColor: AppColors.guardianAmber,
onChanged: (val) => setState(() => _m = val),
),
Row(
children: [
const Text('المقطع الصادي (b):', style: TextStyle(color: Colors.white70, fontSize: 11)),
const Spacer(),
Text(_b.toStringAsFixed(1), style: const TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.bold)),
],
),
Slider(
value: _b,
min: -10.0,
max: 10.0,
divisions: 40,
activeColor: AppColors.guardianAmber,
onChanged: (val) => setState(() => _b = val),
),
] else ...[
Row(
children: [
const Text('الإزاحة الرأسية (c):', style: TextStyle(color: Colors.white70, fontSize: 11)),
const Spacer(),
Text(_c2.toStringAsFixed(1), style: const TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.bold)),
],
),
Slider(
value: _c2,
min: -12.0,
max: 6.0,
divisions: 36,
activeColor: AppColors.guardianAmber,
onChanged: (val) => setState(() => _c2 = val),
),
],
const SizedBox(height: 16),
// Quick Textbook Presets Buttons
const Text(
'نماذج تدريبات الكتاب الوزاري:',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 12),
),
const SizedBox(height: 8),
Wrap(
spacing: 6,
runSpacing: 6,
children: List.generate(_textbookExercises.length, (i) {
final isSelected = _selectedExerciseIndex == i;
return ChoiceChip(
label: Text(
i == 0 ? 'نشاط ص16' : 'تدريب $i',
style: TextStyle(
color: isSelected ? Colors.black : Colors.white,
fontSize: 11,
fontWeight: FontWeight.w700,
),
),
selected: isSelected,
selectedColor: AppColors.saqelCyan,
backgroundColor: AppColors.darkCard,
onSelected: (_) => _applyTextbookPreset(i),
);
}),
),
],
),
),
),
),
],
);
}
// ============================================================================
// TAB 2: TEXTBOOK ACTIVITIES & GEOGEBRA SCRIPT (PAGES 16 - 17)
// ============================================================================
Widget _buildTextbookActivitiesView() {
return ListView.builder(
padding: const EdgeInsets.all(20),
itemCount: _textbookExercises.length,
itemBuilder: (context, index) {
final item = _textbookExercises[index];
final points = item['points'] as List<Offset>;
final isSelected = _selectedExerciseIndex == index;
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: isSelected ? AppColors.saqelCyan.withAlpha(15) : AppColors.darkSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected ? AppColors.saqelCyan : AppColors.darkCardBorder,
width: isSelected ? 1.5 : 1.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isSelected ? AppColors.saqelCyan : AppColors.darkCard,
borderRadius: BorderRadius.circular(8),
),
child: Text(
item['title'] as String,
style: TextStyle(
color: isSelected ? Colors.black : AppColors.saqelCyan,
fontSize: 12,
fontWeight: FontWeight.w700,
),
),
),
const Spacer(),
ElevatedButton.icon(
icon: const Icon(CupertinoIcons.play_arrow_solid, size: 14),
label: const Text('تشغيل في الراسم'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.saqelCyan,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
textStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold),
),
onPressed: () {
_applyTextbookPreset(index);
_tabController.animateTo(0);
},
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black38,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.white10),
),
child: Text(
'نظام المعادلات:\n${item['system']}',
style: const TextStyle(
fontFamily: 'Courier',
color: AppColors.saqelCyan,
fontSize: 13,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(height: 10),
Row(
children: [
const Icon(CupertinoIcons.smallcircle_fill_circle, color: Color(0xFFFF2D55), size: 14),
const SizedBox(width: 6),
Text(
points.isEmpty
? 'مجموعة الحل: ∅ (المميز Δ = ${item['delta']} < 0)'
: 'نقاط التقاطع: ${points.map((p) => "(${p.dx.toStringAsFixed(1)}, ${p.dy.toStringAsFixed(1)})").join(" , ")}',
style: TextStyle(
color: points.isEmpty ? Colors.white70 : const Color(0xFFFF2D55),
fontWeight: FontWeight.w700,
fontSize: 12,
),
),
],
),
const SizedBox(height: 8),
Text(
item['explanation'] as String,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12, height: 1.5),
),
],
),
);
},
);
}
// ============================================================================
// TAB 3: STEP-BY-STEP ALGEBRAIC SOLVER & SOCRATIC DISCRIMINANT RADAR
// ============================================================================
Widget _buildStepByStepSolverView() {
final active = _textbookExercises[_selectedExerciseIndex];
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
LuxuryCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(CupertinoIcons.wand_rays_inverse, color: AppColors.saqelCyan, size: 22),
const SizedBox(width: 10),
Text(
'خوارزمية الحل الجبري المنهجي — ${active['title']}',
style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w800),
),
],
),
const SizedBox(height: 14),
const Text(
'الخطوة 1: جعل أحد المتغيرين موضوعاً للقانون',
style: TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.w700, fontSize: 13),
),
const SizedBox(height: 4),
Text(
'من المعادلة الثانية: نجعل المتغير الخطي أو التربيعي في طرف مستقل:\n${active['eq2']}',
style: const TextStyle(color: Colors.white70, fontSize: 12),
),
const SizedBox(height: 14),
const Text(
'الخطوة 2: التعويض في المعادلة الأولى',
style: TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.w700, fontSize: 13),
),
const SizedBox(height: 4),
Text(
'نعوض التعبير الجبري في معادلة المنحنى الأول: ${active['eq1']} للحصول على معادلة بمتغير واحد.',
style: const TextStyle(color: Colors.white70, fontSize: 12),
),
const SizedBox(height: 14),
const Text(
'الخطوة 3: حساب المميز الجنائي (Discriminant: Δ = b² - 4ac)',
style: TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.w700, fontSize: 13),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black45,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColors.guardianAmber.withAlpha(60)),
),
child: Row(
children: [
Text(
'قيمة المميز: Δ = ${active['delta']}',
style: const TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.bold, fontSize: 14),
),
const Spacer(),
Text(
(active['delta'] as double) > 0
? 'يوجد حلان حقيقيان (قاطع)'
: (active['delta'] as double) == 0
? 'يوجد حل حقيقي وحيد (مماس)'
: 'المميز سالب: لا يوجد تقاطع (∅)',
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
),
],
),
),
const SizedBox(height: 14),
const Text(
'الخطوة 4: التحليل واستخراج نقاط التقاطع النهائية',
style: TextStyle(color: Color(0xFF34C759), fontWeight: FontWeight.w700, fontSize: 13),
),
const SizedBox(height: 4),
Text(
active['explanation'] as String,
style: const TextStyle(color: Colors.white, fontSize: 12, height: 1.6),
),
],
),
),
],
),
);
}
}
// ==============================================================================
// 120 FPS CARTESIAN CANVAS PAINTER
// ==============================================================================
class _CartesianCanvasPainter extends CustomPainter {
final int curve1Type;
final double radiusSquared;
final double a1;
final double c1;
final int curve2Type;
final double m;
final double b;
final double c2;
final double scale;
final List<Offset> intersectionPoints;
_CartesianCanvasPainter({
required this.curve1Type,
required this.radiusSquared,
required this.a1,
required this.c1,
required this.curve2Type,
required this.m,
required this.b,
required this.c2,
required this.scale,
required this.intersectionPoints,
});
@override
void paint(Canvas canvas, Size size) {
final cx = size.width / 2;
final cy = size.height / 2;
// 1. Draw Grid Lines
final gridPaint = Paint()
..color = const Color(0xFF1B263B)
..strokeWidth = 0.8;
for (double x = cx % scale; x < size.width; x += scale) {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), gridPaint);
}
for (double y = cy % scale; y < size.height; y += scale) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
}
// 2. Draw Main Axes (X and Y)
final axisPaint = Paint()
..color = Colors.white38
..strokeWidth = 1.5;
canvas.drawLine(Offset(0, cy), Offset(size.width, cy), axisPaint);
canvas.drawLine(Offset(cx, 0), Offset(cx, size.height), axisPaint);
// 3. Draw Curve 1 (Cyan)
final curve1Paint = Paint()
..color = AppColors.saqelCyan
..style = PaintingStyle.stroke
..strokeWidth = 2.5;
if (curve1Type == 0) {
// Circle: x² + y² = r²
final rPixel = math.sqrt(radiusSquared) * scale;
canvas.drawCircle(Offset(cx, cy), rPixel, curve1Paint);
} else {
// Parabola: y = a1*x² + c1
final path = Path();
bool started = false;
for (double px = 0; px <= size.width; px += 2) {
final mathX = (px - cx) / scale;
final mathY = a1 * mathX * mathX + c1;
final py = cy - mathY * scale;
if (!started) {
path.moveTo(px, py);
started = true;
} else {
path.lineTo(px, py);
}
}
canvas.drawPath(path, curve1Paint);
}
// 4. Draw Curve 2 (Amber)
final curve2Paint = Paint()
..color = AppColors.guardianAmber
..style = PaintingStyle.stroke
..strokeWidth = 2.5;
if (curve2Type == 0) {
// Line: y = m*x + b
final x1 = (0 - cx) / scale;
final y1 = m * x1 + b;
final py1 = cy - y1 * scale;
final x2 = (size.width - cx) / scale;
final y2 = m * x2 + b;
final py2 = cy - y2 * scale;
canvas.drawLine(Offset(0, py1), Offset(size.width, py2), curve2Paint);
} else {
// Parabola: y = x² + c2
final path = Path();
bool started = false;
for (double px = 0; px <= size.width; px += 2) {
final mathX = (px - cx) / scale;
final mathY = mathX * mathX + c2;
final py = cy - mathY * scale;
if (!started) {
path.moveTo(px, py);
started = true;
} else {
path.lineTo(px, py);
}
}
canvas.drawPath(path, curve2Paint);
}
// 5. Draw Intersection Points (Red Glow)
final ptGlow = Paint()
..color = const Color(0xFFFF2D55).withAlpha(100)
..style = PaintingStyle.fill;
final ptPaint = Paint()
..color = const Color(0xFFFF2D55)
..style = PaintingStyle.fill;
for (var pt in intersectionPoints) {
final px = cx + pt.dx * scale;
final py = cy - pt.dy * scale;
canvas.drawCircle(Offset(px, py), 9.0, ptGlow);
canvas.drawCircle(Offset(px, py), 5.0, ptPaint);
}
}
@override
bool shouldRepaint(covariant _CartesianCanvasPainter oldDelegate) {
return oldDelegate.curve1Type != curve1Type ||
oldDelegate.radiusSquared != radiusSquared ||
oldDelegate.a1 != a1 ||
oldDelegate.c1 != c1 ||
oldDelegate.curve2Type != curve2Type ||
oldDelegate.m != m ||
oldDelegate.b != b ||
oldDelegate.c2 != c2 ||
oldDelegate.scale != scale ||
oldDelegate.intersectionPoints != intersectionPoints;
}
}
@@ -25,6 +25,8 @@ import 'physics_interactive_lab_view.dart';
import 'arabic_interactive_lab_view.dart';
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 '../../../data/models/exam_model.dart';
import '../../../data/repositories/app_repositories.dart';
import '../../../core/services/local_document_storage_service.dart';
@@ -44,6 +46,15 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
late TabController _tabController;
late Future<List<ExamModel>> _examsFuture;
bool get _isMathSubject =>
widget.subject.id.contains('math') ||
widget.subject.title.contains('رياضيات');
bool get _isEnglishSubject =>
widget.subject.id.contains('eng') ||
widget.subject.title.contains('إنجليز') ||
widget.subject.title.contains('لغة إنجليزية');
bool get _isScienceSubject =>
widget.subject.id.contains('physic') ||
widget.subject.title.contains('فيزياء') ||
@@ -72,13 +83,25 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
widget.subject.title.contains('دراسات');
bool get _hasLab =>
_isMathSubject ||
_isEnglishSubject ||
_isScienceSubject ||
_isArabicSubject ||
_isIslamicSubject ||
_isHistorySubject;
Tab get _dynamicLabTab {
if (_isArabicSubject) {
if (_isMathSubject) {
return const Tab(
icon: Icon(CupertinoIcons.function, size: 18),
text: 'معمل جيوجبرا للرياضيات 📐',
);
} else if (_isEnglishSubject) {
return const Tab(
icon: Icon(CupertinoIcons.headphones, size: 18),
text: 'مختبر الصوتيات والقواعد 🎧',
);
} else if (_isArabicSubject) {
return const Tab(
icon: Icon(CupertinoIcons.arrow_branch, size: 18),
text: 'مختبر الإعراب والعروض 📜',
@@ -102,7 +125,11 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
}
Widget get _dynamicLabWidget {
if (_isArabicSubject) {
if (_isMathSubject) {
return const MathInteractiveLabView();
} else if (_isEnglishSubject) {
return const EnglishInteractiveLabView();
} else if (_isArabicSubject) {
return const ArabicInteractiveLabView();
} else if (_isIslamicSubject) {
return const IslamicInteractiveLabView();
@@ -26,6 +26,8 @@ import '../../../logic/cubits/video_playback_cubit.dart';
import '../../widgets/luxury_widgets.dart';
import '../../widgets/socratic_dialog.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../curriculum/math_interactive_lab_view.dart';
/// مشغل الفيديو السقراطي الذكي ونقاط الفحص والإرجاع العلاجي
class SocraticVideoPlayerScreen extends StatefulWidget {
@@ -55,6 +57,8 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
int _lastSyncedPosition = -1;
Timer? _controlsHideTimer;
bool _showVideoControls = true;
final FlutterTts _tts = FlutterTts();
bool _isSpeakingChalkboard = false;
void _revealVideoControls() {
if (!mounted) return;
@@ -65,6 +69,21 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
});
}
Future<void> _speakConcept(String text) async {
if (text.isEmpty) return;
setState(() => _isSpeakingChalkboard = true);
try {
await _tts.setLanguage('ar');
await _tts.setSpeechRate(0.48);
_tts.setCompletionHandler(() {
if (mounted) setState(() => _isSpeakingChalkboard = false);
});
await _tts.speak(text);
} catch (_) {
if (mounted) setState(() => _isSpeakingChalkboard = false);
}
}
@override
void initState() {
super.initState();
@@ -97,6 +116,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
@override
void dispose() {
_tts.stop();
_playbackTicker?.cancel();
_controlsHideTimer?.cancel();
_watermarkController.dispose();
@@ -319,30 +339,10 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
),
),
),
// Center Lesson Title & Visuals
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),
),
],
),
),
// Center Lesson Title & Visuals / Interactive Socratic Chalkboard
if (_videoInitError != null || !_isVideoInitialized || state.playbackData.videoUrl.isEmpty)
Positioned.fill(
child: _buildInteractiveChalkboard(state),
)
else if (_showVideoControls) Center(
child: Column(
@@ -772,6 +772,159 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
),
);
}
Widget _buildInteractiveChalkboard(VideoPlaybackReady state) {
final cp = state.playbackData.checkpoints.isNotEmpty ? state.playbackData.checkpoints.first : null;
final explanation = cp?.pedagogicalExplanation ??
'الدرس الوزاري المعتمد: يركز على إتقان المفاهيم الرياضية والعلمية وحل المسائل التحليلية خطوة بخطوة.';
final hint = cp?.hint ?? 'راجع خطوات الحل ونظم المتغيرات بدقة.';
final isMath = (widget.subject?.id ?? '').contains('math') || widget.lesson.title.contains('معادل') || widget.lesson.title.contains('رياضيات');
return Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
gradient: RadialGradient(
center: Alignment(0.0, -0.2),
radius: 1.2,
colors: [Color(0xFF0F2537), Color(0xFF070D18)],
),
),
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 5),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.saqelCyan.withAlpha(90)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(CupertinoIcons.sparkles, color: AppColors.saqelCyan, size: 14),
SizedBox(width: 6),
Text(
'لوحة الشرح السقراطية التفاعلية 💡',
style: TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.w700),
),
],
),
),
const SizedBox(height: 12),
Text(
widget.lesson.title,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w800),
),
const SizedBox(height: 10),
Container(
constraints: const BoxConstraints(maxWidth: 600),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.black.withAlpha(120),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.white12),
),
child: Column(
children: [
Text(
explanation,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 13, height: 1.5),
),
const SizedBox(height: 8),
Text(
'💡 إرشاد ذهني: $hint',
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.guardianAmber, fontSize: 11, fontWeight: FontWeight.w600),
),
],
),
),
const SizedBox(height: 16),
Wrap(
alignment: WrapAlignment.center,
spacing: 10,
runSpacing: 8,
children: [
ElevatedButton.icon(
icon: Icon(_isSpeakingChalkboard ? CupertinoIcons.waveform : CupertinoIcons.volume_up, size: 16),
label: Text(_isSpeakingChalkboard ? 'جاري الاستماع...' : 'استمع للشرح الصوتي 🎧'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.saqelCyan,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
textStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () => _speakConcept('$explanation. $hint'),
),
if (cp != null)
ElevatedButton.icon(
icon: const Icon(CupertinoIcons.question_circle_fill, size: 16),
label: const Text('فحص الفهم السقراطي ⚡'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.appleBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
textStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (modalContext) => SocraticCheckpointModal(
checkpoint: cp,
onOptionSelected: (opt) {
Navigator.of(modalContext).pop();
context.read<VideoPlaybackCubit>().submitCheckpointAnswer(opt);
},
),
);
},
),
if (isMath)
OutlinedButton.icon(
icon: const Icon(CupertinoIcons.function, size: 16, color: AppColors.saqelCyan),
label: const Text('معمل جيوجبرا للتحقق بيانياً 📐', style: TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.w700)),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: AppColors.saqelCyan),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: AppColors.darkBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (ctx) => const SizedBox(
height: 600,
child: MathInteractiveLabView(),
),
);
},
),
],
),
const SizedBox(height: 12),
const Text(
'فيديو المعلم قيد المزامنة الرقمية والتجهيز؛ تم تفعيل لوحة الشرح التفاعلية لضمان التعلم الفوري المستمر 🚀',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
],
),
),
),
);
}
}
class _GridPatternPainter extends CustomPainter {
+6 -6
View File
@@ -340,10 +340,10 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev"
source: hosted
version: "0.12.19"
version: "0.12.18"
material_color_utilities:
dependency: transitive
description:
@@ -356,10 +356,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.18.0"
version: "1.17.0"
nested:
dependency: transitive
description:
@@ -585,10 +585,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
version: "0.7.9"
typed_data:
dependency: transitive
description: