Harden published curriculum and student flows
This commit is contained in:
+698
-591
File diff suppressed because it is too large
Load Diff
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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
+67
-81
@@ -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)),
|
||||
|
||||
+723
-949
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user