Update Saqel Platform: 2026-09-02 00:55:56

This commit is contained in:
Hamza-Ayed
2026-09-02 00:55:56 +03:00
parent 1c0cd88a98
commit f4ab43d49a
18 changed files with 409 additions and 214 deletions
@@ -86,19 +86,26 @@ class GuardianChildModel {
});
factory GuardianChildModel.fromJson(Map<String, dynamic> json) {
final student = json['student'] is Map
? Map<String, dynamic>.from(json['student'])
: <String, dynamic>{};
final metrics = json['metrics'] is Map
? Map<String, dynamic>.from(json['metrics'])
: <String, dynamic>{};
final source = <String, dynamic>{...student, ...metrics, ...json};
return GuardianChildModel(
id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? '0') ?? 0,
uuid: json['uuid']?.toString() ?? '',
name: json['full_name']?.toString() ?? json['name']?.toString() ?? 'الطالب',
nationalId: json['national_id']?.toString(),
gradeLevel: json['grade_level']?.toString(),
stream: json['stream']?.toString(),
schoolName: json['school_name']?.toString() ?? 'مدرسة معتمدة',
readinessScore: json['tawjihi_readiness_score'] != null
? double.tryParse(json['tawjihi_readiness_score'].toString()) ?? 0.0
: (json['readiness_score'] != null ? double.tryParse(json['readiness_score'].toString()) ?? 0.0 : 0.0),
examsPassed: json['exams_passed_count'] is int ? json['exams_passed_count'] : int.tryParse(json['exams_passed_count']?.toString() ?? '0') ?? 0,
examsTotal: json['exams_total_count'] is int ? json['exams_total_count'] : int.tryParse(json['exams_total_count']?.toString() ?? '0') ?? 0,
id: source['id'] is int ? source['id'] : int.tryParse(source['id']?.toString() ?? '0') ?? 0,
uuid: source['uuid']?.toString() ?? '',
name: source['full_name']?.toString() ?? source['name']?.toString() ?? 'الطالب',
nationalId: source['national_id']?.toString(),
gradeLevel: source['grade_level']?.toString(),
stream: source['stream']?.toString(),
schoolName: source['school_name']?.toString() ?? 'مدرسة معتمدة',
readinessScore: source['readiness_score'] != null
? double.tryParse(source['readiness_score'].toString()) ?? 0.0
: (source['tawjihi_readiness_score'] != null ? double.tryParse(source['tawjihi_readiness_score'].toString()) ?? 0.0 : 0.0),
examsPassed: source['exams_passed_count'] is int ? source['exams_passed_count'] : int.tryParse(source['exams_passed_count']?.toString() ?? '0') ?? 0,
examsTotal: source['exams_total_count'] is int ? source['exams_total_count'] : int.tryParse(source['exams_total_count']?.toString() ?? '0') ?? 0,
);
}
}
@@ -1,6 +1,15 @@
import '../../core/config/app_config.dart';
String _absolutePlaybackUrl(String value) {
if (value.isEmpty || value.startsWith('http://') || value.startsWith('https://')) return value;
final base = AppConfig.baseUrl.replaceAll(RegExp(r'/+$'), '');
return '$base/${value.replaceFirst(RegExp(r'^/+'), '')}';
}
/// Model representing an In-Video Socratic Gatekeeping Checkpoint (فحص الفهم السقراطي)
class SocraticCheckpointModel {
final int id;
final int questionId;
final String questionText;
final int timestampSeconds;
final int rewindSecondsOnFail;
@@ -10,6 +19,7 @@ class SocraticCheckpointModel {
const SocraticCheckpointModel({
required this.id,
this.questionId = 0,
required this.questionText,
required this.timestampSeconds,
this.rewindSecondsOnFail = 45,
@@ -29,7 +39,12 @@ class SocraticCheckpointModel {
}
return SocraticCheckpointModel(
id: (json['id'] as num?)?.toInt() ?? 0,
id: json['id'] is num
? (json['id'] as num).toInt()
: int.tryParse((json['id'] ?? json['exam_id'] ?? '0').toString()) ?? 0,
questionId: json['question_id'] is num
? (json['question_id'] as num).toInt()
: int.tryParse(json['question_id']?.toString() ?? '0') ?? 0,
questionText: json['question']?.toString() ?? json['question_text']?.toString() ?? 'سؤال فحص الفهم السقراطي',
timestampSeconds: (json['timestamp_seconds'] as num?)?.toInt() ?? 120,
rewindSecondsOnFail: (json['rewind_seconds'] as num?)?.toInt() ?? 45,
@@ -105,9 +120,9 @@ class LessonPlaybackData {
}
}
final vidUrl = playback['hls_url']?.toString() ??
playback['stream_url']?.toString() ??
playback['video_url']?.toString() ?? '';
final vidUrl = _absolutePlaybackUrl(
playback['hls_url']?.toString() ?? playback['stream_url']?.toString() ?? playback['video_url']?.toString() ?? '',
);
return LessonPlaybackData(
lessonId: (lesson['id'] as num?)?.toInt() ?? 0,
@@ -143,9 +158,9 @@ class LessonVersionModel {
factory LessonVersionModel.fromJson(Map<String, dynamic> json) {
final playback = json['playback'] is Map ? json['playback'] as Map : {};
final vidUrl = playback['hls_url']?.toString() ??
playback['stream_url']?.toString() ??
playback['video_url']?.toString() ?? '';
final vidUrl = _absolutePlaybackUrl(
playback['hls_url']?.toString() ?? playback['stream_url']?.toString() ?? playback['video_url']?.toString() ?? '',
);
return LessonVersionModel(
lessonId: (json['lesson_id'] as num?)?.toInt() ?? 0,
@@ -168,8 +168,8 @@ class GuardianRepository {
Future<List<GuardianChildModel>> getDashboardChildren() async {
final res = await _api.get(AppConfig.guardianDashboardEndpoint);
if (res is Map && res['data'] is List) {
return (res['data'] as List)
if (res is Map && res['data'] is Map && (res['data']['children'] is List)) {
return (res['data']['children'] as List)
.map((item) => GuardianChildModel.fromJson(Map<String, dynamic>.from(item)))
.toList();
}
@@ -137,4 +137,19 @@ class CurriculumRepository {
throw ApiException('فشل جلب بيانات تشغيل الدرس من الخادم');
}
Future<void> saveProgress({required int lessonId, required int positionSeconds, required int watchedSeconds}) async {
await _api.post('/api/student/lessons/$lessonId/progress', body: {
'position_seconds': positionSeconds,
'watched_seconds': watchedSeconds,
});
}
Future<void> submitCheckpoint({required int examId, required int questionId, required int optionId}) async {
await _api.post('/api/exams/$examId/submit', body: {
'answers': [
{'question_id': questionId, 'selected_option_id': optionId}
],
});
}
}