Update Saqel Platform: 2026-09-13 16:31:17

This commit is contained in:
Hamza-Ayed
2026-09-13 16:31:18 +03:00
parent 81fa8c5dc8
commit 72c6575765
25 changed files with 6997 additions and 3852 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,575 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
/// ==============================================================================
/// SAQEL ENTERPRISE - GRADE 10 DIGITAL SKILLS INTERACTIVE LAB
/// ==============================================================================
/// مختبر المهارات الرقمية التفاعلي المعتمد للصف العاشر:
/// 1. معمل تمثيل البيانات والنظام الثنائي وبكسل الألوان (Binary, ASCII & RGB).
/// 2. معمل تحليل البيانات ومعادلات Excel والرسم البياني المرئي.
/// 3. محاكي إنترنت الأشياء (IoT) والذكاء الاصطناعي وشجرة القرارات.
class DigitalSkillsInteractiveLabView extends StatefulWidget {
const DigitalSkillsInteractiveLabView({super.key});
@override
State<DigitalSkillsInteractiveLabView> createState() =>
_DigitalSkillsInteractiveLabViewState();
}
class _DigitalSkillsInteractiveLabViewState
extends State<DigitalSkillsInteractiveLabView>
with SingleTickerProviderStateMixin {
late TabController _tabController;
// ---------------------------------------------------------------------------
// Tab 1: Binary & RGB Data Representation
// ---------------------------------------------------------------------------
final List<bool> _binaryBits = [false, false, false, false, false, true, false, true]; // 5 in 8-bit
int _redVal = 30;
int _greenVal = 210;
int _blueVal = 180;
int get _decimalValue {
int val = 0;
for (int i = 0; i < 8; i++) {
if (_binaryBits[i]) {
val += (1 << (7 - i));
}
}
return val;
}
String get _hexValue => _decimalValue.toRadixString(16).toUpperCase().padLeft(2, '0');
String get _asciiChar {
final d = _decimalValue;
if (d >= 32 && d <= 126) {
return String.fromCharCode(d);
}
return 'غير قابل للطباعة (Control Code)';
}
// ---------------------------------------------------------------------------
// Tab 2: Excel & Data Analytics
// ---------------------------------------------------------------------------
final List<Map<String, dynamic>> _dataRows = [
{'item': 'عمان', 'val': 85.0},
{'item': 'إربد', 'val': 72.0},
{'item': 'الزرقاء', 'val': 68.0},
{'item': 'العقبة', 'val': 94.0},
{'item': 'البلقاء', 'val': 61.0},
];
double get _calcSum => _dataRows.fold(0.0, (acc, r) => acc + (r['val'] as double));
double get _calcAvg => _dataRows.isNotEmpty ? _calcSum / _dataRows.length : 0.0;
double get _calcMax => _dataRows.fold(0.0, (max, r) => (r['val'] as double) > max ? (r['val'] as double) : max);
double get _calcMin => _dataRows.fold(999.0, (min, r) => (r['val'] as double) < min ? (r['val'] as double) : min);
// ---------------------------------------------------------------------------
// Tab 3: IoT Sensors & AI Decision Tree
// ---------------------------------------------------------------------------
double _temperature = 28.0;
double _humidity = 45.0;
bool _motionDetected = false;
String get _aiDecision {
if (_temperature > 32.0 && _humidity > 60.0) {
return 'تشغيل نظام التكييف الذكي وخفض الرطوبة (تحذير إجهاد حراري)';
} else if (_temperature < 16.0) {
return 'تشغيل نظام التدفئة المركزية والتهوية الآمنة';
} else if (_motionDetected) {
return 'تفعيل الإضاءة الذكية وتسجيل نشاط الكاميرا';
}
return 'البيئة مستقرة ومثالية • الاستهلاك الاقتصادي للطاقة مفعل';
}
Color get _aiStatusColor {
if (_temperature > 32.0 || _temperature < 16.0) {
return const Color(0xFFF59E0B);
}
if (_motionDetected) {
return const Color(0xFF38BDF8);
}
return const Color(0xFF10B981);
}
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: const EdgeInsets.fromLTRB(16, 12, 16, 8),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.darkCardBorder),
),
child: TabBar(
controller: _tabController,
indicatorColor: AppColors.saqelCyan,
labelColor: Colors.white,
unselectedLabelColor: AppColors.textSecondaryDark,
indicatorSize: TabBarIndicatorSize.tab,
labelStyle: const TextStyle(fontWeight: FontWeight.w800, fontSize: 11.5),
tabs: const [
Tab(text: 'تمثيل البيانات والثنائي 0101'),
Tab(text: 'تحليل البيانات وExcel 📊'),
Tab(text: 'إنترنت الأشياء والذكاء 🤖'),
],
),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildBinaryLab(),
_buildExcelLab(),
_buildIotLab(),
],
),
),
],
);
}
Widget _buildBinaryLab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 8-bit Binary Switcher Card
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: AppColors.saqelCyan.withValues(alpha: 0.4)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(CupertinoIcons.chevron_left_slash_chevron_right, color: AppColors.saqelCyan, size: 18),
SizedBox(width: 8),
Text(
'محاكي بايت البيانات الثنائي (8-Bit Binary Byte):',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5),
),
],
),
const SizedBox(height: 12),
const Text(
'انقر على البت لتشغيله (1) أو إطفائه (0) وشاهد تحويل القيمة فورياً:',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
),
const SizedBox(height: 14),
// 8 Switches
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(8, (i) {
final weight = 1 << (7 - i);
final isSet = _binaryBits[i];
return GestureDetector(
onTap: () {
setState(() {
_binaryBits[i] = !_binaryBits[i];
});
},
child: Column(
children: [
Text(
'$weight',
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 10, fontWeight: FontWeight.w700),
),
const SizedBox(height: 6),
AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 34,
height: 48,
decoration: BoxDecoration(
color: isSet ? AppColors.saqelCyan.withValues(alpha: 0.25) : Colors.black26,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSet ? AppColors.saqelCyan : Colors.white24,
width: isSet ? 2 : 1,
),
),
child: Center(
child: Text(
isSet ? '1' : '0',
style: TextStyle(
color: isSet ? AppColors.saqelCyan : Colors.white38,
fontSize: 18,
fontWeight: FontWeight.w900,
),
),
),
),
],
),
);
}),
),
const SizedBox(height: 16),
// Computed Output Badges
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black26,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildResultBadge('العشري (Decimal)', '$_decimalValue'),
_buildResultBadge('الست عشري (Hex)', '0x$_hexValue'),
_buildResultBadge('حرف ASCII', _asciiChar),
],
),
),
],
),
),
const SizedBox(height: 16),
// RGB Pixel Inspector
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: Colors.white12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(CupertinoIcons.paintbrush_fill, color: Color(0xFFFF375F), size: 18),
SizedBox(width: 8),
Text(
'مفتش البكسل اللوني وتمثيل الصور (24-bit RGB):',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5),
),
],
),
const SizedBox(height: 12),
Row(
children: [
// Color Preview Box
Container(
width: 70,
height: 70,
decoration: BoxDecoration(
color: Color.fromARGB(255, _redVal, _greenVal, _blueVal),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white24, width: 2),
boxShadow: [
BoxShadow(
color: Color.fromARGB(255, _redVal, _greenVal, _blueVal).withValues(alpha: 0.5),
blurRadius: 14,
),
],
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'RGB($_redVal, $_greenVal, $_blueVal)',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w900, fontSize: 14),
),
const SizedBox(height: 4),
Text(
'حجم البكسل: 3 بايت = 24 بت في الذاكرة (16.7 مليون لون ممكن)',
style: TextStyle(color: Colors.white.withValues(alpha: 0.7), fontSize: 11),
),
],
),
),
],
),
const SizedBox(height: 12),
_buildColorSlider('أحمر (Red R)', _redVal, const Color(0xFFFF453A), (v) => setState(() => _redVal = v.round())),
_buildColorSlider('أخضر (Green G)', _greenVal, const Color(0xFF30D158), (v) => setState(() => _greenVal = v.round())),
_buildColorSlider('أزرق (Blue B)', _blueVal, const Color(0xFF0A84FF), (v) => setState(() => _blueVal = v.round())),
],
),
),
],
),
);
}
Widget _buildExcelLab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Formulas Summary Header
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF10B981).withValues(alpha: 0.4)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatColumn('دالة المجموع', '=SUM(B2:B6)', _calcSum.toStringAsFixed(0), const Color(0xFF10B981)),
_buildStatColumn('دالة المتوسط', '=AVERAGE(B2:B6)', _calcAvg.toStringAsFixed(1), const Color(0xFF38BDF8)),
_buildStatColumn('أعلى قيمة', '=MAX(B2:B6)', _calcMax.toStringAsFixed(0), const Color(0xFFF59E0B)),
_buildStatColumn('أدنى قيمة', '=MIN(B2:B6)', _calcMin.toStringAsFixed(0), const Color(0xFFEC4899)),
],
),
),
const SizedBox(height: 16),
// Interactive Data Table & Bar Chart
Container(
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: [
const Row(
children: [
Icon(CupertinoIcons.table, color: Color(0xFF10B981), size: 18),
SizedBox(width: 8),
Text(
'جدول البيانات والتمثيل المرئي (Data Visualization):',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5),
),
],
),
const SizedBox(height: 14),
..._dataRows.map((row) {
final val = row['val'] as double;
final ratio = (_calcMax > 0) ? (val / _calcMax) : 0.0;
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SizedBox(
width: 60,
child: Text(
row['item'],
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 12.5),
),
),
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(6),
child: LinearProgressIndicator(
value: ratio,
minHeight: 14,
backgroundColor: Colors.white10,
valueColor: const AlwaysStoppedAnimation(Color(0xFF10B981)),
),
),
),
const SizedBox(width: 12),
Text(
val.toStringAsFixed(0),
style: const TextStyle(color: Color(0xFF10B981), fontWeight: FontWeight.w900, fontSize: 13),
),
],
),
],
),
);
}),
],
),
),
],
),
);
}
Widget _buildIotLab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// IoT Sensor Sliders Card
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.saqelCyan.withValues(alpha: 0.4)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(CupertinoIcons.radiowaves_right, color: AppColors.saqelCyan, size: 18),
SizedBox(width: 8),
Text(
'محاكي مجسات إنترنت الأشياء (IoT Smart Sensors):',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5),
),
],
),
const SizedBox(height: 14),
// Temperature Slider
Text(
'حساس درجة الحرارة (DHT22): ${_temperature.toStringAsFixed(1)} °C',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 12.5),
),
Slider(
value: _temperature,
min: 10.0,
max: 45.0,
divisions: 35,
activeColor: const Color(0xFFFF9F0A),
onChanged: (v) => setState(() => _temperature = v),
),
// Humidity Slider
Text(
'حساس الرطوبة النسبية: ${_humidity.toStringAsFixed(0)} %',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 12.5),
),
Slider(
value: _humidity,
min: 20.0,
max: 95.0,
divisions: 75,
activeColor: const Color(0xFF38BDF8),
onChanged: (v) => setState(() => _humidity = v),
),
// Motion Detection Switch
Row(
children: [
const Icon(CupertinoIcons.person_crop_circle_badge_exclam, color: Colors.white70, size: 18),
const SizedBox(width: 8),
const Text('مستشعر الحركة بالأشعة (PIR):', style: TextStyle(color: Colors.white, fontSize: 12.5, fontWeight: FontWeight.w600)),
const Spacer(),
CupertinoSwitch(
value: _motionDetected,
activeTrackColor: AppColors.saqelCyan,
onChanged: (v) => setState(() => _motionDetected = v),
),
],
),
],
),
),
const SizedBox(height: 14),
// AI Decision Output Box
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _aiStatusColor.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _aiStatusColor),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(CupertinoIcons.sparkles, color: _aiStatusColor, size: 20),
const SizedBox(width: 8),
Text(
'قرار الذكاء الاصطناعي (AI Decision Engine):',
style: TextStyle(color: _aiStatusColor, fontWeight: FontWeight.w800, fontSize: 13.5),
),
],
),
const SizedBox(height: 10),
Text(
_aiDecision,
style: const TextStyle(color: Colors.white, fontSize: 13.5, height: 1.45, fontWeight: FontWeight.w700),
),
],
),
),
],
),
);
}
Widget _buildResultBadge(String label, String val) {
return Column(
children: [
Text(label, style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11)),
const SizedBox(height: 4),
Text(val, style: const TextStyle(color: AppColors.saqelCyan, fontSize: 16, fontWeight: FontWeight.w900)),
],
);
}
Widget _buildColorSlider(String label, int value, Color col, ValueChanged<double> onChanged) {
return Row(
children: [
SizedBox(
width: 90,
child: Text(label, style: TextStyle(color: col, fontSize: 11.5, fontWeight: FontWeight.w700)),
),
Expanded(
child: Slider(
value: value.toDouble(),
min: 0,
max: 255,
activeColor: col,
onChanged: onChanged,
),
),
SizedBox(
width: 32,
child: Text('$value', style: const TextStyle(color: Colors.white, fontSize: 11.5, fontWeight: FontWeight.w700)),
),
],
);
}
Widget _buildStatColumn(String label, String formula, String val, Color col) {
return Column(
children: [
Text(label, style: const TextStyle(color: Colors.white70, fontSize: 10.5, fontWeight: FontWeight.w700)),
const SizedBox(height: 2),
Text(formula, style: TextStyle(color: col, fontSize: 9.5, fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text(val, style: TextStyle(color: col, fontSize: 16, fontWeight: FontWeight.w900)),
],
);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -15,6 +15,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/utils/app_logger.dart';
import '../../../core/utils/saqel_toast.dart';
import '../../../data/models/subject_model.dart';
import '../../widgets/luxury_widgets.dart';
@@ -30,6 +31,7 @@ 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 'digital_skills_interactive_lab_view.dart';
import '../virtual_labs/labs_gallery_screen.dart';
import '../virtual_labs/labs_registry.dart';
import '../virtual_labs/subject_virtual_labs_view.dart';
@@ -54,6 +56,7 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
late Future<List<ExamModel>> _examsFuture;
String _selectedSemester = 'semester_1';
bool get _isMathSubject =>
widget.subject.id.contains('math') ||
@@ -101,6 +104,12 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
(widget.subject.title.contains('تاريخ') && !widget.subject.title.contains('جغرافيا')) ||
(widget.subject.title.contains('أردن') && !widget.subject.title.contains('جغرافيا'));
bool get _isDigitalSkillsSubject =>
widget.subject.id.contains('digital') ||
widget.subject.id.contains('computer') ||
widget.subject.title.contains('رقمي') ||
widget.subject.title.contains('حاسوب');
bool get _hasLab =>
Grade10LabsRegistry.bySubjectNormalized(widget.subject.title).isNotEmpty ||
_isMathSubject ||
@@ -111,7 +120,8 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
_isPhysicsSubject ||
_isArabicSubject ||
_isIslamicSubject ||
_isHistorySubject;
_isHistorySubject ||
_isDigitalSkillsSubject;
Tab get _dynamicLabTab {
final norm = Grade10LabsRegistry.normalizeSubject(widget.subject.title);
@@ -133,12 +143,12 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
} else if (_isIslamicSubject || norm == 'التربية الإسلامية') {
return const Tab(
icon: Icon(CupertinoIcons.money_dollar_circle_fill, size: 18),
text: 'حاسبة المواريث والتجويد ⚖️',
text: 'معمل المعاملات والتجويد ⚖️',
);
} else if (_isHistorySubject || norm == 'تاريخ الأردن' || norm == 'التاريخ') {
return const Tab(
icon: Icon(CupertinoIcons.time_solid, size: 18),
text: 'الخط الزمني الشامل والتاريخ 🏛️',
text: 'مختبر التاريخ والخط الزمني 🏛️',
);
} else if (_isChemistrySubject || norm == 'الكيمياء') {
return const Tab(
@@ -160,10 +170,10 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
icon: Icon(CupertinoIcons.chart_bar_square_fill, size: 18),
text: 'مختبر الثقافة المالية والجدوى 📊',
);
} else if (norm == 'المهارات الرقمية') {
} else if (_isDigitalSkillsSubject || norm == 'المهارات الرقمية') {
return const Tab(
icon: Icon(CupertinoIcons.desktopcomputer, size: 18),
text: 'مختبر الخوارزميات والبرمجة 💻',
text: 'معمل البيانات والذكاء الاصطناعي 💻',
);
} else if (norm == 'الجغرافيا') {
return const Tab(
@@ -204,10 +214,13 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
specializedTitle = 'شجرة الإعراب 📜';
} else if (_isIslamicSubject || norm == 'التربية الإسلامية') {
specializedTool = const IslamicInteractiveLabView();
specializedTitle = 'حاسبة المواريث ⚖️';
specializedTitle = 'معمل المعاملات والتجويد ⚖️';
} else if (_isHistorySubject || norm == 'تاريخ الأردن' || norm == 'التاريخ') {
specializedTool = const HistoryInteractiveTimelineView();
specializedTitle = 'الخط الزمني الشامل 🏛️';
specializedTitle = 'الخط الزمني الشامل والتاريخ 🏛️';
} else if (_isDigitalSkillsSubject || norm == 'المهارات الرقمية') {
specializedTool = const DigitalSkillsInteractiveLabView();
specializedTitle = 'معمل البيانات والذكاء 💻';
} else if (_isChemistrySubject || norm == 'الكيمياء') {
specializedTool = const ChemistryInteractiveLabView();
specializedTitle = 'طيف ذرة بور ⚗️';
@@ -227,6 +240,7 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
primaryColor: widget.subject.primaryColor,
specializedToolWidget: specializedTool,
specializedToolTitle: specializedTitle,
selectedSemester: _selectedSemester,
);
}
@@ -310,9 +324,115 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
);
}
/// Semester Switcher Component for Multi-Semester Subjects
Widget _buildSemesterSelector() {
final s1Count = widget.subject.semester1Units.length;
final s2Count = widget.subject.semester2Units.length;
return Container(
margin: const EdgeInsets.fromLTRB(18, 14, 18, 6),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.darkCardBorder),
),
child: Row(
children: [
Expanded(
child: _buildSemesterSegment(
key: 'semester_1',
title: 'الفصل الدراسي الأول',
count: s1Count,
isSelected: _selectedSemester == 'semester_1',
),
),
const SizedBox(width: 6),
Expanded(
child: _buildSemesterSegment(
key: 'semester_2',
title: 'الفصل الدراسي الثاني',
count: s2Count,
isSelected: _selectedSemester == 'semester_2',
),
),
],
),
);
}
Widget _buildSemesterSegment({
required String key,
required String title,
required int count,
required bool isSelected,
}) {
final color = widget.subject.primaryColor;
return GestureDetector(
onTap: () {
if (_selectedSemester != key) {
setState(() {
_selectedSemester = key;
});
}
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: const EdgeInsets.symmetric(vertical: 9, horizontal: 12),
decoration: BoxDecoration(
color: isSelected ? color.withValues(alpha: 0.18) : Colors.transparent,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSelected ? color.withValues(alpha: 0.6) : Colors.transparent,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
isSelected ? CupertinoIcons.checkmark_seal_fill : CupertinoIcons.calendar,
size: 14,
color: isSelected ? color : AppColors.textSecondaryDark,
),
const SizedBox(width: 6),
Text(
title,
style: TextStyle(
color: isSelected ? Colors.white : AppColors.textSecondaryDark,
fontSize: 12.5,
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500,
),
),
if (count > 0) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1.5),
decoration: BoxDecoration(
color: isSelected ? color.withValues(alpha: 0.3) : AppColors.darkCardBorder,
borderRadius: BorderRadius.circular(10),
),
child: Text(
'$count وحدات',
style: TextStyle(
color: isSelected ? color : AppColors.textSecondaryDark,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
],
],
),
),
);
}
/// Tab 1: Interactive Video Lessons by Unit
Widget _buildLessonsTab(BuildContext context) {
final units = widget.subject.units;
final hasMultipleSemesters = widget.subject.semester1Units.isNotEmpty && widget.subject.semester2Units.isNotEmpty;
final units = hasMultipleSemesters
? widget.subject.unitsForSemester(_selectedSemester)
: widget.subject.units;
if (units.isEmpty) {
return Center(
@@ -330,11 +450,15 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
);
}
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 20),
itemCount: units.length,
itemBuilder: (context, uIdx) {
final unit = units[uIdx];
return Column(
children: [
if (hasMultipleSemesters) _buildSemesterSelector(),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
itemCount: units.length,
itemBuilder: (context, uIdx) {
final unit = units[uIdx];
return Container(
margin: const EdgeInsets.only(bottom: 20),
@@ -380,6 +504,9 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
lessonTitle: lesson.title,
lessonId: lesson.id,
curriculumLessonId: lesson.curriculumLessonId,
filePath: lesson.markdownFilePath,
unitKey: unit.id,
semesterKey: _selectedSemester,
);
return Container(
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
@@ -455,6 +582,13 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
size: 16,
),
onTap: () {
AppLogger.event('LessonTileTapped', details: {
'subject': widget.subject.title,
'lessonTitle': lesson.title,
'lessonId': lesson.id,
'hasVideo': hasVid,
'curriculumLessonId': lesson.curriculumLessonId,
}, tag: 'SUBJECT_HUB');
_showLessonVideoSelector(context, lesson);
},
),
@@ -463,7 +597,15 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => Grade10LabsRegistry.openLab(context, lab),
onTap: () {
AppLogger.event('OpenLessonLab', details: {
'subject': widget.subject.title,
'lesson': lesson.title,
'lab': lab.lessonAr,
'curriculumId': lab.identity.curriculumLessonId,
}, tag: 'SUBJECT_HUB');
Grade10LabsRegistry.openLab(context, lab);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
decoration: BoxDecoration(
@@ -476,14 +618,42 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
const Icon(CupertinoIcons.lab_flask_solid, color: AppColors.saqelCyan, size: 14),
const SizedBox(width: 6),
Expanded(
child: Text(
'المختبر الافتراضي: ${lab.lessonAr}',
style: const TextStyle(
color: Colors.white,
fontSize: 11.5,
fontWeight: FontWeight.w700,
),
overflow: TextOverflow.ellipsis,
child: Row(
children: [
Flexible(
child: Text(
'المختبر: ${lab.lessonAr}',
style: const TextStyle(
color: Colors.white,
fontSize: 11.5,
fontWeight: FontWeight.w700,
),
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1.5),
decoration: BoxDecoration(
color: lab.identity.isPublished
? AppColors.teacherEmerald.withAlpha(35)
: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(4),
),
child: Text(
lab.identity.isPublished
? 'معتمد 🟢'
: (lab.identity.isBound ? 'جاهز للاستخدام 🟢' : 'معاينة 🔵'),
style: TextStyle(
color: lab.identity.isPublished
? AppColors.teacherEmerald
: AppColors.saqelCyan,
fontSize: 9.5,
fontWeight: FontWeight.w800,
),
),
),
],
),
),
Container(
@@ -513,62 +683,32 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
),
),
)
else if (_hasLab)
else
Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () {
if (_tabController.length > 1) {
_tabController.animateTo(1);
}
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
decoration: BoxDecoration(
color: widget.subject.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: widget.subject.primaryColor.withValues(alpha: 0.25)),
),
child: Row(
children: [
Icon(CupertinoIcons.lab_flask_solid, color: widget.subject.primaryColor, size: 14),
const SizedBox(width: 6),
Expanded(
child: Text(
'مختبر ${widget.subject.title} التفاعلي المعتمد',
style: const TextStyle(
color: Colors.white,
fontSize: 11.5,
fontWeight: FontWeight.w700,
),
overflow: TextOverflow.ellipsis,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withAlpha(6),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.white.withAlpha(12)),
),
child: const Row(
children: [
Icon(CupertinoIcons.lab_flask, color: Colors.white38, size: 13),
SizedBox(width: 6),
Expanded(
child: Text(
'المختبر الافتراضي: قيد الإعداد الأكاديمي ⚪',
style: TextStyle(
color: Colors.white54,
fontSize: 11,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: widget.subject.primaryColor.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(5),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'انتقل للمختبر',
style: TextStyle(
color: widget.subject.primaryColor,
fontSize: 10.5,
fontWeight: FontWeight.w800,
),
),
const SizedBox(width: 3),
Icon(CupertinoIcons.arrow_left, color: widget.subject.primaryColor, size: 10),
],
),
),
],
),
),
],
),
),
),
@@ -634,6 +774,9 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
),
);
},
),
),
],
);
}
@@ -1098,6 +1241,7 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
lessonTitle: lesson.title,
lessonId: lesson.id,
curriculumLessonId: lesson.curriculumLessonId,
filePath: lesson.markdownFilePath,
);
showModalBottomSheet(