291 lines
12 KiB
Dart
291 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../core/theme/app_colors.dart';
|
|
|
|
/// Model representing a Curriculum Subject (المادة الدراسية)
|
|
class SubjectModel {
|
|
final String id;
|
|
final String title;
|
|
final String englishTitle;
|
|
final String iconCode;
|
|
final Color primaryColor;
|
|
final Color secondaryColor;
|
|
final String gradeLevel;
|
|
final String stream;
|
|
final int totalUnits;
|
|
final int totalLessons;
|
|
final double masteryScore; // 0 - 100%
|
|
final List<CurriculumUnitModel> units;
|
|
final List<ResourceItemModel> textbooks;
|
|
final List<ResourceItemModel> worksheets;
|
|
final List<SubjectExamModel> exams;
|
|
|
|
const SubjectModel({
|
|
required this.id,
|
|
required this.title,
|
|
required this.englishTitle,
|
|
required this.iconCode,
|
|
required this.primaryColor,
|
|
required this.secondaryColor,
|
|
this.gradeLevel = 'grade_10',
|
|
this.stream = 'scientific',
|
|
this.totalUnits = 0,
|
|
this.totalLessons = 0,
|
|
this.masteryScore = 85.0,
|
|
this.units = const [],
|
|
this.textbooks = const [],
|
|
this.worksheets = const [],
|
|
this.exams = const [],
|
|
});
|
|
|
|
factory SubjectModel.fromJson(String id, Map<String, dynamic> json) {
|
|
final title = json['name']?.toString() ?? json['title']?.toString() ?? id;
|
|
final englishTitle = json['english_name']?.toString() ?? '';
|
|
|
|
// Parse units if available
|
|
final List<CurriculumUnitModel> parsedUnits = [];
|
|
if (json['semesters'] != null && json['semesters'] is Map) {
|
|
final semesters = json['semesters'] as Map;
|
|
semesters.forEach((semKey, semVal) {
|
|
if (semVal is Map && semVal['units'] != null && semVal['units'] is Map) {
|
|
final unitsMap = semVal['units'] as Map;
|
|
unitsMap.forEach((uKey, uVal) {
|
|
if (uVal is Map) {
|
|
parsedUnits.add(CurriculumUnitModel.fromJson(
|
|
uKey.toString(),
|
|
Map<String, dynamic>.from(uVal),
|
|
semester: semKey.toString(),
|
|
));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Parse textbooks
|
|
final List<ResourceItemModel> parsedTextbooks = [];
|
|
final List<ResourceItemModel> parsedWorksheets = [];
|
|
if (json['resources'] != null && json['resources'] is Map) {
|
|
final res = json['resources'] as Map;
|
|
if (res['textbooks'] != null && res['textbooks']['items'] is List) {
|
|
for (var item in res['textbooks']['items']) {
|
|
if (item is Map) {
|
|
parsedTextbooks.add(ResourceItemModel.fromJson(Map<String, dynamic>.from(item)));
|
|
}
|
|
}
|
|
}
|
|
if (res['worksheets'] != null && res['worksheets']['items'] is List) {
|
|
for (var item in res['worksheets']['items']) {
|
|
if (item is Map) {
|
|
parsedWorksheets.add(ResourceItemModel.fromJson(Map<String, dynamic>.from(item)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
final styling = getSubjectStyling(id);
|
|
|
|
return SubjectModel(
|
|
id: id,
|
|
title: title,
|
|
englishTitle: englishTitle.isNotEmpty ? englishTitle : styling.englishTitle,
|
|
iconCode: styling.iconCode,
|
|
primaryColor: styling.primaryColor,
|
|
secondaryColor: styling.secondaryColor,
|
|
gradeLevel: json['grade_level']?.toString() ?? 'grade_10',
|
|
stream: json['stream']?.toString() ?? 'scientific',
|
|
totalUnits: parsedUnits.isNotEmpty ? parsedUnits.length : styling.defaultUnits,
|
|
totalLessons: parsedUnits.fold(0, (sum, u) => sum + u.lessons.length),
|
|
masteryScore: (json['mastery_score'] as num?)?.toDouble() ?? 88.0,
|
|
units: parsedUnits,
|
|
textbooks: parsedTextbooks,
|
|
worksheets: parsedWorksheets,
|
|
);
|
|
}
|
|
|
|
static SubjectStyling getSubjectStyling(String key) {
|
|
final k = key.toLowerCase();
|
|
if (k.contains('math') || k.contains('رياضيات')) {
|
|
return const SubjectStyling('الرياضيات', 'Mathematics', 'calculator', Color(0xFF0071E3), Color(0xFF00F5D4), 4);
|
|
} else if (k.contains('arabic') || k.contains('عربي') || k.contains('لغتي')) {
|
|
return const SubjectStyling('العربية لغتي', 'Arabic Language', 'book_closed_fill', Color(0xFF30D158), Color(0xFF10B981), 5);
|
|
} else if (k.contains('english') || k.contains('انجليز') || k.contains('إنجليز')) {
|
|
return const SubjectStyling('اللغة الإنجليزية', 'English Language', 'chat_bubble_2_fill', Color(0xFF5E5CE6), Color(0xFFBF5AF2), 5);
|
|
} else if (k.contains('islamic') || k.contains('إسلامي') || k.contains('دين')) {
|
|
return const SubjectStyling('التربية الإسلامية', 'Islamic Studies', 'moon_stars_fill', Color(0xFF00B4D8), Color(0xFF48CAE4), 4);
|
|
} else if (k.contains('physic') || k.contains('فيزياء')) {
|
|
return const SubjectStyling('الفيزياء', 'Physics', 'bolt_fill', Color(0xFFFF9F0A), Color(0xFFFFD60A), 4);
|
|
} else if (k.contains('chem') || k.contains('كيمياء')) {
|
|
return const SubjectStyling('الكيمياء', 'Chemistry', 'flame_fill', Color(0xFFFF375F), Color(0xFFFF2D55), 4);
|
|
} else if (k.contains('bio') || k.contains('أحياء') || k.contains('حياتية')) {
|
|
return const SubjectStyling('العلوم الحياتية', 'Biology', 'tree_fill', Color(0xFF34C759), Color(0xFF30D158), 4);
|
|
} else if (k.contains('earth') || k.contains('أرض') || k.contains('بيئة') || k.contains('geology')) {
|
|
return const SubjectStyling('علوم الأرض والبيئة', 'Earth & Environment', 'globe', Color(0xFF0A84FF), Color(0xFF64D2FF), 3);
|
|
} else if (k.contains('history') || k.contains('تاريخ')) {
|
|
return const SubjectStyling('التاريخ', 'History', 'building_columns_fill', Color(0xFFAC8E68), Color(0xFFD4AF37), 3);
|
|
} else if (k.contains('geo') || k.contains('جغرافيا')) {
|
|
return const SubjectStyling('الجغرافيا', 'Geography', 'map_fill', Color(0xFF32D74B), Color(0xFF66D4CF), 3);
|
|
} else if (k.contains('civic') || k.contains('وطنية') || k.contains('مدنية')) {
|
|
return const SubjectStyling('التربية الوطنية والمدنية', 'Civics', 'person_2_fill', Color(0xFF64D2FF), Color(0xFF0A84FF), 3);
|
|
} else if (k.contains('digital') || k.contains('حاسوب') || k.contains('رقمية')) {
|
|
return const SubjectStyling('المهارات الرقمية', 'Digital Skills', 'desktopcomputer', Color(0xFF00F5D4), Color(0xFF0071E3), 4);
|
|
} else if (k.contains('finance') || k.contains('مالية')) {
|
|
return const SubjectStyling('الثقافة المالية', 'Financial Literacy', 'creditcard_fill', Color(0xFF30D158), Color(0xFFFFD60A), 3);
|
|
} else if (k.contains('vocational') || k.contains('مهنية')) {
|
|
return const SubjectStyling('التربية المهنية', 'Vocational Education', 'wrench_fill', Color(0xFFFF9F0A), Color(0xFFFF453A), 3);
|
|
} else if (k.contains('art') || k.contains('فنية')) {
|
|
return const SubjectStyling('التربية الفنية', 'Art', 'paintbrush_fill', Color(0xFFFF375F), Color(0xFFBF5AF2), 2);
|
|
} else if (k.contains('sport') || k.contains('رياضية')) {
|
|
return const SubjectStyling('التربية الرياضية', 'Physical Education', 'sportscourt_fill', Color(0xFF0A84FF), Color(0xFF30D158), 2);
|
|
}
|
|
return const SubjectStyling('مبحث دراسي', 'Curriculum Subject', 'book_fill', AppColors.appleBlue, AppColors.saqelCyan, 3);
|
|
}
|
|
}
|
|
|
|
class SubjectStyling {
|
|
final String defaultTitle;
|
|
final String englishTitle;
|
|
final String iconCode;
|
|
final Color primaryColor;
|
|
final Color secondaryColor;
|
|
final int defaultUnits;
|
|
|
|
const SubjectStyling(
|
|
this.defaultTitle,
|
|
this.englishTitle,
|
|
this.iconCode,
|
|
this.primaryColor,
|
|
this.secondaryColor,
|
|
this.defaultUnits,
|
|
);
|
|
}
|
|
|
|
/// Model representing a Curriculum Unit (الوحدة الدراسية)
|
|
class CurriculumUnitModel {
|
|
final String id;
|
|
final String name;
|
|
final String semester;
|
|
final List<CurriculumLessonItemModel> lessons;
|
|
|
|
const CurriculumUnitModel({
|
|
required this.id,
|
|
required this.name,
|
|
this.semester = 'semester_1',
|
|
this.lessons = const [],
|
|
});
|
|
|
|
factory CurriculumUnitModel.fromJson(String id, Map<String, dynamic> json, {String semester = 'semester_1'}) {
|
|
final List<CurriculumLessonItemModel> parsedLessons = [];
|
|
if (json['lessons'] != null && json['lessons'] is List) {
|
|
for (var l in json['lessons']) {
|
|
if (l is Map) {
|
|
parsedLessons.add(CurriculumLessonItemModel.fromJson(Map<String, dynamic>.from(l)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return CurriculumUnitModel(
|
|
id: id,
|
|
name: json['name']?.toString() ?? id,
|
|
semester: semester,
|
|
lessons: parsedLessons,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Model representing a Lesson Item inside a Unit
|
|
class CurriculumLessonItemModel {
|
|
final String id;
|
|
final String title;
|
|
final List<String> outcomes;
|
|
final String? markdownFilePath;
|
|
final int durationSeconds;
|
|
final int checkpointsCount;
|
|
final bool isCompleted;
|
|
final bool hasVideo;
|
|
|
|
const CurriculumLessonItemModel({
|
|
required this.id,
|
|
required this.title,
|
|
this.outcomes = const [],
|
|
this.markdownFilePath,
|
|
this.durationSeconds = 1200, // 20 mins default
|
|
this.checkpointsCount = 3,
|
|
this.isCompleted = false,
|
|
this.hasVideo = false,
|
|
});
|
|
|
|
factory CurriculumLessonItemModel.fromJson(Map<String, dynamic> json) {
|
|
final outcomesRaw = json['outcomes'];
|
|
final List<String> outs = [];
|
|
if (outcomesRaw is List) {
|
|
for (var o in outcomesRaw) {
|
|
if (o != null) outs.add(o.toString());
|
|
}
|
|
}
|
|
|
|
final filePath = json['file']?.toString();
|
|
// Default video availability: true for core Grade 10 subjects (Math, Physics, English) and explicitly tagged lessons
|
|
final bool isCoreTenthSubjectLesson = filePath != null && (
|
|
filePath.contains('math_10/semester_1') ||
|
|
filePath.contains('physics_10/semester_1') ||
|
|
filePath.contains('english_10/semester_1') ||
|
|
filePath.contains('lesson_01') ||
|
|
filePath.contains('lesson_1a')
|
|
);
|
|
|
|
final bool hasVideoExplicit = (json['has_video'] as bool?) ??
|
|
((json['video_url'] != null && json['video_url'].toString().isNotEmpty) || isCoreTenthSubjectLesson);
|
|
|
|
return CurriculumLessonItemModel(
|
|
id: json['id']?.toString() ?? 'lesson_${DateTime.now().millisecondsSinceEpoch}',
|
|
title: json['title']?.toString() ?? 'درس بدون عنوان',
|
|
outcomes: outs,
|
|
markdownFilePath: filePath,
|
|
durationSeconds: (json['duration_seconds'] as num?)?.toInt() ?? 1200,
|
|
checkpointsCount: (json['checkpoints_count'] as num?)?.toInt() ?? 3,
|
|
isCompleted: (json['is_completed'] as bool?) ?? false,
|
|
hasVideo: hasVideoExplicit,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Model representing a Resource Item (PDF Textbook or Worksheet)
|
|
class ResourceItemModel {
|
|
final String title;
|
|
final String filePath;
|
|
final String type; // textbook, worksheet, summary
|
|
|
|
const ResourceItemModel({
|
|
required this.title,
|
|
required this.filePath,
|
|
this.type = 'textbook',
|
|
});
|
|
|
|
factory ResourceItemModel.fromJson(Map<String, dynamic> json) {
|
|
return ResourceItemModel(
|
|
title: json['title']?.toString() ?? 'ملف وزاري',
|
|
filePath: json['file']?.toString() ?? '',
|
|
type: json['type']?.toString() ?? 'textbook',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Model representing an Exam / Question Bank item for the subject
|
|
class SubjectExamModel {
|
|
final String id;
|
|
final String title;
|
|
final int questionsCount;
|
|
final int durationMinutes;
|
|
final double targetScore;
|
|
final String difficulty; // easy, medium, hard, ministerial
|
|
|
|
const SubjectExamModel({
|
|
required this.id,
|
|
required this.title,
|
|
this.questionsCount = 20,
|
|
this.durationMinutes = 40,
|
|
this.targetScore = 100.0,
|
|
this.difficulty = 'ministerial',
|
|
});
|
|
}
|