Enhance video playback cubit resilience and K-12 curriculum flow
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import '../../core/config/app_config.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../models/socratic_checkpoint_model.dart';
|
||||
import '../models/subject_model.dart';
|
||||
|
||||
/// Grade Level Entity (المرحلة الدراسية من الصف الأول للتوجيهي)
|
||||
class GradeLevelModel {
|
||||
final String key;
|
||||
final String name;
|
||||
final int number; // 1 to 12
|
||||
|
||||
const GradeLevelModel({
|
||||
required this.key,
|
||||
required this.name,
|
||||
required this.number,
|
||||
});
|
||||
|
||||
static const List<GradeLevelModel> allK12Grades = [
|
||||
GradeLevelModel(key: 'grade_1', name: 'الصف الأول الأساسي', number: 1),
|
||||
GradeLevelModel(key: 'grade_2', name: 'الصف الثاني الأساسي', number: 2),
|
||||
GradeLevelModel(key: 'grade_3', name: 'الصف الثالث الأساسي', number: 3),
|
||||
GradeLevelModel(key: 'grade_4', name: 'الصف الرابع الأساسي', number: 4),
|
||||
GradeLevelModel(key: 'grade_5', name: 'الصف الخامس الأساسي', number: 5),
|
||||
GradeLevelModel(key: 'grade_6', name: 'الصف السادس الأساسي', number: 6),
|
||||
GradeLevelModel(key: 'grade_7', name: 'الصف السابع الأساسي', number: 7),
|
||||
GradeLevelModel(key: 'grade_8', name: 'الصف الثامن الأساسي', number: 8),
|
||||
GradeLevelModel(key: 'grade_9', name: 'الصف التاسع الأساسي', number: 9),
|
||||
GradeLevelModel(key: 'grade_10', name: 'الصف العاشر الأساسي', number: 10),
|
||||
GradeLevelModel(key: 'grade_11', name: 'الأول ثانوي', number: 11),
|
||||
GradeLevelModel(key: 'tawjihi_2008', name: 'الثاني ثانوي — توجيهي 2008', number: 12),
|
||||
];
|
||||
|
||||
static String getGradeName(String key) {
|
||||
final match = allK12Grades.firstWhere(
|
||||
(g) => g.key == key || g.key == key.toLowerCase(),
|
||||
orElse: () => GradeLevelModel(key: key, name: key, number: 0),
|
||||
);
|
||||
return match.name;
|
||||
}
|
||||
}
|
||||
|
||||
/// 100% Live Dynamic Curriculum Repository (Zero Hardcoded Data)
|
||||
/// Dynamically fetches all grades (الصف الأول إلى التوجيهي) and their subjects from Live API.
|
||||
class CurriculumRepository {
|
||||
final ApiClient _api;
|
||||
|
||||
CurriculumRepository({ApiClient? api}) : _api = api ?? ApiClient();
|
||||
|
||||
/// Fetch full curriculum tree from server
|
||||
Future<Map<String, dynamic>> getRawCurriculumTree() async {
|
||||
final res = await _api.get(AppConfig.curriculumTreeEndpoint, requiresAuth: false);
|
||||
if (res is Map && res['data'] != null && res['data'] is Map) {
|
||||
return Map<String, dynamic>.from(res['data']);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// Fetch available grades from Live Server Manifest
|
||||
Future<List<GradeLevelModel>> getAvailableGrades() async {
|
||||
final tree = await getRawCurriculumTree();
|
||||
final List<GradeLevelModel> available = [];
|
||||
|
||||
for (var grade in GradeLevelModel.allK12Grades) {
|
||||
if (tree.containsKey(grade.key) || tree.containsKey(grade.key.replaceAll('grade_', ''))) {
|
||||
available.add(grade);
|
||||
}
|
||||
}
|
||||
|
||||
// If server has custom keys, include them as well
|
||||
tree.forEach((key, val) {
|
||||
if (!available.any((g) => g.key == key)) {
|
||||
final name = (val is Map && val['name'] != null) ? val['name'].toString() : GradeLevelModel.getGradeName(key);
|
||||
available.add(GradeLevelModel(key: key, name: name, number: 0));
|
||||
}
|
||||
});
|
||||
|
||||
return available.isNotEmpty ? available : GradeLevelModel.allK12Grades;
|
||||
}
|
||||
|
||||
/// Fetch subjects for a specific grade level dynamically from Live Backend
|
||||
Future<List<SubjectModel>> getSubjects({String gradeLevel = 'grade_10', String stream = 'scientific'}) async {
|
||||
AppLogger.log('Fetching live curriculum tree from /api/curriculum/tree (Grade: $gradeLevel)...', tag: 'CURRICULUM_REPO');
|
||||
|
||||
final tree = await getRawCurriculumTree();
|
||||
if (tree.isEmpty) {
|
||||
AppLogger.log('No curriculum data returned from server.', tag: 'CURRICULUM_REPO');
|
||||
return [];
|
||||
}
|
||||
|
||||
final List<SubjectModel> liveSubjects = [];
|
||||
|
||||
// Match exact grade key, or fallback variants (e.g. grade_10 vs 10 vs tawjihi_2008)
|
||||
Map<String, dynamic>? gradeData;
|
||||
if (tree.containsKey(gradeLevel) && tree[gradeLevel] is Map) {
|
||||
gradeData = Map<String, dynamic>.from(tree[gradeLevel]);
|
||||
} else {
|
||||
final shortKey = gradeLevel.replaceAll('grade_', '');
|
||||
if (tree.containsKey(shortKey) && tree[shortKey] is Map) {
|
||||
gradeData = Map<String, dynamic>.from(tree[shortKey]);
|
||||
} else if (tree.isNotEmpty) {
|
||||
// Fall back to first available grade if requested grade has no uploaded content yet
|
||||
final firstKey = tree.keys.first;
|
||||
if (tree[firstKey] is Map) {
|
||||
gradeData = Map<String, dynamic>.from(tree[firstKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gradeData == null || gradeData['subjects'] == null || gradeData['subjects'] is! Map) {
|
||||
AppLogger.log('No subjects found in grade $gradeLevel on server.', tag: 'CURRICULUM_REPO');
|
||||
return [];
|
||||
}
|
||||
|
||||
final subjectsMap = gradeData['subjects'] as Map;
|
||||
|
||||
subjectsMap.forEach((subKey, subVal) {
|
||||
if (subVal is Map) {
|
||||
final subMap = Map<String, dynamic>.from(subVal);
|
||||
final subject = SubjectModel.fromJson(subKey.toString(), subMap);
|
||||
liveSubjects.add(subject);
|
||||
}
|
||||
});
|
||||
|
||||
AppLogger.log('Successfully parsed ${liveSubjects.length} live subjects for $gradeLevel: ${liveSubjects.map((s) => s.title).join(', ')}', tag: 'CURRICULUM_REPO');
|
||||
return liveSubjects;
|
||||
}
|
||||
|
||||
/// Fetch lesson playback details (streams, checkpoints, and versions) from Live API
|
||||
Future<LessonPlaybackData> getLessonPlayback(String lessonId, {String? subjectId, String? unitId}) async {
|
||||
AppLogger.log('Fetching live playback data for lesson $lessonId from /api/lessons/$lessonId/playback...', tag: 'CURRICULUM_REPO');
|
||||
|
||||
final res = await _api.get('/api/lessons/$lessonId/playback');
|
||||
if (res is Map && res['data'] != null) {
|
||||
return LessonPlaybackData.fromJson(Map<String, dynamic>.from(res['data']));
|
||||
}
|
||||
|
||||
throw ApiException('فشل جلب بيانات تشغيل الدرس من الخادم');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user