Harden published curriculum and student flows

This commit is contained in:
Hamza-Ayed
2026-09-30 08:22:17 +03:00
parent e8163fe265
commit ce7b0fcf14
53 changed files with 4723 additions and 3317 deletions
@@ -27,6 +27,7 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
final ErrorNotebookRepository _repository = ErrorNotebookRepository();
bool _isLoading = true;
bool _loadFailed = false;
ErrorNotebookSummary? _summary;
List<ErrorNotebookItem> _items = [];
@@ -42,15 +43,27 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
Future<void> _loadNotebookData() async {
setState(() => _isLoading = true);
final data = (widget.isGuardianMode && widget.studentId != null)
? await _repository.getChildErrorNotebook(widget.studentId!)
: await _repository.getErrorNotebook();
if (mounted) {
setState(() {
_summary = data['summary'] as ErrorNotebookSummary?;
_items = (data['items'] as List<ErrorNotebookItem>?) ?? [];
_isLoading = false;
});
try {
final data = (widget.isGuardianMode && widget.studentId != null)
? await _repository.getChildErrorNotebook(widget.studentId!)
: await _repository.getErrorNotebook();
if (mounted) {
setState(() {
_summary = data['summary'] as ErrorNotebookSummary?;
_items = (data['items'] as List<ErrorNotebookItem>?) ?? [];
_loadFailed = false;
_isLoading = false;
});
}
} catch (_) {
if (mounted) {
setState(() {
_summary = null;
_items = [];
_loadFailed = true;
_isLoading = false;
});
}
}
}
@@ -146,6 +159,8 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
? const Center(
child: CircularProgressIndicator(color: Color(0xFF38BDF8)),
)
: _loadFailed
? Center(child: TextButton(onPressed: _loadNotebookData, child: const Text('تعذر تحميل دفتر الأخطاء. اضغط لإعادة المحاولة.')))
: RefreshIndicator(
onRefresh: _loadNotebookData,
color: const Color(0xFF38BDF8),
@@ -647,7 +662,9 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
),
)
: ElevatedButton.icon(
onPressed: () => _startRemediation(item),
onPressed: item.isInRemediation && !item.retentionIsDue
? null
: () => _startRemediation(item),
icon: Icon(
item.isInRemediation
? CupertinoIcons.arrow_2_circlepath_circle_fill
@@ -656,7 +673,9 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
),
label: Text(
item.isInRemediation
? 'بدء اختبار الاسترجاع والتثبيت (تكرار متباعد 7 أيام) 🎯'
? (item.retentionIsDue
? 'بدء اختبار الاسترجاع بعد 7 أيام 🎯'
: 'اختبار الاسترجاع متاح بعد موعد المراجعة')
: 'بدء المسار العلاجي لسد هذه الفجوة (3 أسئلة تفريدية) 🚀',
style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800),
),
@@ -723,11 +742,14 @@ class _RemediationModalSheet extends StatefulWidget {
class _RemediationModalSheetState extends State<_RemediationModalSheet> {
bool _isLoadingQuiz = true;
List<RemedialQuestion> _questions = [];
String _attemptUuid = '';
final List<int> _answers = [];
int _currentQuestionIndex = 0;
int? _selectedOptionIndex;
bool _hasAnsweredCurrent = false;
int _correctAnswersCount = 0;
bool _isCompleted = false;
bool _passed = false;
bool _isSubmitting = false;
@override
void initState() {
@@ -736,44 +758,43 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
}
Future<void> _fetchQuiz() async {
final qs = await widget.repository.getRemediationQuiz(
final result = await widget.repository.getRemediationQuiz(
errorUuid: widget.errorItem.uuid,
topicName: widget.errorItem.topicName,
);
if (mounted) {
setState(() {
_questions = qs;
_questions = result['questions'] as List<RemedialQuestion>;
_attemptUuid = result['attempt_uuid'] as String;
_isLoadingQuiz = false;
});
}
}
void _submitAnswer() {
if (_selectedOptionIndex == null || _hasAnsweredCurrent) return;
final q = _questions[_currentQuestionIndex];
final isCorrect = _selectedOptionIndex == q.correctIndex;
setState(() {
_hasAnsweredCurrent = true;
if (isCorrect) {
_correctAnswersCount++;
}
});
}
void _nextQuestion() async {
Future<void> _nextQuestion() async {
if (_selectedOptionIndex == null || _isSubmitting) return;
_answers.add(_questions[_currentQuestionIndex].options[_selectedOptionIndex!].id);
if (_currentQuestionIndex < _questions.length - 1) {
setState(() {
_currentQuestionIndex++;
_selectedOptionIndex = null;
_hasAnsweredCurrent = false;
});
} else {
// Completed drill
setState(() => _isCompleted = true);
if (_correctAnswersCount >= 2) {
await widget.repository.resolveError(widget.errorItem.uuid);
setState(() => _isSubmitting = true);
final result = await widget.repository.resolveError(widget.errorItem.uuid, _attemptUuid, _answers);
if (!mounted) return;
if (result == null) {
_answers.removeLast();
setState(() => _isSubmitting = false);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('تعذر اعتماد المحاولة. حاول مرة أخرى.')));
return;
}
setState(() {
_correctAnswersCount = (result['correct_count'] as num?)?.toInt() ?? 0;
_passed = result['passed'] == true;
_isSubmitting = false;
_isCompleted = true;
});
if (_passed) {
widget.onRemediationCompleted();
}
}
@@ -795,6 +816,8 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
? const Center(
child: CircularProgressIndicator(color: Color(0xFF38BDF8)),
)
: _questions.isEmpty
? const Center(child: Text('لا تتوفر أسئلة مراجعة معتمدة لهذا المفهوم حالياً.', style: TextStyle(color: Colors.white70)))
: _isCompleted
? _buildCompletionState()
: _buildQuestionState(),
@@ -856,30 +879,19 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
child: ListView.builder(
itemCount: q.options.length,
itemBuilder: (ctx, idx) {
final optText = q.options[idx];
final optText = q.options[idx].text;
final isSel = _selectedOptionIndex == idx;
final isCorrectOpt = idx == q.correctIndex;
Color borderCol = const Color(0xFF1E293B);
Color bgCol = const Color(0xFF161F30);
if (_hasAnsweredCurrent) {
if (isCorrectOpt) {
borderCol = const Color(0xFF10B981);
bgCol = const Color(0xFF064E3B);
} else if (isSel) {
borderCol = const Color(0xFFEF4444);
bgCol = const Color(0xFF7F1D1D);
}
} else if (isSel) {
if (isSel) {
borderCol = const Color(0xFF38BDF8);
bgCol = const Color(0xFF0369A1);
}
return GestureDetector(
onTap: _hasAnsweredCurrent
? null
: () => setState(() => _selectedOptionIndex = idx),
onTap: () => setState(() => _selectedOptionIndex = idx),
child: Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
@@ -926,34 +938,11 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
),
),
// Explanation if answered
if (_hasAnsweredCurrent)
Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(10),
),
child: Text(
q.explanation,
style: const TextStyle(
fontSize: 12,
color: Color(0xFFCBD5E1),
height: 1.4,
),
),
),
// Action Button
ElevatedButton(
onPressed: !_hasAnsweredCurrent
? (_selectedOptionIndex != null ? _submitAnswer : null)
: _nextQuestion,
onPressed: _selectedOptionIndex != null && !_isSubmitting ? _nextQuestion : null,
style: ElevatedButton.styleFrom(
backgroundColor: _hasAnsweredCurrent
? const Color(0xFF10B981)
: const Color(0xFF38BDF8),
backgroundColor: const Color(0xFF38BDF8),
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
@@ -961,11 +950,8 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
),
),
child: Text(
!_hasAnsweredCurrent
? 'تأكيد الإجابة'
: (_currentQuestionIndex < _questions.length - 1
? 'السؤال التالي ➔'
: 'إنهاء المسار العلاجي 🏆'),
_isSubmitting ? 'جارٍ تصحيح الإجابات...' : (_currentQuestionIndex < _questions.length - 1
? 'السؤال التالي ➔' : 'إنهاء وتصحيح المحاولة'),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w800),
),
),
@@ -974,7 +960,7 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
}
Widget _buildCompletionState() {
final passed = _correctAnswersCount >= 2;
final passed = _passed;
return Center(
child: Column(
@@ -1005,7 +991,7 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
Text(
passed
? (widget.errorItem.isInRemediation
? 'أجبت عن $_correctAnswersCount من ${_questions.length} أسئلة بشكل سليم. تم تثبيت المفهوم في الذاكرة طويلة المدى وترحيله إلى المتقنات 🏆.'
? 'أجبت عن $_correctAnswersCount من ${_questions.length} أسئلة بشكل سليم في اختبار الاسترجاع بعد أسبوع.'
: 'أجبت عن $_correctAnswersCount من ${_questions.length} أسئلة. دخل المفهوم مرحلة التثبيت والتكرار المتباعد (المراجعة بعد 7 أيام لتأكيد الإتقان التام).')
: 'أجبت عن $_correctAnswersCount من ${_questions.length}. حاول مجدداً لضمان إتقان السؤال في الامتحان الوزاري.',
style: const TextStyle(fontSize: 13, color: Color(0xFF94A3B8)),