Update Saqel Platform: 2026-09-20 16:38:51

This commit is contained in:
Hamza-Ayed
2026-09-20 16:38:51 +03:00
parent 5805413f51
commit e8163fe265
11 changed files with 1140 additions and 3002 deletions
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
class ErrorNotebookSummary {
final int totalErrors;
final int masteredCount;
final int inRemediationCount;
final int pendingCount;
final double masteryPercentage;
final Map<String, int> bySubject;
@@ -14,6 +15,7 @@ class ErrorNotebookSummary {
ErrorNotebookSummary({
required this.totalErrors,
required this.masteredCount,
this.inRemediationCount = 0,
required this.pendingCount,
required this.masteryPercentage,
required this.bySubject,
@@ -31,6 +33,7 @@ class ErrorNotebookSummary {
return ErrorNotebookSummary(
totalErrors: (json['total_errors'] as num?)?.toInt() ?? 0,
masteredCount: (json['mastered_count'] as num?)?.toInt() ?? 0,
inRemediationCount: (json['in_remediation_count'] as num?)?.toInt() ?? 0,
pendingCount: (json['pending_count'] as num?)?.toInt() ?? 0,
masteryPercentage: (json['mastery_percentage'] as num?)?.toDouble() ?? 0.0,
bySubject: bySub,
@@ -74,6 +77,8 @@ class ErrorNotebookItem {
});
bool get isMastered => status == 'mastered';
bool get isInRemediation => status == 'in_remediation';
bool get isPending => status == 'pending_remediation';
String get categoryArabicName {
switch (errorCategory) {
@@ -76,33 +76,7 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
errorItem: errorItem,
repository: _repository,
onRemediationCompleted: () {
setState(() {
// Optimistically update local item
final idx = _items.indexWhere((it) => it.uuid == errorItem.uuid);
if (idx != -1) {
_items[idx] = _items[idx].copyWith(
status: 'mastered',
masteredAt: 'الآن',
remediationAttemptsCount: _items[idx].remediationAttemptsCount + 1,
);
}
// Update summary
if (_summary != null) {
final newMastered = _summary!.masteredCount + 1;
final newPending = (_summary!.pendingCount - 1).clamp(0, 999);
final newRate = _summary!.totalErrors > 0
? (newMastered / _summary!.totalErrors) * 100
: 100.0;
_summary = ErrorNotebookSummary(
totalErrors: _summary!.totalErrors,
masteredCount: newMastered,
pendingCount: newPending,
masteryPercentage: newRate,
bySubject: _summary!.bySubject,
);
}
});
_loadNotebookData();
},
),
);
@@ -286,14 +260,21 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
color: const Color(0xFF94A3B8),
icon: CupertinoIcons.scope,
),
const SizedBox(width: 8),
const SizedBox(width: 6),
_metricBox(
title: 'بحاجة إلى علاج',
title: 'بحاجة لعلاج',
value: '${summary.pendingCount}',
color: const Color(0xFFEF4444),
icon: CupertinoIcons.exclamationmark_triangle_fill,
),
const SizedBox(width: 8),
const SizedBox(width: 6),
_metricBox(
title: 'قيد التثبيت 🌱',
value: '${summary.inRemediationCount}',
color: const Color(0xFFF59E0B),
icon: CupertinoIcons.arrow_2_circlepath_circle_fill,
),
const SizedBox(width: 6),
_metricBox(
title: 'تم الإتقان 🏆',
value: '${summary.masteredCount}',
@@ -315,7 +296,7 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
}) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
decoration: BoxDecoration(
color: const Color(0xFF0B111E),
borderRadius: BorderRadius.circular(10),
@@ -323,12 +304,12 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
),
child: Column(
children: [
Icon(icon, size: 16, color: color),
Icon(icon, size: 15, color: color),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontSize: 15,
fontWeight: FontWeight.w900,
color: color,
),
@@ -336,8 +317,10 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
const SizedBox(height: 2),
Text(
title,
style: const TextStyle(fontSize: 10.5, color: Color(0xFF64748B)),
style: const TextStyle(fontSize: 10, color: Color(0xFF64748B)),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
@@ -346,20 +329,28 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
}
Widget _buildStatusTabs() {
return Row(
children: [
_statusFilterButton('all', 'جميع الأخطاء (${_items.length})'),
const SizedBox(width: 8),
_statusFilterButton(
'pending_remediation',
'بحاجة لعلاج (${_items.where((e) => !e.isMastered).length})',
),
const SizedBox(width: 8),
_statusFilterButton(
'mastered',
'تم إتقانها (${_items.where((e) => e.isMastered).length})',
),
],
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_statusFilterButton('all', 'جميع الفجوات (${_items.length})'),
const SizedBox(width: 8),
_statusFilterButton(
'pending_remediation',
'بحاجة لعلاج (${_items.where((e) => e.isPending).length})',
),
const SizedBox(width: 8),
_statusFilterButton(
'in_remediation',
'قيد التثبيت 🌱 (${_items.where((e) => e.isInRemediation).length})',
),
const SizedBox(width: 8),
_statusFilterButton(
'mastered',
'تم إتقانها 🏆 (${_items.where((e) => e.isMastered).length})',
),
],
),
);
}
@@ -474,8 +465,11 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: item.isMastered
? const Color(0xFF10B981).withOpacity(0.3)
: const Color(0xFFEF4444).withOpacity(0.3),
? const Color(0xFF10B981).withOpacity(0.35)
: (item.isInRemediation
? const Color(0xFFF59E0B).withOpacity(0.4)
: const Color(0xFFEF4444).withOpacity(0.3)),
width: item.isInRemediation ? 1.5 : 1.0,
),
),
child: Column(
@@ -654,13 +648,22 @@ class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
)
: ElevatedButton.icon(
onPressed: () => _startRemediation(item),
icon: const Icon(CupertinoIcons.bolt_horizontal_circle_fill, size: 17),
label: const Text(
'بدء المسار العلاجي لسد هذه الفجوة (3 أسئلة تفريدية) 🚀',
style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800),
icon: Icon(
item.isInRemediation
? CupertinoIcons.arrow_2_circlepath_circle_fill
: CupertinoIcons.bolt_horizontal_circle_fill,
size: 17,
),
label: Text(
item.isInRemediation
? 'بدء اختبار الاسترجاع والتثبيت (تكرار متباعد 7 أيام) 🎯'
: 'بدء المسار العلاجي لسد هذه الفجوة (3 أسئلة تفريدية) 🚀',
style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800),
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0284C7),
backgroundColor: item.isInRemediation
? const Color(0xFFD97706)
: const Color(0xFF0284C7),
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 42),
shape: RoundedRectangleBorder(
@@ -987,7 +990,9 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
const SizedBox(height: 16),
Text(
passed
? 'مبارك! تم إتقان المهارة وسد الفجوة بنجاح 🎉'
? (widget.errorItem.isInRemediation
? 'مبارك! تم اجتياز اختبار التثبيت بنجاح 🎉'
: 'تم إنجاز الكويز العلاجي الأولي بنجاح 🌱')
: 'أحسنت المحاولة، راجع الشرح وأعد الاختبار',
style: const TextStyle(
fontSize: 17,
@@ -999,7 +1004,9 @@ class _RemediationModalSheetState extends State<_RemediationModalSheet> {
const SizedBox(height: 8),
Text(
passed
? 'أجبت عن $_correctAnswersCount من ${_questions.length} أسئلة بشكل سليم. تم ترحيل الخطأ إلى أرشيف المتقنات.'
? (widget.errorItem.isInRemediation
? 'أجبت عن $_correctAnswersCount من ${_questions.length} أسئلة بشكل سليم. تم تثبيت المفهوم في الذاكرة طويلة المدى وترحيله إلى المتقنات 🏆.'
: 'أجبت عن $_correctAnswersCount من ${_questions.length} أسئلة. دخل المفهوم مرحلة التثبيت والتكرار المتباعد (المراجعة بعد 7 أيام لتأكيد الإتقان التام).')
: 'أجبت عن $_correctAnswersCount من ${_questions.length}. حاول مجدداً لضمان إتقان السؤال في الامتحان الوزاري.',
style: const TextStyle(fontSize: 13, color: Color(0xFF94A3B8)),
textAlign: TextAlign.center,
@@ -38,6 +38,8 @@ class TeacherStudioState {
final String? uploadSuccessMessage;
final double uploadProgress;
final String uploadPhase;
final bool isReplacement;
final int versionNumber;
const TeacherStudioState({
required this.lessonTitle,
@@ -62,6 +64,8 @@ class TeacherStudioState {
this.uploadSuccessMessage,
this.uploadProgress = 0.0,
this.uploadPhase = 'idle',
this.isReplacement = false,
this.versionNumber = 1,
});
TeacherStudioState copyWith({
@@ -88,6 +92,8 @@ class TeacherStudioState {
String? uploadSuccessMessage,
double? uploadProgress,
String? uploadPhase,
bool? isReplacement,
int? versionNumber,
}) {
return TeacherStudioState(
lessonTitle: lessonTitle ?? this.lessonTitle,
@@ -112,6 +118,8 @@ class TeacherStudioState {
uploadSuccessMessage: uploadSuccessMessage ?? this.uploadSuccessMessage,
uploadProgress: uploadProgress ?? this.uploadProgress,
uploadPhase: uploadPhase ?? this.uploadPhase,
isReplacement: isReplacement ?? this.isReplacement,
versionNumber: versionNumber ?? this.versionNumber,
);
}
}
@@ -359,6 +367,12 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
));
return false;
}
final isReplacement = preflight['replacement'] == true;
final versionNum = (preflight['version_number'] as num?)?.toInt() ?? 1;
emit(state.copyWith(
isReplacement: isReplacement,
versionNumber: versionNum,
));
final videoVersionId = preflight['video_version_id']?.toString();
if (videoVersionId == null || videoVersionId.isEmpty) {
throw StateError('لم ينشئ الخادم نسخة فيديو صالحة للرفع.');
@@ -553,6 +553,29 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
minHeight: 8,
),
),
if (state.isReplacement) ...[
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.amber.withOpacity(0.12),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.amber.withOpacity(0.35)),
),
child: Row(
children: [
const Icon(CupertinoIcons.arrow_2_circlepath, color: Colors.amber, size: 14),
const SizedBox(width: 6),
Expanded(
child: Text(
'نسخة مرشحة للاستبدال (إصدار ${state.versionNumber}) — الحصة المنشورة الحالية تظل متاحة للطلاب حتى اجتياز الفحص.',
style: const TextStyle(color: Colors.amber, fontSize: 11, fontWeight: FontWeight.bold),
),
),
],
),
),
],
],
),
),