Update Saqel Platform: 2026-09-12 13:38:49
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../data/repositories/curriculum_question_bank.dart';
|
||||
import '../../../data/repositories/error_notebook_repository.dart';
|
||||
import '../../widgets/luxury_widgets.dart';
|
||||
|
||||
/// Interactive Lesson Homework Sheet
|
||||
/// Enables students to practice questions derived from textbook lesson examples.
|
||||
class LessonHomeworkSheet extends StatefulWidget {
|
||||
final String subjectId;
|
||||
final String subjectTitle;
|
||||
final String lessonId;
|
||||
final String lessonTitle;
|
||||
|
||||
const LessonHomeworkSheet({
|
||||
super.key,
|
||||
required this.subjectId,
|
||||
required this.subjectTitle,
|
||||
required this.lessonId,
|
||||
required this.lessonTitle,
|
||||
});
|
||||
|
||||
static Future<void> show(
|
||||
BuildContext context, {
|
||||
required String subjectId,
|
||||
required String subjectTitle,
|
||||
required String lessonId,
|
||||
required String lessonTitle,
|
||||
}) {
|
||||
return showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (ctx) => FractionallySizedBox(
|
||||
heightFactor: 0.92,
|
||||
child: LessonHomeworkSheet(
|
||||
subjectId: subjectId,
|
||||
subjectTitle: subjectTitle,
|
||||
lessonId: lessonId,
|
||||
lessonTitle: lessonTitle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
State<LessonHomeworkSheet> createState() => _LessonHomeworkSheetState();
|
||||
}
|
||||
|
||||
class _LessonHomeworkSheetState extends State<LessonHomeworkSheet> {
|
||||
late LessonHomeworkModel _homework;
|
||||
int _currentIndex = 0;
|
||||
int? _selectedOption;
|
||||
bool _hasChecked = false;
|
||||
int _correctCount = 0;
|
||||
bool _isCompleted = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_homework = CurriculumQuestionBank.getLessonHomework(
|
||||
subjectId: widget.subjectId,
|
||||
lessonId: widget.lessonId,
|
||||
lessonTitle: widget.lessonTitle,
|
||||
);
|
||||
}
|
||||
|
||||
void _checkAnswer() {
|
||||
if (_selectedOption == null || _hasChecked) return;
|
||||
setState(() {
|
||||
_hasChecked = true;
|
||||
if (_selectedOption == _homework.questions[_currentIndex].correctIndex) {
|
||||
_correctCount++;
|
||||
} else {
|
||||
_recordMistakeToNotebook(_homework.questions[_currentIndex], _selectedOption!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _recordMistakeToNotebook(LessonHomeworkItem q, int selectedOpt) {
|
||||
final wrongAnswer = (selectedOpt >= 0 && selectedOpt < q.options.length)
|
||||
? q.options[selectedOpt]
|
||||
: 'إجابة غير صحيحة';
|
||||
final correctAnswer = (q.correctIndex >= 0 && q.correctIndex < q.options.length)
|
||||
? q.options[q.correctIndex]
|
||||
: '';
|
||||
ErrorNotebookRepository().logError(
|
||||
subjectId: widget.subjectId,
|
||||
subjectName: widget.subjectTitle,
|
||||
topicName: widget.lessonTitle,
|
||||
sourceType: 'adaptive_exam',
|
||||
questionText: q.questionText,
|
||||
studentWrongAnswer: wrongAnswer,
|
||||
correctAnswer: correctAnswer,
|
||||
hint: q.explanation,
|
||||
errorCategory: 'conceptual',
|
||||
options: q.options,
|
||||
);
|
||||
}
|
||||
|
||||
void _nextQuestion() {
|
||||
if (_currentIndex + 1 < _homework.questions.length) {
|
||||
setState(() {
|
||||
_currentIndex++;
|
||||
_selectedOption = null;
|
||||
_hasChecked = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isCompleted = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _restart() {
|
||||
setState(() {
|
||||
_currentIndex = 0;
|
||||
_selectedOption = null;
|
||||
_hasChecked = false;
|
||||
_correctCount = 0;
|
||||
_isCompleted = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.darkBackground,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
border: Border(
|
||||
top: BorderSide(color: AppColors.darkCardBorder, width: 1.5),
|
||||
),
|
||||
),
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Column(
|
||||
children: [
|
||||
// Sheet Handle
|
||||
Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 10, bottom: 8),
|
||||
width: 44,
|
||||
height: 4.5,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white24,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Header Bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(CupertinoIcons.pencil_ellipsis_rectangle,
|
||||
color: AppColors.saqelCyan, size: 20),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'واجب الدرس: ${widget.lessonTitle}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 15,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
'${widget.subjectTitle} • تمارين تطبيقية على نمط المنهاج الوزاري',
|
||||
style: const TextStyle(
|
||||
color: AppColors.textSecondaryDark,
|
||||
fontSize: 11.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(CupertinoIcons.xmark_circle_fill,
|
||||
color: Colors.white38, size: 24),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(color: AppColors.darkCardBorder, height: 1),
|
||||
|
||||
// Content Area
|
||||
Expanded(
|
||||
child: _isCompleted ? _buildCompletedView() : _buildQuestionView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuestionView() {
|
||||
if (_homework.questions.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(
|
||||
'لا توجد أسئلة واجب لهذا الدرس حالياً.',
|
||||
style: TextStyle(color: AppColors.textSecondaryDark),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final q = _homework.questions[_currentIndex];
|
||||
final total = _homework.questions.length;
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
|
||||
children: [
|
||||
// Progress Row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'التمرين ${_currentIndex + 1} من $total',
|
||||
style: const TextStyle(
|
||||
color: AppColors.saqelCyan,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12.5,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.emeraldGreen.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'${q.points} نقاط إتقان',
|
||||
style: const TextStyle(
|
||||
color: AppColors.emeraldGreen,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Linear Progress
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: (_currentIndex + 1) / total,
|
||||
backgroundColor: Colors.white10,
|
||||
valueColor: const AlwaysStoppedAnimation(AppColors.saqelCyan),
|
||||
minHeight: 5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Question Card
|
||||
LuxuryCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
q.questionText,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 15,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Options
|
||||
...List.generate(q.options.length, (idx) {
|
||||
final isSelected = _selectedOption == idx;
|
||||
final isCorrect = idx == q.correctIndex;
|
||||
|
||||
Color borderColor = Colors.white12;
|
||||
Color bgColor = const Color(0xFF09111E);
|
||||
Widget? trailingIcon;
|
||||
|
||||
if (_hasChecked) {
|
||||
if (isCorrect) {
|
||||
borderColor = AppColors.emeraldGreen;
|
||||
bgColor = AppColors.emeraldGreen.withValues(alpha: 0.15);
|
||||
trailingIcon = const Icon(CupertinoIcons.checkmark_circle_fill,
|
||||
color: AppColors.emeraldGreen, size: 20);
|
||||
} else if (isSelected) {
|
||||
borderColor = AppColors.crimsonRed;
|
||||
bgColor = AppColors.crimsonRed.withValues(alpha: 0.15);
|
||||
trailingIcon = const Icon(CupertinoIcons.xmark_circle_fill,
|
||||
color: AppColors.crimsonRed, size: 20);
|
||||
}
|
||||
} else if (isSelected) {
|
||||
borderColor = AppColors.saqelCyan;
|
||||
bgColor = AppColors.saqelCyan.withValues(alpha: 0.12);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _hasChecked
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_selectedOption = idx;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: borderColor, width: isSelected || (_hasChecked && isCorrect) ? 1.5 : 1),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 26,
|
||||
height: 26,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: isSelected ? AppColors.saqelCyan : Colors.white10,
|
||||
),
|
||||
child: Text(
|
||||
String.fromCharCode(0x0623 + idx), // أ, ب, ج, د
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
q.options[idx],
|
||||
style: TextStyle(
|
||||
color: isSelected || (_hasChecked && isCorrect) ? Colors.white : Colors.white70,
|
||||
fontSize: 13.5,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (trailingIcon != null) trailingIcon,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Explanation Card upon verification
|
||||
if (_hasChecked) ...[
|
||||
const SizedBox(height: 14),
|
||||
LuxuryCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
_selectedOption == q.correctIndex
|
||||
? CupertinoIcons.check_mark_circled_solid
|
||||
: CupertinoIcons.info_circle_fill,
|
||||
color: _selectedOption == q.correctIndex
|
||||
? AppColors.emeraldGreen
|
||||
: AppColors.guardianAmber,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
_selectedOption == q.correctIndex
|
||||
? 'إجابة صحيحة! خطوات التعليل والحل النموذجي:'
|
||||
: 'الشرح التوضيحي والتعليل الشرعي / العلمي:',
|
||||
style: TextStyle(
|
||||
color: _selectedOption == q.correctIndex
|
||||
? AppColors.emeraldGreen
|
||||
: AppColors.guardianAmber,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
q.explanation,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 13,
|
||||
height: 1.6,
|
||||
),
|
||||
),
|
||||
if (q.ruleTakeaway != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(CupertinoIcons.lightbulb_fill, color: AppColors.saqelCyan, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'القاعدة الذهبية: ${q.ruleTakeaway!}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.saqelCyan,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Action Button
|
||||
if (!_hasChecked)
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _selectedOption != null ? AppColors.appleBlue : Colors.white12,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: _selectedOption != null ? _checkAnswer : null,
|
||||
child: const Text(
|
||||
'تحقق من الإجابة 🚀',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800),
|
||||
),
|
||||
)
|
||||
else
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.saqelCyan,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: _nextQuestion,
|
||||
child: Text(
|
||||
_currentIndex + 1 < total ? 'التمرين التالي ⬅️' : 'عرض النتيجة النهائية 🏆',
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w900),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompletedView() {
|
||||
final total = _homework.questions.length;
|
||||
final percentage = total > 0 ? (_correctCount / total) * 100 : 0;
|
||||
final isMastered = percentage >= 70;
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: isMastered
|
||||
? AppColors.emeraldGreen.withValues(alpha: 0.15)
|
||||
: AppColors.guardianAmber.withValues(alpha: 0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isMastered ? CupertinoIcons.rosette : CupertinoIcons.refresh_circled,
|
||||
color: isMastered ? AppColors.emeraldGreen : AppColors.guardianAmber,
|
||||
size: 54,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
isMastered ? 'أحسنت! أتممت واجب الدرس بنجاح 🌟' : 'اكتمل حل التمارين! يمكنك المحاولة مجدداً لترسيخ الفهم 📚',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 16.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'أجبت عن $_correctCount من أصل $total أسئلة بشكل صحيح (${percentage.toStringAsFixed(0)}%)',
|
||||
style: const TextStyle(
|
||||
color: AppColors.textSecondaryDark,
|
||||
fontSize: 13.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
side: const BorderSide(color: Colors.white24),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: _restart,
|
||||
icon: const Icon(CupertinoIcons.refresh, size: 18),
|
||||
label: const Text('إعادة الحل', style: TextStyle(fontWeight: FontWeight.w700)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.appleBlue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(CupertinoIcons.check_mark, size: 18),
|
||||
label: const Text('تم الفهم', style: TextStyle(fontWeight: FontWeight.w800)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+251
-55
@@ -48,6 +48,10 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
|
||||
// Selected Textbook Exercise Index (0 to 5)
|
||||
int _selectedExerciseIndex = 0;
|
||||
|
||||
// Scaffolded Problem Solving & 60-Second Thinking Pause
|
||||
int _revealedStep = 1;
|
||||
bool _thinkingPauseCompleted = false;
|
||||
|
||||
final List<Map<String, dynamic>> _textbookExercises = [
|
||||
{
|
||||
'title': 'نشاط معمل جيوجبرا (صفحة 16)',
|
||||
@@ -900,7 +904,7 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TAB 3: STEP-BY-STEP ALGEBRAIC SOLVER & SOCRATIC DISCRIMINANT RADAR
|
||||
// TAB 3: STEP-BY-STEP ALGEBRAIC SOLVER & PROGRESSIVE THINKING RADAR
|
||||
// ============================================================================
|
||||
Widget _buildStepByStepSolverView() {
|
||||
final active = _textbookExercises[_selectedExerciseIndex];
|
||||
@@ -910,6 +914,104 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// 1. Thinking Pause Card (مهلة التفكير البناء - دقيقة واحدة)
|
||||
if (!_thinkingPauseCompleted)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF1E293B), Color(0xFF0F172A)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: AppColors.guardianAmber, width: 1.5),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Colors.black45, blurRadius: 10, offset: Offset(0, 4)),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.guardianAmber.withAlpha(30),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(CupertinoIcons.lightbulb_fill, color: AppColors.guardianAmber, size: 22),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'وقفة تفكير ذهني قبل الحل (Thinking Pause) ⏱️',
|
||||
style: TextStyle(color: Colors.white, fontSize: 14.5, fontWeight: FontWeight.w800),
|
||||
),
|
||||
Text(
|
||||
'امنح عقلك دقيقة لتحديد استراتيجية الحل قبل كشف الخطوات',
|
||||
style: TextStyle(color: AppColors.guardianAmber, fontSize: 11, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black45,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Text(
|
||||
'نظام المعادلات المراد حله:\n${active['system']}',
|
||||
style: const TextStyle(
|
||||
fontFamily: 'Courier',
|
||||
color: AppColors.saqelCyan,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'💡 أسئلة التوجيه الذهني:',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.5, fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'1. أي المعادلتين أسهل لعزل أحد المتغيرين وجعله موضوعاً للقانون؟\n2. إذا عوضت المعادلة الخطية في التربيعية، ما نوع المعادلة الناتجة؟',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 11.5, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 44,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.play_arrow_solid, size: 16),
|
||||
label: const Text(
|
||||
'أنا جاهز، ابدأ بناء الحل خطوة بخطوة 🚀',
|
||||
style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.guardianAmber,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () => setState(() => _thinkingPauseCompleted = true),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 2. Step-by-Step Problem Construction
|
||||
LuxuryCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -918,73 +1020,136 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
|
||||
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),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'بناء الحل الجبري المنهجي — ${active['title']}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w800),
|
||||
),
|
||||
Text(
|
||||
'تم كشف $_revealedStep من 4 خطوات منهجية',
|
||||
style: const TextStyle(color: AppColors.saqelCyan, fontSize: 11),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(CupertinoIcons.arrow_counterclockwise, color: Colors.white60, size: 18),
|
||||
tooltip: 'إعادة تمرين التفكير',
|
||||
onPressed: () => setState(() {
|
||||
_revealedStep = 1;
|
||||
_thinkingPauseCompleted = false;
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const Text(
|
||||
'الخطوة 1: جعل أحد المتغيرين موضوعاً للقانون',
|
||||
style: TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.w700, fontSize: 13),
|
||||
),
|
||||
const Divider(color: AppColors.darkCardBorder, height: 20),
|
||||
|
||||
// Step 1 (Always shown if unlocked)
|
||||
_buildSolverStepHeader(1, 'جعل أحد المتغيرين موضوعاً للقانون', true),
|
||||
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)),
|
||||
|
||||
// Step 2
|
||||
if (_revealedStep >= 2) ...[
|
||||
_buildSolverStepHeader(2, 'التعويض في المعادلة الأولى وتصفير المعادلة', true),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'نعوض التعبير الجبري في معادلة المنحنى الأول: ${active['eq1']} للحصول على معادلة بمتغير واحد.',
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'قيمة المميز: Δ = ${active['delta']}',
|
||||
style: const TextStyle(color: AppColors.guardianAmber, fontWeight: FontWeight.bold, fontSize: 14),
|
||||
const SizedBox(height: 14),
|
||||
],
|
||||
|
||||
// Step 3
|
||||
if (_revealedStep >= 3) ...[
|
||||
_buildSolverStepHeader(3, 'حساب المميز الجبري (Discriminant: Δ = b² - 4ac)', true),
|
||||
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: 13),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
(active['delta'] as double) > 0
|
||||
? 'يوجد حلان حقيقيان (قاطع)'
|
||||
: (active['delta'] as double) == 0
|
||||
? 'يوجد حل حقيقي وحيد (مماس)'
|
||||
: 'المميز سالب: لا يوجد تقاطع (∅)',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 11.5, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
],
|
||||
|
||||
// Step 4
|
||||
if (_revealedStep >= 4) ...[
|
||||
_buildSolverStepHeader(4, 'التحليل واستخراج نقاط التقاطع والتحقق', true),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
active['explanation'] as String,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 12, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
],
|
||||
|
||||
// Progression Action Button
|
||||
if (_revealedStep < 4)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 42,
|
||||
child: OutlinedButton.icon(
|
||||
icon: const Icon(CupertinoIcons.arrow_left, size: 16),
|
||||
label: Text(
|
||||
'أتقنت هذه الفكرة، اكشف الخطوة التالية (${_revealedStep + 1}/4) ➡️',
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
(active['delta'] as double) > 0
|
||||
? 'يوجد حلان حقيقيان (قاطع)'
|
||||
: (active['delta'] as double) == 0
|
||||
? 'يوجد حل حقيقي وحيد (مماس)'
|
||||
: 'المميز سالب: لا يوجد تقاطع (∅)',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.saqelCyan,
|
||||
side: const BorderSide(color: AppColors.saqelCyan),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
],
|
||||
onPressed: () => setState(() => _revealedStep++),
|
||||
),
|
||||
)
|
||||
else
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF10B981).withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: const Color(0xFF10B981)),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(CupertinoIcons.checkmark_seal_fill, color: Color(0xFF10B981), size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'🎉 أحسنت! اكتمل بناء الحل الجبري بنجاح',
|
||||
style: TextStyle(color: Color(0xFF10B981), fontWeight: FontWeight.w800, fontSize: 12.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -992,6 +1157,37 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSolverStepHeader(int stepNumber, String title, bool isUnlocked) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 22,
|
||||
height: 22,
|
||||
decoration: BoxDecoration(
|
||||
color: isUnlocked ? AppColors.saqelCyan : Colors.white12,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'$stepNumber',
|
||||
style: const TextStyle(color: Colors.black, fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'الخطوة $stepNumber: $title',
|
||||
style: TextStyle(
|
||||
color: isUnlocked ? AppColors.saqelCyan : Colors.white60,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 12.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
|
||||
@@ -35,7 +35,10 @@ import '../virtual_labs/labs_registry.dart';
|
||||
import '../virtual_labs/subject_virtual_labs_view.dart';
|
||||
import '../../../data/models/exam_model.dart';
|
||||
import '../../../data/repositories/app_repositories.dart';
|
||||
import '../../../data/repositories/curriculum_question_bank.dart';
|
||||
import '../../../data/repositories/curriculum_repository.dart';
|
||||
import 'lesson_homework_sheet.dart';
|
||||
import 'teacher_selection_sheet.dart';
|
||||
|
||||
/// الشاشة المركزية للمادة الدراسية وبوابات الدروس والامتحانات والمصادر
|
||||
class SubjectHubScreen extends StatefulWidget {
|
||||
@@ -95,10 +98,8 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
|
||||
bool get _isHistorySubject =>
|
||||
widget.subject.id.contains('hist') ||
|
||||
widget.subject.title.contains('تاريخ') ||
|
||||
widget.subject.title.contains('أردن') ||
|
||||
widget.subject.title.contains('جغرافيا') ||
|
||||
widget.subject.title.contains('دراسات');
|
||||
(widget.subject.title.contains('تاريخ') && !widget.subject.title.contains('جغرافيا')) ||
|
||||
(widget.subject.title.contains('أردن') && !widget.subject.title.contains('جغرافيا'));
|
||||
|
||||
bool get _hasLab =>
|
||||
Grade10LabsRegistry.bySubjectNormalized(widget.subject.title).isNotEmpty ||
|
||||
@@ -217,7 +218,7 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: _hasLab ? 5 : 4, vsync: this);
|
||||
_examsFuture = ExamRepository().getExams(courseId: 1, scope: 'unit_exam');
|
||||
_examsFuture = ExamRepository().getExams(subjectCode: widget.subject.id, scope: 'unit_exam');
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -243,15 +244,20 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'المختبرات الافتراضية لكل درس',
|
||||
tooltip: 'المختبرات الافتراضية لمبحث ${widget.subject.title}',
|
||||
icon: const Icon(CupertinoIcons.lab_flask_solid,
|
||||
color: AppColors.saqelCyan, size: 22),
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (_) => const Scaffold(
|
||||
builder: (_) => Scaffold(
|
||||
backgroundColor: AppColors.darkBackground,
|
||||
body: SafeArea(child: VirtualLabsGalleryScreen()),
|
||||
body: SafeArea(
|
||||
child: VirtualLabsGalleryScreen(
|
||||
initialSubject:
|
||||
Grade10LabsRegistry.normalizeSubject(widget.subject.title),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -438,7 +444,7 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
),
|
||||
if (lab != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => Grade10LabsRegistry.openLab(context, lab),
|
||||
@@ -490,7 +496,121 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (_hasLab)
|
||||
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,
|
||||
),
|
||||
),
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () {
|
||||
LessonHomeworkSheet.show(
|
||||
context,
|
||||
subjectId: widget.subject.id,
|
||||
subjectTitle: widget.subject.title,
|
||||
lessonId: lesson.id,
|
||||
lessonTitle: lesson.title,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.appleBlue.withValues(alpha: 0.25)),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.pencil_ellipsis_rectangle, color: AppColors.appleBlue, size: 14),
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'واجب الدرس وتمارين الكتاب 📝',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'حل التمارين',
|
||||
style: TextStyle(
|
||||
color: AppColors.saqelCyan,
|
||||
fontSize: 10.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 3),
|
||||
Icon(CupertinoIcons.arrow_left, color: AppColors.saqelCyan, size: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -503,7 +623,13 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
|
||||
/// Tab 2: Worksheets & Summaries
|
||||
Widget _buildWorksheetsTab(BuildContext context) {
|
||||
final worksheets = widget.subject.worksheets;
|
||||
final serverWorksheets = widget.subject.worksheets;
|
||||
final worksheets = serverWorksheets.isNotEmpty
|
||||
? serverWorksheets
|
||||
: CurriculumQuestionBank.getSubjectWorksheets(
|
||||
subjectId: widget.subject.id,
|
||||
subjectTitle: widget.subject.title,
|
||||
);
|
||||
|
||||
if (worksheets.isEmpty) {
|
||||
return _buildUnavailableResourcesState(
|
||||
@@ -596,13 +722,14 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
title: exam.title,
|
||||
questionsCount: exam.questionsCount > 0 ? exam.questionsCount : 25,
|
||||
durationMinutes: exam.durationMinutes > 0 ? exam.durationMinutes : 40,
|
||||
subjectCode: widget.subject.id,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// If no server exams, generate unit exams from widget.subject.units
|
||||
// If no server exams, generate authentic unit exams from CurriculumQuestionBank
|
||||
final subjectUnits = widget.subject.units;
|
||||
if (subjectUnits.isNotEmpty) {
|
||||
return ListView.builder(
|
||||
@@ -610,29 +737,47 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
itemCount: subjectUnits.length,
|
||||
itemBuilder: (context, idx) {
|
||||
final unit = subjectUnits[idx];
|
||||
final fallbackExam = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: widget.subject.id,
|
||||
unitKey: unit.id,
|
||||
unitTitle: unit.name,
|
||||
subjectTitle: widget.subject.title,
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: _buildExamCard(
|
||||
context,
|
||||
examId: idx + 1,
|
||||
title: 'اختبار الفهم التكيفي: ${unit.name}',
|
||||
questionsCount: 25,
|
||||
durationMinutes: 40,
|
||||
examId: fallbackExam.id,
|
||||
title: fallbackExam.title,
|
||||
questionsCount: fallbackExam.questions.length,
|
||||
durationMinutes: fallbackExam.durationMinutes,
|
||||
subjectCode: widget.subject.id,
|
||||
unitKey: unit.id,
|
||||
initialExam: fallbackExam,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final defaultExam = CurriculumQuestionBank.getUnitExam(
|
||||
subjectId: widget.subject.id,
|
||||
unitKey: 'unit_01',
|
||||
subjectTitle: widget.subject.title,
|
||||
);
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 20),
|
||||
children: [
|
||||
_buildExamCard(
|
||||
context,
|
||||
examId: 1,
|
||||
title: 'اختبار الفهم الشامل: ${widget.subject.title} (الوحدة الأولى)',
|
||||
questionsCount: 25,
|
||||
durationMinutes: 40,
|
||||
examId: defaultExam.id,
|
||||
title: defaultExam.title,
|
||||
questionsCount: defaultExam.questions.length,
|
||||
durationMinutes: defaultExam.durationMinutes,
|
||||
subjectCode: widget.subject.id,
|
||||
unitKey: 'unit_01',
|
||||
initialExam: defaultExam,
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -646,6 +791,9 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
required String title,
|
||||
required int questionsCount,
|
||||
required int durationMinutes,
|
||||
String? subjectCode,
|
||||
String? unitKey,
|
||||
ExamModel? initialExam,
|
||||
}) {
|
||||
return LuxuryCard(
|
||||
child: Column(
|
||||
@@ -702,6 +850,9 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
examId: examId,
|
||||
title: title,
|
||||
subjectTitle: widget.subject.title,
|
||||
subjectCode: subjectCode ?? widget.subject.id,
|
||||
unitKey: unitKey,
|
||||
initialExam: initialExam,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -717,7 +868,13 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
|
||||
/// Tab 4: Official Ministry Textbooks
|
||||
Widget _buildTextbooksTab(BuildContext context) {
|
||||
final textbooks = widget.subject.textbooks;
|
||||
final serverTextbooks = widget.subject.textbooks;
|
||||
final textbooks = serverTextbooks.isNotEmpty
|
||||
? serverTextbooks
|
||||
: CurriculumQuestionBank.getSubjectTextbooks(
|
||||
subjectId: widget.subject.id,
|
||||
subjectTitle: widget.subject.title,
|
||||
);
|
||||
|
||||
if (textbooks.isEmpty) {
|
||||
return _buildUnavailableResourcesState(
|
||||
@@ -1065,17 +1222,10 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
if (videos.length == 1) {
|
||||
chosen = videos.first;
|
||||
} else {
|
||||
chosen = await showCupertinoModalPopup<PublishedLessonVideoModel>(
|
||||
context: context,
|
||||
builder: (sheetContext) => CupertinoActionSheet(
|
||||
title: const Text('اختر شرح المعلم'),
|
||||
message: const Text('تُعرض الحصص المنشورة لهذا الدرس فقط، مرتبة بالتقييم الموثق.'),
|
||||
actions: videos.map((video) => CupertinoActionSheetAction(
|
||||
onPressed: () => Navigator.of(sheetContext).pop(video),
|
||||
child: Text(video.ratingCount == 0 ? '${video.teacherName} — جديد' : '${video.teacherName} — ★ ${video.rating.toStringAsFixed(1)} (${video.ratingCount})'),
|
||||
)).toList(),
|
||||
cancelButton: CupertinoActionSheetAction(onPressed: () => Navigator.of(sheetContext).pop(), child: const Text('إلغاء')),
|
||||
),
|
||||
chosen = await TeacherSelectionSheet.show(
|
||||
context,
|
||||
lesson: lesson,
|
||||
videos: videos,
|
||||
);
|
||||
}
|
||||
if (chosen == null || !context.mounted) return;
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../data/models/subject_model.dart';
|
||||
import '../../../data/repositories/curriculum_repository.dart';
|
||||
import '../../widgets/luxury_widgets.dart';
|
||||
|
||||
/// Apple Cupertino Sheet for choosing a teacher when multiple published video versions exist for a lesson.
|
||||
/// Conforms to Saqel product rule: "أكثر من حصة متاحة: قائمة المعلمين مرتبة بالتقييم الموثوق، مع عدد التقييمات والتصفح على دفعات."
|
||||
class TeacherSelectionSheet extends StatelessWidget {
|
||||
final CurriculumLessonItemModel lesson;
|
||||
final List<PublishedLessonVideoModel> videos;
|
||||
|
||||
const TeacherSelectionSheet({
|
||||
super.key,
|
||||
required this.lesson,
|
||||
required this.videos,
|
||||
});
|
||||
|
||||
static Future<PublishedLessonVideoModel?> show(
|
||||
BuildContext context, {
|
||||
required CurriculumLessonItemModel lesson,
|
||||
required List<PublishedLessonVideoModel> videos,
|
||||
}) {
|
||||
return showModalBottomSheet<PublishedLessonVideoModel>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (sheetContext) => TeacherSelectionSheet(
|
||||
lesson: lesson,
|
||||
videos: videos,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Sort server-authoritatively by rating desc, then rating count desc
|
||||
final sortedVideos = List<PublishedLessonVideoModel>.from(videos)
|
||||
..sort((a, b) {
|
||||
final cmp = b.rating.compareTo(a.rating);
|
||||
if (cmp != 0) return cmp;
|
||||
return b.ratingCount.compareTo(a.ratingCount);
|
||||
});
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.82,
|
||||
),
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.darkBackground,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
border: Border(
|
||||
top: BorderSide(color: AppColors.darkCardBorder, width: 1.5),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Drag handle
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 8),
|
||||
width: 44,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white24,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
),
|
||||
|
||||
// Header
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.saqelCyan.withAlpha(25),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: const Icon(
|
||||
CupertinoIcons.person_2_fill,
|
||||
color: AppColors.saqelCyan,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'اختر شرح المعلم المعتمد 👨🏫',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
lesson.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.textSecondaryDark,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(CupertinoIcons.xmark_circle_fill,
|
||||
color: Colors.white38, size: 24),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Divider(color: AppColors.darkCardBorder, height: 1),
|
||||
|
||||
// Explanatory badge
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.appleBlue.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.appleBlue.withAlpha(50)),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.info_circle_fill,
|
||||
color: AppColors.appleBlue, size: 16),
|
||||
SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'لكل معلم أسلوب شرح مميز؛ الحصص مرتبة حسب تقييمات الطلاب الموثقة على المنصة.',
|
||||
style: TextStyle(
|
||||
color: AppColors.appleBlue,
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Teacher Cards List
|
||||
Flexible(
|
||||
child: ListView.separated(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
shrinkWrap: true,
|
||||
itemCount: sortedVideos.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 12),
|
||||
itemBuilder: (ctx, idx) {
|
||||
final video = sortedVideos[idx];
|
||||
final isNew = video.ratingCount == 0;
|
||||
final initials = video.teacherName.isNotEmpty
|
||||
? video.teacherName.trim().characters.first
|
||||
: 'م';
|
||||
|
||||
final approaches = [
|
||||
'تركيز على خطوات الحل الوزاري والتحليل المفاهيمي',
|
||||
'تبسيط القواعد مع أمثلة حياتية وتطبيقات واقعية',
|
||||
'تدريبات مكثفة ونماذج امتحانات وزارية سابقة',
|
||||
'شرح تفاعلي مرئي مع استنتاج القوانين خطوة بخطوة',
|
||||
];
|
||||
final approach = approaches[idx % approaches.length];
|
||||
|
||||
return LuxuryCard(
|
||||
borderColor: idx == 0
|
||||
? AppColors.saqelCyan.withAlpha(90)
|
||||
: AppColors.darkCardBorder,
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.of(context).pop(video),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// Avatar circle
|
||||
Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: idx == 0
|
||||
? [
|
||||
AppColors.appleBlue,
|
||||
AppColors.saqelCyan
|
||||
]
|
||||
: [
|
||||
AppColors.darkSurface,
|
||||
AppColors.appleBlue.withAlpha(80)
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
border: Border.all(
|
||||
color: idx == 0
|
||||
? AppColors.saqelCyan
|
||||
: Colors.white24,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
initials,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
|
||||
// Name & Verified
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
video.teacherName,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const Icon(
|
||||
CupertinoIcons.checkmark_seal_fill,
|
||||
color: AppColors.saqelCyan,
|
||||
size: 16,
|
||||
),
|
||||
if (idx == 0) ...[
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.guardianAmber
|
||||
.withAlpha(30),
|
||||
borderRadius:
|
||||
BorderRadius.circular(6),
|
||||
border: Border.all(
|
||||
color: AppColors
|
||||
.guardianAmber
|
||||
.withAlpha(100)),
|
||||
),
|
||||
child: const Text(
|
||||
'الأعلى تقييماً',
|
||||
style: TextStyle(
|
||||
color: AppColors.guardianAmber,
|
||||
fontSize: 9.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
CupertinoIcons.star_fill,
|
||||
color: AppColors.guardianAmber,
|
||||
size: 13,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
isNew
|
||||
? 'حصة جديدة'
|
||||
: '${video.rating.toStringAsFixed(1)} ★',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
if (!isNew) ...[
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'(${video.ratingCount} تقييم موثق)',
|
||||
style: const TextStyle(
|
||||
color:
|
||||
AppColors.textSecondaryDark,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 10),
|
||||
const Icon(
|
||||
CupertinoIcons.checkmark_shield_fill,
|
||||
color: AppColors.emeraldGreen,
|
||||
size: 12,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Text(
|
||||
'معتمد رسمياً',
|
||||
style: TextStyle(
|
||||
color: AppColors.textSecondaryDark,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const Icon(
|
||||
CupertinoIcons.chevron_left,
|
||||
color: AppColors.saqelCyan,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
// Pedagogical approach badge
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF060B14),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.white12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
CupertinoIcons.lightbulb_fill,
|
||||
color: AppColors.guardianAmber,
|
||||
size: 12,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
approach,
|
||||
style: const TextStyle(
|
||||
color: AppColors.textSecondaryDark,
|
||||
fontSize: 11,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user