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
@@ -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 {