Update Saqel Platform: 2026-09-04 02:57:19

This commit is contained in:
Hamza-Ayed
2026-09-04 02:57:19 +03:00
parent 1fe97bf3eb
commit 882541a56a
17 changed files with 4002 additions and 206 deletions
@@ -50,7 +50,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
'word': 'العِلْمَ',
'role': 'اسم إنَّ',
'case': 'منصوب وعلامة نصبه الفتحة الظاهرة على آخره',
'explanation': 'هو المسند إليه في الأصل، نُصب لدخول الحرف الناسخ عليه.',
'explanation':
'هو المسند إليه في الأصل، نُصب لدخول الحرف الناسخ عليه.',
'tag': 'اسم منصوب',
},
{
@@ -64,7 +65,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
'word': 'يَهْدِي',
'role': 'فعل مضارع مرفوع (وجملة فعلية في محل رفع نعت)',
'case': 'مرفوع بالضمة المقدرة على الياء للثقل، والفاعل ضمير مستتر',
'explanation': 'الجمل بعد النكرات صفات؛ فجملة (يهدي) نعت لكلمة (نور).',
'explanation':
'الجمل بعد النكرات صفات؛ فجملة (يهدي) نعت لكلمة (نور).',
'tag': 'فعل + نعت',
},
{
@@ -84,7 +86,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
],
},
{
'fullText': 'قَرَأَ الطَّالِبُ المُجْتَهِدُ كِتَابَيْنِ مُفِيدَيْنِ صَبَاحاً',
'fullText':
'قَرَأَ الطَّالِبُ المُجْتَهِدُ كِتَابَيْنِ مُفِيدَيْنِ صَبَاحاً',
'type': 'جملة فعلية تامة',
'rootNode': 'الجملة الفعلية (فعل + فاعل + مفعول به + فضلات)',
'words': [
@@ -92,35 +95,40 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
'word': 'قَرَأَ',
'role': 'فعل ماضٍ مبني',
'case': 'مبني على الفتح الظاهر على آخره',
'explanation': 'فعل ماضٍ مجرد، مبني للمعلوم، يدل على حدث في الزمن الماضي.',
'explanation':
'فعل ماضٍ مجرد، مبني للمعلوم، يدل على حدث في الزمن الماضي.',
'tag': 'فعل ماضٍ',
},
{
'word': 'الطَّالِبُ',
'role': 'فاعل مرفوع',
'case': 'مرفوع وعلامة رفعه الضمة الظاهرة',
'explanation': 'من قام بالفعل، معرف بأل، ركن أساسي في الجملة الفعلية.',
'explanation':
'من قام بالفعل، معرف بأل، ركن أساسي في الجملة الفعلية.',
'tag': 'فاعل مرفوع',
},
{
'word': 'المُجْتَهِدُ',
'role': 'نعت (صفة) للطالب',
'case': 'مرفوع وعلامة رفعه الضمة الظاهرة',
'explanation': 'طابق المنعوت في التعريف، الإفراد، التذكير، وحركة الإعراب.',
'explanation':
'طابق المنعوت في التعريف، الإفراد، التذكير، وحركة الإعراب.',
'tag': 'نعت / صفة',
},
{
'word': 'كِتَابَيْنِ',
'role': 'مفعول به منصوب',
'case': 'منصوب وعلامة نصبه الياء لأنه مثنى',
'explanation': 'وقع عليه فعل القراءة، والمثنى يُنصب بالياء وتُكسر نونه.',
'explanation':
'وقع عليه فعل القراءة، والمثنى يُنصب بالياء وتُكسر نونه.',
'tag': 'مثنى منصوب',
},
{
'word': 'مُفِيدَيْنِ',
'role': 'نعت لكتابين',
'case': 'منصوب وعلامة نصبه الياء لأنه مثنى',
'explanation': 'صفة تابعة للمفعول به في التثنية والتنكير والنصب بالياء.',
'explanation':
'صفة تابعة للمفعول به في التثنية والتنكير والنصب بالياء.',
'tag': 'نعت مثنى',
},
{
@@ -142,8 +150,10 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
final List<Map<String, dynamic>> _poetryMeters = [
{
'name': 'البحر البسيط',
'key': 'إِنَّ البَسِيطَ لَدَيْهِ يُبْسَطُ الأَمَلُ .. مُسْتَفْعِلُنْ فاعِلُنْ مُسْتَفْعِلُنْ فَعِلُ',
'verse': 'لِكُلِّ شَيْءٍ إِذَا مَا تَمَّ نُقْصَانُ .. فَلَا يُغَرَّ بِطِيبِ العَيْشِ إِنْسَانُ',
'key':
'إِنَّ البَسِيطَ لَدَيْهِ يُبْسَطُ الأَمَلُ .. مُسْتَفْعِلُنْ فاعِلُنْ مُسْتَفْعِلُنْ فَعِلُ',
'verse':
'لِكُلِّ شَيْءٍ إِذَا مَا تَمَّ نُقْصَانُ .. فَلَا يُغَرَّ بِطِيبِ العَيْشِ إِنْسَانُ',
'poet': 'أبو البقاء الرندي',
'pattern': ['مُسْتَفْعِلُنْ', 'فَاعِلُنْ', 'مُسْتَفْعِلُنْ', 'فَعِلُنْ'],
'scansion': ['//0//0', '/0//0', '//0//0', '///0'],
@@ -151,8 +161,10 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
},
{
'name': 'البحر الوافر',
'key': 'بُحُورُ الشِّعْرِ وَافِرُهَا جَمِيلُ .. مُفَاعَلَتُنْ مُفَاعَلَتُنْ فَعُولُ',
'verse': 'إِذَا غَامَرْتَ فِي شَرَفٍ مَرُومِ .. فَلَا تَقْنَعْ بِمَا دُونَ النُّجُومِ',
'key':
'بُحُورُ الشِّعْرِ وَافِرُهَا جَمِيلُ .. مُفَاعَلَتُنْ مُفَاعَلَتُنْ فَعُولُ',
'verse':
'إِذَا غَامَرْتَ فِي شَرَفٍ مَرُومِ .. فَلَا تَقْنَعْ بِمَا دُونَ النُّجُومِ',
'poet': 'المتنبي',
'pattern': ['مُفَاعَلَتُنْ', 'مُفَاعَلَتُنْ', 'فَعُولُنْ'],
'scansion': ['//0///0', '//0///0', '//0/0'],
@@ -160,8 +172,10 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
},
{
'name': 'البحر الكامل',
'key': 'كَمُلَ الجَمَالُ مِنَ البُحُورِ الكَامِلُ .. مُتَفَاعِلُنْ مُتَفَاعِلُنْ مُتَفَاعِلُ',
'verse': 'وَإِذَا صَحَوْتُ فَمَا أُقَصِّرُ عَنْ نَدَى .. وَكَمَا عَلِمْتِ شَمَائِلِي وَتَكَرُّمِي',
'key':
'كَمُلَ الجَمَالُ مِنَ البُحُورِ الكَامِلُ .. مُتَفَاعِلُنْ مُتَفَاعِلُنْ مُتَفَاعِلُ',
'verse':
'وَإِذَا صَحَوْتُ فَمَا أُقَصِّرُ عَنْ نَدَى .. وَكَمَا عَلِمْتِ شَمَائِلِي وَتَكَرُّمِي',
'poet': 'عنترة بن شداد',
'pattern': ['مُتَفَاعِلُنْ', 'مُتَفَاعِلُنْ', 'مُتَفَاعِلُنْ'],
'scansion': ['///0//0', '///0//0', '///0//0'],
@@ -241,7 +255,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF10B981) : const Color(0xFF1E293B),
color:
isSelected ? const Color(0xFF10B981) : const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(10),
),
child: Row(
@@ -293,14 +308,15 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
color: Colors.white),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF10B981).withOpacity(0.15),
borderRadius: BorderRadius.circular(6),
),
child: Text(
widget.customSentences != null ? 'أمثلة الكتاب + ذكاء اصطناعي' : 'وزاري · التوجيهي 2008',
widget.customSentences != null
? 'أمثلة الكتاب + ذكاء اصطناعي'
: 'وزاري · التوجيهي 2008',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
@@ -347,10 +363,9 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
),
),
),
),
);
}),
),
);
}),
),
),
const SizedBox(height: 16),
@@ -555,11 +570,13 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
const SizedBox(height: 14),
// Role
_infoRow('الموقع الإعرابي', wordData['role'], const Color(0xFF38BDF8)),
_infoRow(
'الموقع الإعرابي', wordData['role'], const Color(0xFF38BDF8)),
const SizedBox(height: 8),
// Case & Sign
_infoRow('الحالة والعلامة', wordData['case'], const Color(0xFFFBBF24)),
_infoRow(
'الحالة والعلامة', wordData['case'], const Color(0xFFFBBF24)),
const SizedBox(height: 8),
// Pedagogical Socratic explanation
@@ -619,7 +636,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
_isPlayingBeat = false;
}),
child: Container(
margin: EdgeInsets.only(left: idx < _poetryMeters.length - 1 ? 8 : 0),
margin: EdgeInsets.only(
left: idx < _poetryMeters.length - 1 ? 8 : 0),
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isCur
@@ -714,7 +732,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
final scan = scansions[idx];
return Expanded(
child: Container(
margin: EdgeInsets.only(left: idx < patterns.length - 1 ? 8 : 0),
margin:
EdgeInsets.only(left: idx < patterns.length - 1 ? 8 : 0),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: const LinearGradient(
@@ -778,7 +797,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
_isPlayingBeat
? 'إيقاف الإيقاع العروضي التفاعلي'
: 'عزف الإيقاع العروضي الموزون (${meter['speedBpm']} نقرة/دقيقة) 🥁',
style: const TextStyle(fontSize: 13.5, fontWeight: FontWeight.w700),
style:
const TextStyle(fontSize: 13.5, fontWeight: FontWeight.w700),
),
style: ElevatedButton.styleFrom(
backgroundColor: _isPlayingBeat
@@ -11,6 +11,7 @@ import '../../../logic/cubits/auth_cubit.dart';
import '../../../logic/cubits/dashboard_cubits.dart';
import '../../widgets/luxury_widgets.dart';
import '../curriculum/subjects_grid_screen.dart';
import '../notebook/smart_error_notebook_screen.dart';
class UnifiedHomeScreen extends StatefulWidget {
final UserModel user;
@@ -316,7 +317,79 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
),
),
const SizedBox(height: 20),
const SizedBox(height: 12),
// Smart Error Notebook & Remediation Pathway Gateway
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const SmartErrorNotebookScreen()),
);
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF1E1B4B), Color(0xFF0F172A)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFF8B5CF6).withOpacity(0.4), width: 1.5),
boxShadow: [
BoxShadow(
color: const Color(0xFF8B5CF6).withOpacity(0.15),
blurRadius: 15,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF8B5CF6), Color(0xFF38BDF8)],
),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(CupertinoIcons.book_circle_fill, color: Colors.white, size: 24),
),
const SizedBox(width: 14),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'دفتر الأخطاء الذكي والمسار العلاجي 📓',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 14),
),
SizedBox(width: 6),
Text(
'· نشط',
style: TextStyle(color: Color(0xFF34D399), fontWeight: FontWeight.w700, fontSize: 11),
),
],
),
SizedBox(height: 3),
Text(
'رصد الأسئلة المتعثرة وسد الفجوات التراكمية بالذكاء الاصطناعي',
style: TextStyle(color: Color(0xFF94A3B8), fontSize: 11.5),
),
],
),
),
const Icon(CupertinoIcons.chevron_back, color: Color(0xFF8B5CF6), size: 18),
],
),
),
),
const SizedBox(height: 16),
// Real Readiness Score Card
LuxuryCard(
@@ -0,0 +1,969 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../data/models/error_notebook_model.dart';
import '../../../data/repositories/error_notebook_repository.dart';
/// ==============================================================================
/// SAQEL ENTERPRISE - SMART ERROR NOTEBOOK & ADAPTIVE REMEDIATION SCREEN
/// ==============================================================================
class SmartErrorNotebookScreen extends StatefulWidget {
const SmartErrorNotebookScreen({super.key});
@override
State<SmartErrorNotebookScreen> createState() =>
_SmartErrorNotebookScreenState();
}
class _SmartErrorNotebookScreenState extends State<SmartErrorNotebookScreen> {
final ErrorNotebookRepository _repository = ErrorNotebookRepository();
bool _isLoading = true;
ErrorNotebookSummary? _summary;
List<ErrorNotebookItem> _items = [];
// Filters
String _activeStatusFilter = 'all'; // 'all', 'pending_remediation', 'mastered'
String _activeSubjectFilter = 'all'; // 'all' or subject name
@override
void initState() {
super.initState();
_loadNotebookData();
}
Future<void> _loadNotebookData() async {
setState(() => _isLoading = true);
final data = await _repository.getErrorNotebook();
if (mounted) {
setState(() {
_summary = data['summary'] as ErrorNotebookSummary?;
_items = (data['items'] as List<ErrorNotebookItem>?) ?? [];
_isLoading = false;
});
}
}
List<ErrorNotebookItem> get _filteredItems {
return _items.where((item) {
if (_activeStatusFilter != 'all' && item.status != _activeStatusFilter) {
return false;
}
if (_activeSubjectFilter != 'all' &&
item.subjectName != _activeSubjectFilter) {
return false;
}
return true;
}).toList();
}
void _startRemediation(ErrorNotebookItem errorItem) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (ctx) => _RemediationModalSheet(
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,
);
}
});
},
),
);
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
backgroundColor: const Color(0xFF070B12),
appBar: AppBar(
backgroundColor: const Color(0xFF0E1626),
elevation: 0,
leading: IconButton(
icon: const Icon(CupertinoIcons.back, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
),
title: const Row(
children: [
Icon(CupertinoIcons.book_circle_fill,
color: Color(0xFF38BDF8), size: 22),
SizedBox(width: 8),
Text(
'دفتر الأخطاء الذكي والمسار العلاجي',
style: TextStyle(
fontSize: 16.5,
fontWeight: FontWeight.w800,
color: Colors.white,
),
),
],
),
actions: [
IconButton(
icon: const Icon(CupertinoIcons.arrow_clockwise, color: Colors.white70),
onPressed: _loadNotebookData,
tooltip: 'تحديث السجل',
),
],
),
body: _isLoading
? const Center(
child: CircularProgressIndicator(color: Color(0xFF38BDF8)),
)
: RefreshIndicator(
onRefresh: _loadNotebookData,
color: const Color(0xFF38BDF8),
child: ListView(
padding: const EdgeInsets.all(16),
children: [
if (_summary != null) _buildSummaryHeader(_summary!),
const SizedBox(height: 18),
_buildStatusTabs(),
const SizedBox(height: 12),
_buildSubjectFilterChips(),
const SizedBox(height: 16),
_buildItemsList(),
],
),
),
),
);
}
Widget _buildSummaryHeader(ErrorNotebookSummary summary) {
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF0F172A), Color(0xFF1E293B)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(18),
border: Border.all(color: const Color(0xFF334155)),
boxShadow: const [
BoxShadow(
color: Color(0x22000000),
blurRadius: 15,
offset: Offset(0, 6),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'مؤشر الشفاء والتمكن المعرفي',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w800,
color: Colors.white,
),
),
SizedBox(height: 2),
Text(
'رصد الفجوات وسدها قبل امتحان التوجيهي الوزاري',
style: TextStyle(
fontSize: 11.5,
color: Color(0xFF94A3B8),
),
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: summary.masteryPercentage >= 70
? const Color(0xFF10B981).withOpacity(0.2)
: const Color(0xFFF59E0B).withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${summary.masteryPercentage.toStringAsFixed(0)}% تمكن',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w900,
color: summary.masteryPercentage >= 70
? const Color(0xFF34D399)
: const Color(0xFFFBBF24),
),
),
),
],
),
const SizedBox(height: 14),
// Progress Bar
ClipRRect(
borderRadius: BorderRadius.circular(6),
child: LinearProgressIndicator(
value: (summary.masteryPercentage / 100).clamp(0.0, 1.0),
minHeight: 8,
backgroundColor: const Color(0xFF0F172A),
valueColor: AlwaysStoppedAnimation<Color>(
summary.masteryPercentage >= 70
? const Color(0xFF10B981)
: const Color(0xFFF59E0B),
),
),
),
const SizedBox(height: 16),
// KPI Stats Row
Row(
children: [
_metricBox(
title: 'إجمالي الفجوات',
value: '${summary.totalErrors}',
color: const Color(0xFF94A3B8),
icon: CupertinoIcons.scope,
),
const SizedBox(width: 8),
_metricBox(
title: 'بحاجة إلى علاج',
value: '${summary.pendingCount}',
color: const Color(0xFFEF4444),
icon: CupertinoIcons.exclamationmark_triangle_fill,
),
const SizedBox(width: 8),
_metricBox(
title: 'تم الإتقان 🏆',
value: '${summary.masteredCount}',
color: const Color(0xFF10B981),
icon: CupertinoIcons.checkmark_seal_fill,
),
],
),
],
),
);
}
Widget _metricBox({
required String title,
required String value,
required Color color,
required IconData icon,
}) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
decoration: BoxDecoration(
color: const Color(0xFF0B111E),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF1E293B)),
),
child: Column(
children: [
Icon(icon, size: 16, color: color),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w900,
color: color,
),
),
const SizedBox(height: 2),
Text(
title,
style: const TextStyle(fontSize: 10.5, color: Color(0xFF64748B)),
textAlign: TextAlign.center,
),
],
),
),
);
}
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})',
),
],
);
}
Widget _statusFilterButton(String filterKey, String title) {
final isSel = _activeStatusFilter == filterKey;
return Expanded(
child: GestureDetector(
onTap: () => setState(() => _activeStatusFilter = filterKey),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: isSel ? const Color(0xFF38BDF8) : const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
title,
style: TextStyle(
fontSize: 11.5,
fontWeight: isSel ? FontWeight.w800 : FontWeight.w600,
color: isSel ? Colors.black : const Color(0xFF94A3B8),
),
),
),
),
),
);
}
Widget _buildSubjectFilterChips() {
final subjects = ['all', ..._items.map((e) => e.subjectName).toSet()];
return SizedBox(
height: 34,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: subjects.length,
itemBuilder: (ctx, idx) {
final sub = subjects[idx];
final isSel = _activeSubjectFilter == sub;
final title = sub == 'all' ? 'جميع المواد' : sub;
return GestureDetector(
onTap: () => setState(() => _activeSubjectFilter = sub),
child: Container(
margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
decoration: BoxDecoration(
color: isSel ? const Color(0xFF10B981) : const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isSel ? const Color(0xFF34D399) : const Color(0xFF1E293B),
),
),
child: Center(
child: Text(
title,
style: TextStyle(
fontSize: 12,
fontWeight: isSel ? FontWeight.w800 : FontWeight.w600,
color: isSel ? Colors.black : const Color(0xFF94A3B8),
),
),
),
),
);
},
),
);
}
Widget _buildItemsList() {
final items = _filteredItems;
if (items.isEmpty) {
return Container(
padding: const EdgeInsets.all(40),
child: const Center(
child: Column(
children: [
Icon(CupertinoIcons.sparkles, size: 48, color: Color(0xFF10B981)),
SizedBox(height: 12),
Text(
'دفتر الأخطاء نظيف تماماً! أحسنت 🌟',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w800,
color: Colors.white,
),
),
SizedBox(height: 4),
Text(
'لم يتم رصد أي فجوات غير معالجة في هذا المبحث.',
style: TextStyle(fontSize: 12, color: Color(0xFF64748B)),
),
],
),
),
);
}
return Column(
children: items.map((it) => _buildErrorCard(it)).toList(),
);
}
Widget _buildErrorCard(ErrorNotebookItem item) {
return Container(
margin: const EdgeInsets.only(bottom: 14),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: item.isMastered
? const Color(0xFF10B981).withOpacity(0.3)
: const Color(0xFFEF4444).withOpacity(0.3),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header: Subject & Category Badge
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(6),
),
child: Text(
item.subjectName,
style: const TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.w800,
color: Color(0xFF38BDF8),
),
),
),
const SizedBox(width: 8),
Text(
item.topicName,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF94A3B8),
),
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: item.categoryColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(6),
),
child: Text(
item.categoryArabicName,
style: TextStyle(
fontSize: 10.5,
fontWeight: FontWeight.w700,
color: item.categoryColor,
),
),
),
],
),
const SizedBox(height: 12),
// Question Text
Text(
item.questionText,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w800,
color: Colors.white,
height: 1.4,
),
),
const SizedBox(height: 12),
// Comparison Box (Wrong vs Correct)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF161F30),
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
Row(
children: [
const Icon(CupertinoIcons.xmark_circle_fill,
size: 15, color: Color(0xFFEF4444)),
const SizedBox(width: 8),
const Text('إجابتك السابقة: ',
style: TextStyle(fontSize: 12, color: Color(0xFF94A3B8))),
Expanded(
child: Text(
item.studentWrongAnswer,
style: const TextStyle(
fontSize: 12.5,
fontWeight: FontWeight.w700,
color: Color(0xFFF87171),
),
),
),
],
),
const Divider(color: Color(0xFF1E293B), height: 16),
Row(
children: [
const Icon(CupertinoIcons.checkmark_circle_fill,
size: 15, color: Color(0xFF10B981)),
const SizedBox(width: 8),
const Text('الإجابة الصحيحة: ',
style: TextStyle(fontSize: 12, color: Color(0xFF94A3B8))),
Expanded(
child: Text(
item.correctAnswer,
style: const TextStyle(
fontSize: 12.5,
fontWeight: FontWeight.w700,
color: Color(0xFF34D399),
),
),
),
],
),
],
),
),
const SizedBox(height: 12),
// Socratic Hint Box
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF451A03),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFFF59E0B).withOpacity(0.3)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(CupertinoIcons.lightbulb_fill,
size: 16, color: Color(0xFFFBBF24)),
const SizedBox(width: 8),
Expanded(
child: Text(
item.socraticHint,
style: const TextStyle(
fontSize: 12,
color: Color(0xFFFEF3C7),
height: 1.4,
),
),
),
],
),
),
const SizedBox(height: 14),
// Action Button
if (!item.isMastered)
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),
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0284C7),
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 42),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
)
else
Container(
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF064E3B).withOpacity(0.3),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFF10B981).withOpacity(0.4)),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(CupertinoIcons.checkmark_seal_fill,
color: Color(0xFF34D399), size: 16),
SizedBox(width: 8),
Text(
'تم إتقان المهارة وسد الفجوة المعرفية بنجاح 🏆',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w800,
color: Color(0xFF6EE7B7),
),
),
],
),
),
],
),
);
}
}
/// ==============================================================================
/// REMEDIATION MODAL SHEET (3 TARGETED DRILL QUESTIONS)
/// ==============================================================================
class _RemediationModalSheet extends StatefulWidget {
final ErrorNotebookItem errorItem;
final ErrorNotebookRepository repository;
final VoidCallback onRemediationCompleted;
const _RemediationModalSheet({
required this.errorItem,
required this.repository,
required this.onRemediationCompleted,
});
@override
State<_RemediationModalSheet> createState() => _RemediationModalSheetState();
}
class _RemediationModalSheetState extends State<_RemediationModalSheet> {
bool _isLoadingQuiz = true;
List<RemedialQuestion> _questions = [];
int _currentQuestionIndex = 0;
int? _selectedOptionIndex;
bool _hasAnsweredCurrent = false;
int _correctAnswersCount = 0;
bool _isCompleted = false;
@override
void initState() {
super.initState();
_fetchQuiz();
}
Future<void> _fetchQuiz() async {
final qs = await widget.repository.getRemediationQuiz(
errorUuid: widget.errorItem.uuid,
topicName: widget.errorItem.topicName,
);
if (mounted) {
setState(() {
_questions = qs;
_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 {
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);
widget.onRemediationCompleted();
}
}
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
height: MediaQuery.of(context).size.height * 0.85,
decoration: const BoxDecoration(
color: Color(0xFF0F172A),
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
border: Border(top: BorderSide(color: Color(0xFF38BDF8), width: 2)),
),
padding: const EdgeInsets.all(20),
child: _isLoadingQuiz
? const Center(
child: CircularProgressIndicator(color: Color(0xFF38BDF8)),
)
: _isCompleted
? _buildCompletionState()
: _buildQuestionState(),
),
);
}
Widget _buildQuestionState() {
final q = _questions[_currentQuestionIndex];
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Top Handle & Progress Header
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: const Color(0xFF334155),
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 14),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'المسار العلاجي (${_currentQuestionIndex + 1} من ${_questions.length})',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w800,
color: Color(0xFF38BDF8),
),
),
IconButton(
icon: const Icon(CupertinoIcons.xmark, color: Colors.white70),
onPressed: () => Navigator.of(context).pop(),
),
],
),
const SizedBox(height: 8),
Text(
q.question,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w800,
color: Colors.white,
height: 1.4,
),
),
const SizedBox(height: 16),
// Options
Expanded(
child: ListView.builder(
itemCount: q.options.length,
itemBuilder: (ctx, idx) {
final optText = q.options[idx];
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) {
borderCol = const Color(0xFF38BDF8);
bgCol = const Color(0xFF0369A1);
}
return GestureDetector(
onTap: _hasAnsweredCurrent
? null
: () => setState(() => _selectedOptionIndex = idx),
child: Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: bgCol,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: borderCol, width: 1.5),
),
child: Row(
children: [
Container(
width: 26,
height: 26,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
shape: BoxShape.circle,
),
child: Center(
child: Text(
'${idx + 1}',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w800,
color: Colors.white,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
optText,
style: const TextStyle(
fontSize: 13,
color: Colors.white,
),
),
),
],
),
),
);
},
),
),
// 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,
style: ElevatedButton.styleFrom(
backgroundColor: _hasAnsweredCurrent
? const Color(0xFF10B981)
: const Color(0xFF38BDF8),
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
!_hasAnsweredCurrent
? 'تأكيد الإجابة'
: (_currentQuestionIndex < _questions.length - 1
? 'السؤال التالي ➔'
: 'إنهاء المسار العلاجي 🏆'),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w800),
),
),
],
);
}
Widget _buildCompletionState() {
final passed = _correctAnswersCount >= 2;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
passed
? CupertinoIcons.check_mark_circled_solid
: CupertinoIcons.exclamationmark_triangle_fill,
size: 64,
color: passed ? const Color(0xFF10B981) : const Color(0xFFF59E0B),
),
const SizedBox(height: 16),
Text(
passed
? 'مبارك! تم إتقان المهارة وسد الفجوة بنجاح 🎉'
: 'أحسنت المحاولة، راجع الشرح وأعد الاختبار',
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.w900,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
passed
? 'أجبت عن $_correctAnswersCount من ${_questions.length} أسئلة بشكل سليم. تم ترحيل الخطأ إلى أرشيف المتقنات.'
: 'أجبت عن $_correctAnswersCount من ${_questions.length}. حاول مجدداً لضمان إتقان السؤال في الامتحان الوزاري.',
style: const TextStyle(fontSize: 13, color: Color(0xFF94A3B8)),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
backgroundColor: passed ? const Color(0xFF10B981) : const Color(0xFF38BDF8),
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
'العودة إلى دفتر الأخطاء',
style: TextStyle(fontWeight: FontWeight.w800),
),
),
],
),
);
}
}