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
File diff suppressed because it is too large Load Diff
@@ -116,7 +116,7 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
Icon(CupertinoIcons.checkmark_seal_fill, color: AppColors.saqelCyan, size: 14),
SizedBox(width: 6),
Text(
'المنهاج الوزاري الأردني 🇯🇴',
'فهرس الدروس المنشورة',
style: TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontWeight: FontWeight.w700),
),
],
@@ -141,7 +141,9 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
const SizedBox(height: 14),
LuxuryButton(
text: 'إعادة المحاولة',
onPressed: () => context.read<CurriculumCubit>().fetchSubjects(),
onPressed: () => context.read<CurriculumCubit>().fetchSubjects(
gradeLevel: GradeLevelModel.normalizeGradeKey(widget.initialGrade ?? 'grade_10'),
),
),
],
),
@@ -162,7 +164,7 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'المواد المقررة — ${GradeLevelModel.getGradeName(state.selectedGrade)} 📚',
'المواد المنشورة — ${GradeLevelModel.getGradeName(state.selectedGrade)} 📚',
style: AppTypography.displayLarge.copyWith(color: Colors.white, fontSize: 20),
),
const SizedBox(height: 4),
@@ -188,7 +190,8 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
),
const SizedBox(height: 16),
// Official Academic Grade Level Banner (Strict Grade-Gate — الصف العاشر فقط)
// The catalogue lists reviewed publications; do not claim
// ministry certification without a separate source of proof.
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
@@ -212,12 +215,12 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${GradeLevelModel.getGradeName(state.selectedGrade)} — المسار الأكاديمي المعتمد',
'${GradeLevelModel.getGradeName(state.selectedGrade)} — الدروس المنشورة',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5),
),
const SizedBox(height: 2),
const Text(
'خطة دراسية موحدة ومطابقة للمنهاج الوزاري الأردني المطور',
'تظهر هنا الدروس التي اكتملت مراجعتها ونُشرت على المنصة',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
],
@@ -255,15 +258,17 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
child: const Icon(CupertinoIcons.square_grid_2x2, size: 32, color: AppColors.saqelCyan),
),
const SizedBox(height: 16),
const Text(
'لا توجد مواد مضافة بعد في السيرفر',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 16),
Text(
state.subjects.isEmpty ? 'لا توجد دروس منشورة لهذا الصف بعد' : 'لا توجد مواد تطابق البحث',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 16),
),
const SizedBox(height: 6),
const Text(
'يتم إدراج المواد تلقائياً بمجرد رفع وتحليل كتب المناهج في الخادم',
Text(
state.subjects.isEmpty
? 'يمكنك العودة لاحقاً للاطلاع على الحزم بعد نشرها.'
: 'جرّب اسم مادة مختلفاً أو امسح البحث.',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 13),
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 13),
),
],
),
@@ -7,20 +7,26 @@ 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 {
class TeacherSelectionSheet extends StatefulWidget {
final CurriculumLessonItemModel lesson;
final List<PublishedLessonVideoModel> videos;
final int? nextCursor;
final int availableCount;
const TeacherSelectionSheet({
super.key,
required this.lesson,
required this.videos,
required this.nextCursor,
required this.availableCount,
});
static Future<PublishedLessonVideoModel?> show(
BuildContext context, {
required CurriculumLessonItemModel lesson,
required List<PublishedLessonVideoModel> videos,
required int? nextCursor,
required int availableCount,
}) {
return showModalBottomSheet<PublishedLessonVideoModel>(
context: context,
@@ -29,20 +35,43 @@ class TeacherSelectionSheet extends StatelessWidget {
builder: (sheetContext) => TeacherSelectionSheet(
lesson: lesson,
videos: videos,
nextCursor: nextCursor,
availableCount: availableCount,
),
);
}
@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);
});
State<TeacherSelectionSheet> createState() => _TeacherSelectionSheetState();
}
class _TeacherSelectionSheetState extends State<TeacherSelectionSheet> {
late final List<PublishedLessonVideoModel> _videos = List.of(widget.videos);
late int? _nextCursor = widget.nextCursor;
bool _loading = false;
bool _failed = false;
Future<void> _loadMore() async {
final cursor = _nextCursor;
final lessonId = widget.lesson.curriculumLessonId;
if (_loading || cursor == null || lessonId == null) return;
setState(() { _loading = true; _failed = false; });
try {
final page = await CurriculumRepository().getPublishedLessonVideosPage(lessonId, cursor: cursor, limit: 20);
if (!mounted) return;
final known = _videos.map((video) => video.videoVersionId).toSet();
setState(() {
_videos.addAll(page.items.where((video) => known.add(video.videoVersionId)));
_nextCursor = page.nextCursor;
_loading = false;
});
} catch (_) {
if (mounted) setState(() { _loading = false; _failed = true; });
}
}
@override
Widget build(BuildContext context) {
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.82,
@@ -103,7 +132,7 @@ class TeacherSelectionSheet extends StatelessWidget {
),
const SizedBox(height: 2),
Text(
lesson.title,
widget.lesson.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
@@ -161,23 +190,21 @@ class TeacherSelectionSheet extends StatelessWidget {
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
shrinkWrap: true,
itemCount: sortedVideos.length,
itemCount: _videos.length + (_nextCursor != null ? 1 : 0),
separatorBuilder: (_, __) => const SizedBox(height: 12),
itemBuilder: (ctx, idx) {
final video = sortedVideos[idx];
if (idx == _videos.length) {
return Center(child: TextButton(
onPressed: _loading ? null : _loadMore,
child: Text(_loading ? 'جارٍ تحميل المزيد…' : _failed ? 'تعذر التحميل — أعد المحاولة' : 'عرض المزيد (${_videos.length} من ${widget.availableCount})'),
));
}
final video = _videos[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)
@@ -344,8 +371,8 @@ class TeacherSelectionSheet extends StatelessWidget {
),
const SizedBox(height: 10),
// Pedagogical approach badge
Container(
// Only metadata supplied by the published video.
if (video.durationSeconds > 0 || video.publishedAt != null) Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
decoration: BoxDecoration(
@@ -356,14 +383,17 @@ class TeacherSelectionSheet extends StatelessWidget {
child: Row(
children: [
const Icon(
CupertinoIcons.lightbulb_fill,
CupertinoIcons.time,
color: AppColors.guardianAmber,
size: 12,
),
const SizedBox(width: 6),
Expanded(
child: Text(
approach,
[
if (video.durationSeconds > 0) '${(video.durationSeconds / 60).ceil()} دقيقة',
if (video.publishedAt != null) 'نُشرت ${video.publishedAt!.year}-${video.publishedAt!.month.toString().padLeft(2, '0')}-${video.publishedAt!.day.toString().padLeft(2, '0')}',
].join(' · '),
style: const TextStyle(
color: AppColors.textSecondaryDark,
fontSize: 11,
@@ -42,7 +42,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
@override
void didUpdateWidget(covariant UnifiedHomeScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.user.gradeLevel != widget.user.gradeLevel || oldWidget.user.stream != widget.user.stream) {
if (oldWidget.user.gradeLevel != widget.user.gradeLevel ||
oldWidget.user.stream != widget.user.stream) {
_refreshData();
}
}
@@ -50,10 +51,10 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
void _refreshData() {
if (_currentRole == 'student') {
context.read<StudentCubit>().fetchLessons(
gradeLevel: widget.user.gradeLevel,
stream: widget.user.stream,
initialReadiness: widget.user.readinessScore ?? 0.0,
);
gradeLevel: widget.user.gradeLevel,
stream: widget.user.stream,
initialReadiness: widget.user.readinessScore ?? 0.0,
);
} else {
context.read<GuardianCubit>().fetchDashboard();
}
@@ -83,7 +84,9 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
decoration: const BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
border: Border(top: BorderSide(color: AppColors.darkCardBorder, width: 1.5)),
border: Border(
top: BorderSide(
color: AppColors.darkCardBorder, width: 1.5)),
),
child: SafeArea(
child: Column(
@@ -109,7 +112,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(CupertinoIcons.book_fill, color: AppColors.saqelCyan, size: 24),
child: const Icon(CupertinoIcons.book_fill,
color: AppColors.saqelCyan, size: 24),
),
const SizedBox(width: 14),
const Expanded(
@@ -118,12 +122,17 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
children: [
Text(
'تحديد وتعديل الصف الدراسي 🎓',
style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.w800),
style: TextStyle(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.w800),
),
SizedBox(height: 3),
Text(
'اختر صفك لفتح المناهج الوزارية والشروحات المعتمدة',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
style: TextStyle(
color: AppColors.textSecondaryDark,
fontSize: 12),
),
],
),
@@ -132,9 +141,21 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
const SizedBox(height: 20),
...[
('grade_10', 'الصف العاشر الأساسي (Grade 10)', 'يشمل الرياضيات المطور، الفيزياء، والإنجليزية'),
('grade_11', 'الصف الحادي عشر (الأول ثانوي)', 'المسار الأكاديمي للفرعين العلمي والأدبي'),
('grade_12', 'الصف الثاني عشر (التوجيهي الوزاري)', 'منهاج الثانوية العامة التوجيهي الوزاري'),
(
'grade_10',
'الصف العاشر الأساسي (Grade 10)',
'يشمل الرياضيات المطور، الفيزياء، والإنجليزية'
),
(
'grade_11',
'الصف الحادي عشر (الأول ثانوي)',
'المسار الأكاديمي للفرعين العلمي والأدبي'
),
(
'grade_12',
'الصف الثاني عشر (التوجيهي الوزاري)',
'منهاج الثانوية العامة التوجيهي الوزاري'
),
].map((g) {
final isSelected = tempGrade == g.$1;
return Padding(
@@ -143,44 +164,61 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
onTap: () {
setModalState(() {
tempGrade = g.$1;
if (tempGrade == 'grade_10') tempStream = 'general';
if (tempGrade == 'grade_10')
tempStream = 'general';
});
},
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: isSelected ? AppColors.saqelCyan.withAlpha(25) : AppColors.darkBackground,
color: isSelected
? AppColors.saqelCyan.withAlpha(25)
: AppColors.darkBackground,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected ? AppColors.saqelCyan : AppColors.darkCardBorder,
color: isSelected
? AppColors.saqelCyan
: AppColors.darkCardBorder,
width: isSelected ? 1.5 : 1.0,
),
),
child: Row(
children: [
Icon(
isSelected ? CupertinoIcons.checkmark_circle_fill : CupertinoIcons.circle,
color: isSelected ? AppColors.saqelCyan : AppColors.textMutedDark,
isSelected
? CupertinoIcons.checkmark_circle_fill
: CupertinoIcons.circle,
color: isSelected
? AppColors.saqelCyan
: AppColors.textMutedDark,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
g.$2,
style: TextStyle(
color: isSelected ? Colors.white : AppColors.textSecondaryDark,
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w600,
color: isSelected
? Colors.white
: AppColors.textSecondaryDark,
fontWeight: isSelected
? FontWeight.w800
: FontWeight.w600,
fontSize: 14,
),
),
const SizedBox(height: 2),
Text(
g.$3,
style: const TextStyle(color: AppColors.textMutedDark, fontSize: 11),
style: const TextStyle(
color: AppColors.textMutedDark,
fontSize: 11),
),
],
),
@@ -196,7 +234,9 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
text: 'حفظ الصف وتحديث المسار 🚀',
onPressed: () async {
Navigator.of(ctx).pop();
await context.read<AuthCubit>().updateGrade(tempGrade, stream: tempStream);
await context
.read<AuthCubit>()
.updateGrade(tempGrade, stream: tempStream);
if (mounted) {
_refreshData();
SaqelToast.showSuccess(
@@ -225,7 +265,9 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
body: Container(
decoration: BoxDecoration(
gradient: RadialGradient(
center: isStudent ? const Alignment(0.8, -0.6) : const Alignment(-0.8, -0.6),
center: isStudent
? const Alignment(0.8, -0.6)
: const Alignment(-0.8, -0.6),
radius: 1.3,
colors: isStudent
? const [Color(0xFF0A2540), AppColors.darkBackground]
@@ -277,7 +319,10 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
child: const Center(
child: Text(
'ص',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w900, color: Colors.black),
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: Colors.black),
),
),
),
@@ -294,17 +339,20 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
GestureDetector(
onTap: () => _showGradeSelectionModal(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.saqelCyan.withAlpha(80), width: 0.8),
border: Border.all(
color: AppColors.saqelCyan.withAlpha(80), width: 0.8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
GradeLevelModel.getGradeName(widget.user.gradeLevel ?? 'grade_10'),
GradeLevelModel.getGradeName(
widget.user.gradeLevel ?? 'grade_10'),
style: const TextStyle(
color: AppColors.saqelCyan,
fontSize: 10.5,
@@ -312,7 +360,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
),
const SizedBox(width: 4),
const Icon(CupertinoIcons.chevron_down, color: AppColors.saqelCyan, size: 10),
const Icon(CupertinoIcons.chevron_down,
color: AppColors.saqelCyan, size: 10),
],
),
),
@@ -320,7 +369,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
else
Text(
AppStrings.guardianPortal,
style: AppTypography.labelSmall.copyWith(color: AppColors.textMutedDark),
style: AppTypography.labelSmall
.copyWith(color: AppColors.textMutedDark),
),
],
),
@@ -336,8 +386,10 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
child: Row(
children: [
_buildRolePill('الطالب', isStudent, () => _switchRole('student')),
_buildRolePill('ولي الأمر', !isStudent, () => _switchRole('guardian')),
_buildRolePill(
'الطالب', isStudent, () => _switchRole('student')),
_buildRolePill(
'ولي الأمر', !isStudent, () => _switchRole('guardian')),
],
),
),
@@ -345,7 +397,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
// Logout Icon
IconButton(
icon: const Icon(CupertinoIcons.power, color: AppColors.errorRed, size: 20),
icon: const Icon(CupertinoIcons.power,
color: AppColors.errorRed, size: 20),
onPressed: () => context.read<AuthCubit>().logout(),
tooltip: AppStrings.logout,
),
@@ -388,9 +441,11 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(CupertinoIcons.exclamationmark_triangle, color: AppColors.errorRed, size: 40),
const Icon(CupertinoIcons.exclamationmark_triangle,
color: AppColors.errorRed, size: 40),
const SizedBox(height: 12),
Text(state.message, style: const TextStyle(color: Colors.white)),
Text(state.message,
style: const TextStyle(color: Colors.white)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _refreshData,
@@ -412,27 +467,37 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(25),
shape: BoxShape.circle,
border: Border.all(color: AppColors.saqelCyan.withAlpha(80), width: 1.5),
border: Border.all(
color: AppColors.saqelCyan.withAlpha(80), width: 1.5),
),
child: const Icon(CupertinoIcons.book_fill, color: AppColors.saqelCyan, size: 36),
child: const Icon(CupertinoIcons.book_fill,
color: AppColors.saqelCyan, size: 36),
),
const SizedBox(height: 18),
const Text(
'المنهاج الوزاري المعتمد — صَقِل 🇯🇴',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w800),
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w800),
),
const SizedBox(height: 8),
const Text(
'اختر من بين 16 مبحثاً دراسياً لبدء شروحات الفيديو التفاعلية، أوراق العمل، وبنك الأسئلة المعتمد',
'لا توجد حصص تشغيل منشورة لهذا الحساب حالياً. يمكنك تصفح الدروس والموارد التي اجتازت النشر من فهرس المنهاج.',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 13, height: 1.4),
style: TextStyle(
color: AppColors.textSecondaryDark,
fontSize: 13,
height: 1.4),
),
const SizedBox(height: 24),
LuxuryButton(
text: 'تصفح المناهج والمواد 📚',
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => SubjectsGridScreen(initialGrade: widget.user.gradeLevel)),
MaterialPageRoute(
builder: (_) => SubjectsGridScreen(
initialGrade: widget.user.gradeLevel)),
);
},
),
@@ -450,7 +515,9 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => SubjectsGridScreen(initialGrade: widget.user.gradeLevel)),
MaterialPageRoute(
builder: (_) => SubjectsGridScreen(
initialGrade: widget.user.gradeLevel)),
);
},
borderRadius: BorderRadius.circular(20),
@@ -463,7 +530,9 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.saqelCyan.withAlpha(100), width: 1.5),
border: Border.all(
color: AppColors.saqelCyan.withAlpha(100),
width: 1.5),
boxShadow: [
BoxShadow(
color: AppColors.saqelCyan.withAlpha(30),
@@ -479,11 +548,15 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
height: 48,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.appleBlue, AppColors.saqelCyan],
colors: [
AppColors.appleBlue,
AppColors.saqelCyan
],
),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(CupertinoIcons.square_grid_2x2_fill, color: Colors.black, size: 24),
child: const Icon(CupertinoIcons.square_grid_2x2_fill,
color: Colors.black, size: 24),
),
const SizedBox(width: 14),
const Expanded(
@@ -491,18 +564,24 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'تصفح المناهج والمواد (16 مادة) 📚',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 15),
'تصفح المناهج والمواد 📚',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w800,
fontSize: 15),
),
SizedBox(height: 4),
Text(
'الرياضيات، الفيزياء، الإنجليزي، الكيمياء، والمزيد',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
style: TextStyle(
color: AppColors.textSecondaryDark,
fontSize: 12),
),
],
),
),
const Icon(CupertinoIcons.chevron_back, color: AppColors.saqelCyan, size: 18),
const Icon(CupertinoIcons.chevron_back,
color: AppColors.saqelCyan, size: 18),
],
),
),
@@ -513,7 +592,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const VocationalTrainingScreen()),
MaterialPageRoute(
builder: (_) => const VocationalTrainingScreen()),
);
},
borderRadius: BorderRadius.circular(20),
@@ -526,7 +606,9 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFF10B981).withAlpha(160), width: 1.5),
border: Border.all(
color: const Color(0xFF10B981).withAlpha(160),
width: 1.5),
boxShadow: [
BoxShadow(
color: const Color(0xFF10B981).withAlpha(35),
@@ -548,7 +630,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(CupertinoIcons.wrench_fill, color: Colors.black, size: 24),
child: const Icon(CupertinoIcons.wrench_fill,
color: Colors.black, size: 24),
),
const SizedBox(width: 14),
const Expanded(
@@ -557,17 +640,24 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
children: [
Text(
'مؤسسة التدريب المهني (VTC Hub) ⚡',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 14.5),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w800,
fontSize: 14.5),
),
SizedBox(height: 4),
Text(
'140 مهنة • 8 قطاعات صناعية • مختبر فحص المركبات الكهربائية (EV)',
style: TextStyle(color: Color(0xFF6EE7B7), fontSize: 11.5, fontWeight: FontWeight.w600),
style: TextStyle(
color: Color(0xFF6EE7B7),
fontSize: 11.5,
fontWeight: FontWeight.w600),
),
],
),
),
const Icon(CupertinoIcons.chevron_back, color: Color(0xFF10B981), size: 18),
const Icon(CupertinoIcons.chevron_back,
color: Color(0xFF10B981), size: 18),
],
),
),
@@ -578,7 +668,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const SmartErrorNotebookScreen()),
MaterialPageRoute(
builder: (_) => const SmartErrorNotebookScreen()),
);
},
borderRadius: BorderRadius.circular(20),
@@ -591,7 +682,9 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFF8B5CF6).withOpacity(0.4), width: 1.5),
border: Border.all(
color: const Color(0xFF8B5CF6).withOpacity(0.4),
width: 1.5),
boxShadow: [
BoxShadow(
color: const Color(0xFF8B5CF6).withOpacity(0.15),
@@ -611,7 +704,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(CupertinoIcons.book_circle_fill, color: Colors.white, size: 24),
child: const Icon(CupertinoIcons.book_circle_fill,
color: Colors.white, size: 24),
),
const SizedBox(width: 14),
const Expanded(
@@ -622,24 +716,32 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
children: [
Text(
'دفتر الأخطاء الذكي والمسار العلاجي 📓',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 14),
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w800,
fontSize: 14),
),
SizedBox(width: 6),
Text(
'· نشط',
style: TextStyle(color: Color(0xFF34D399), fontWeight: FontWeight.w700, fontSize: 11),
style: TextStyle(
color: Color(0xFF34D399),
fontWeight: FontWeight.w700,
fontSize: 11),
),
],
),
SizedBox(height: 3),
Text(
'رصد الأسئلة المتعثرة وسد الفجوات التراكمية بالذكاء الاصطناعي',
style: TextStyle(color: Color(0xFF94A3B8), fontSize: 11.5),
style: TextStyle(
color: Color(0xFF94A3B8), fontSize: 11.5),
),
],
),
),
const Icon(CupertinoIcons.chevron_back, color: Color(0xFF8B5CF6), size: 18),
const Icon(CupertinoIcons.chevron_back,
color: Color(0xFF8B5CF6), size: 18),
],
),
),
@@ -658,17 +760,21 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
GestureDetector(
onTap: () => _showGradeSelectionModal(context),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: AppColors.saqelCyan.withAlpha(80), width: 0.8),
border: Border.all(
color: AppColors.saqelCyan.withAlpha(80),
width: 0.8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
GradeLevelModel.getGradeName(widget.user.gradeLevel ?? 'grade_10'),
GradeLevelModel.getGradeName(
widget.user.gradeLevel ?? 'grade_10'),
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
@@ -676,7 +782,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
),
const SizedBox(width: 4),
const Icon(CupertinoIcons.pencil, color: AppColors.saqelCyan, size: 11),
const Icon(CupertinoIcons.pencil,
color: AppColors.saqelCyan, size: 11),
],
),
),
@@ -684,12 +791,15 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
const SizedBox(height: 8),
Text(
AppStrings.readinessScore,
style: AppTypography.headlineMedium.copyWith(color: Colors.white),
style: AppTypography.headlineMedium
.copyWith(color: Colors.white),
),
const SizedBox(height: 4),
Text(
'تقييم حقيقي مبني على الكويزات وفحوصات الفهم',
style: AppTypography.bodyMedium.copyWith(color: AppColors.textSecondaryDark, fontSize: 12),
style: AppTypography.bodyMedium.copyWith(
color: AppColors.textSecondaryDark,
fontSize: 12),
),
],
),
@@ -699,12 +809,16 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
height: 64,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: AppColors.saqelCyan, width: 3),
border:
Border.all(color: AppColors.saqelCyan, width: 3),
),
child: Center(
child: Text(
'${state.readinessScore.toStringAsFixed(0)}%',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: Colors.white),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
color: Colors.white),
),
),
),
@@ -716,7 +830,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
// Real Lessons List from API
Text(
AppStrings.tawjihiCurriculum,
style: AppTypography.titleMedium.copyWith(color: Colors.white),
style:
AppTypography.titleMedium.copyWith(color: Colors.white),
),
const SizedBox(height: 12),
@@ -751,7 +866,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
color: AppColors.appleBlue.withAlpha(30),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(CupertinoIcons.play_fill, color: AppColors.appleBlue, size: 20),
child: const Icon(CupertinoIcons.play_fill,
color: AppColors.appleBlue, size: 20),
),
const SizedBox(width: 14),
Expanded(
@@ -771,14 +887,18 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
if (lesson.isAiVersion) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
padding: const EdgeInsets.symmetric(
horizontal: 5, vertical: 2),
decoration: BoxDecoration(
color: AppColors.adminViolet.withAlpha(40),
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'ذكاء اصطناعي ✨',
style: TextStyle(fontSize: 9, fontWeight: FontWeight.w700, color: Color(0xFFA78BFA)),
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w700,
color: Color(0xFFA78BFA)),
),
),
],
@@ -789,12 +909,16 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
lesson.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: Colors.white),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
const SizedBox(height: 2),
Text(
lesson.formattedDuration,
style: const TextStyle(fontSize: 11, color: AppColors.textMutedDark),
style: const TextStyle(
fontSize: 11, color: AppColors.textMutedDark),
),
],
),
@@ -818,9 +942,11 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(CupertinoIcons.exclamationmark_triangle, color: AppColors.errorRed, size: 40),
const Icon(CupertinoIcons.exclamationmark_triangle,
color: AppColors.errorRed, size: 40),
const SizedBox(height: 12),
Text(state.message, style: const TextStyle(color: Colors.white)),
Text(state.message,
style: const TextStyle(color: Colors.white)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _refreshData,
@@ -834,9 +960,11 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(CupertinoIcons.person_2, color: AppColors.textMutedDark, size: 48),
const Icon(CupertinoIcons.person_2,
color: AppColors.textMutedDark, size: 48),
const SizedBox(height: 12),
Text(AppStrings.noChildrenLinked, style: const TextStyle(color: AppColors.textSecondaryDark)),
Text(AppStrings.noChildrenLinked,
style: const TextStyle(color: AppColors.textSecondaryDark)),
],
),
);
@@ -850,7 +978,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
children: [
Text(
AppStrings.linkedChildren,
style: AppTypography.titleMedium.copyWith(color: Colors.white),
style:
AppTypography.titleMedium.copyWith(color: Colors.white),
),
const SizedBox(height: 10),
@@ -861,15 +990,20 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
final child = state.children[index];
return Expanded(
child: GestureDetector(
onTap: () => context.read<GuardianCubit>().selectChild(index),
onTap: () =>
context.read<GuardianCubit>().selectChild(index),
child: Container(
margin: EdgeInsets.only(left: index == 0 ? 8 : 0),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isSelected ? AppColors.guardianAmber.withAlpha(35) : AppColors.darkCard,
color: isSelected
? AppColors.guardianAmber.withAlpha(35)
: AppColors.darkCard,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: isSelected ? AppColors.guardianAmber : AppColors.darkCardBorder,
color: isSelected
? AppColors.guardianAmber
: AppColors.darkCardBorder,
),
),
child: Column(
@@ -880,13 +1014,17 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: isSelected ? const Color(0xFFFBBF24) : Colors.white,
color: isSelected
? const Color(0xFFFBBF24)
: Colors.white,
),
),
const SizedBox(height: 2),
Text(
child.gradeLevel ?? 'طالب مسجل',
style: const TextStyle(fontSize: 10.5, color: AppColors.textMutedDark),
style: const TextStyle(
fontSize: 10.5,
color: AppColors.textMutedDark),
),
],
),
@@ -906,11 +1044,13 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
children: [
Text(
'تقرير الطالب: ${selectedChild.name}',
style: AppTypography.titleMedium.copyWith(color: Colors.white),
style: AppTypography.titleMedium
.copyWith(color: Colors.white),
),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: AppColors.teacherEmerald.withAlpha(30),
borderRadius: BorderRadius.circular(6),
@@ -929,12 +1069,14 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
const SizedBox(height: 6),
Text(
'المدرسة: ${selectedChild.schoolName ?? 'مدرسة الثقافة العسكرية'}',
style: const TextStyle(fontSize: 12, color: AppColors.textMutedDark),
style: const TextStyle(
fontSize: 12, color: AppColors.textMutedDark),
),
const SizedBox(height: 16),
const Divider(color: AppColors.darkCardBorder),
const SizedBox(height: 10),
_buildGuardianStat('الامتحانات المنجزة بنجاح', '${selectedChild.examsPassed} من ${selectedChild.examsTotal}'),
_buildGuardianStat('الامتحانات المنجزة بنجاح',
'${selectedChild.examsPassed} من ${selectedChild.examsTotal}'),
InkWell(
onTap: () {
Navigator.of(context).push(
@@ -958,7 +1100,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
),
),
_buildGuardianStat('الرقم الوطني المشفر', selectedChild.nationalId ?? 'مسجل بالهاتف'),
_buildGuardianStat('الرقم الوطني المشفر',
selectedChild.nationalId ?? 'مسجل بالهاتف'),
],
),
),
@@ -972,7 +1115,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
builder: (ctx) => Directionality(
textDirection: TextDirection.rtl,
child: CupertinoAlertDialog(
title: const Text('ومضة التقرير الشهري لولي الأمر 📲'),
title:
const Text('ومضة التقرير الشهري لولي الأمر 📲'),
content: Text(
'🇯🇴 تقرير التحصيل الأكاديمي المعتمد\n'
'الطالب: ${selectedChild.name}\n'
@@ -999,7 +1143,8 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
borderColor: AppColors.whatsappGreen.withAlpha(90),
child: const Row(
children: [
Icon(CupertinoIcons.chat_bubble_2_fill, color: AppColors.whatsappGreen, size: 26),
Icon(CupertinoIcons.chat_bubble_2_fill,
color: AppColors.whatsappGreen, size: 26),
SizedBox(width: 12),
Expanded(
child: Column(
@@ -1009,24 +1154,33 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
children: [
Text(
'تقارير بوابة نبيه للواتساب 📲',
style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w800, color: Colors.white),
style: TextStyle(
fontSize: 13.5,
fontWeight: FontWeight.w800,
color: Colors.white),
),
SizedBox(width: 6),
Text(
'· نشطة',
style: TextStyle(color: Color(0xFF34D399), fontWeight: FontWeight.w700, fontSize: 11),
style: TextStyle(
color: Color(0xFF34D399),
fontWeight: FontWeight.w700,
fontSize: 11),
),
],
),
SizedBox(height: 3),
Text(
'انقر لمعاينة ومضة الأداء الأكاديمي الشهرية المرسلة لولي الأمر',
style: TextStyle(fontSize: 11, color: AppColors.textSecondaryDark),
style: TextStyle(
fontSize: 11,
color: AppColors.textSecondaryDark),
),
],
),
),
Icon(CupertinoIcons.chevron_back, color: AppColors.whatsappGreen, size: 16),
Icon(CupertinoIcons.chevron_back,
color: AppColors.whatsappGreen, size: 16),
],
),
),
@@ -1045,9 +1199,15 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Text(label, style: const TextStyle(fontSize: 12, color: AppColors.textSecondaryDark)),
Text(label,
style: const TextStyle(
fontSize: 12, color: AppColors.textSecondaryDark)),
const Spacer(),
Text(value, style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w700, color: Colors.white)),
Text(value,
style: const TextStyle(
fontSize: 12.5,
fontWeight: FontWeight.w700,
color: Colors.white)),
],
),
);
@@ -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)),
File diff suppressed because it is too large Load Diff
@@ -368,7 +368,9 @@ class Grade10LabsRegistry {
/// Whether to surface draft / unreleased virtual labs for local inspection and review.
/// Defaults to true in development so that the reviewer/teacher can immediately test the labs.
static bool previewModeEnabled = true;
// Student builds must show released labs only. Draft previews stay available
// through explicit authoring/review tooling, never through the student flow.
static bool previewModeEnabled = false;
/// Exact identity lookup strictly isolated per subject.
/// When [previewModeEnabled] is active or [allowPreview] is true, returns the lab for testing.
@@ -126,9 +126,6 @@ class _SubjectVirtualLabsViewState extends State<SubjectVirtualLabsView> {
final labs = candidateLabs;
if (labs.isEmpty) {
if (widget.specializedToolWidget != null) {
return widget.specializedToolWidget!;
}
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
@@ -154,16 +151,6 @@ class _SubjectVirtualLabsViewState extends State<SubjectVirtualLabsView> {
color: Color(0xFF94A3B8), fontSize: 11.5, height: 1.6),
),
const SizedBox(height: 14),
TextButton.icon(
onPressed: () => _openGallery(context),
icon: const Icon(CupertinoIcons.square_grid_2x2, size: 16),
label: const Text('معرض المعاينة التأليفية',
style:
TextStyle(fontWeight: FontWeight.w800, fontSize: 12.5)),
style: TextButton.styleFrom(
foregroundColor: AppColors.saqelCyan,
),
),
],
),
),