Update Saqel Platform: 2026-08-31 01:52:23

This commit is contained in:
Hamza-Ayed
2026-08-31 01:52:24 +03:00
parent 4e29915383
commit ccf5f13a09
419 changed files with 19777 additions and 41 deletions
@@ -0,0 +1,104 @@
/// Real Lesson Model from /api/student/lessons
class LessonModel {
final int id;
final int courseId;
final String title;
final int sequenceOrder;
final String storageType;
final String? videoUuid;
final String? hlsUrl;
final String? thumbnailUrl;
final String? aiVideoUrl;
final int durationSeconds;
final bool isFreePreview;
final String courseTitle;
final bool isAiVersion;
final int? checkpointCount;
LessonModel({
required this.id,
required this.courseId,
required this.title,
required this.sequenceOrder,
required this.storageType,
this.videoUuid,
this.hlsUrl,
this.thumbnailUrl,
this.aiVideoUrl,
required this.durationSeconds,
required this.isFreePreview,
required this.courseTitle,
required this.isAiVersion,
this.checkpointCount,
});
factory LessonModel.fromJson(Map<String, dynamic> json) {
return LessonModel(
id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? '0') ?? 0,
courseId: json['course_id'] is int ? json['course_id'] : int.tryParse(json['course_id']?.toString() ?? '0') ?? 0,
title: json['title']?.toString() ?? 'درس تفاعلي',
sequenceOrder: json['sequence_order'] is int ? json['sequence_order'] : int.tryParse(json['sequence_order']?.toString() ?? '1') ?? 1,
storageType: json['storage_type']?.toString() ?? 'local',
videoUuid: json['video_uuid']?.toString(),
hlsUrl: json['hls_url']?.toString(),
thumbnailUrl: json['thumbnail_url']?.toString(),
aiVideoUrl: json['ai_video_url']?.toString(),
durationSeconds: json['duration_seconds'] is int ? json['duration_seconds'] : int.tryParse(json['duration_seconds']?.toString() ?? '0') ?? 0,
isFreePreview: json['is_free_preview'] == 1 || json['is_free_preview'] == true,
courseTitle: json['course_title']?.toString() ?? 'المنهاج المعتمد',
isAiVersion: json['is_ai_version'] == 1 || json['is_ai_version'] == true,
checkpointCount: json['checkpoint_count'] is int ? json['checkpoint_count'] : int.tryParse(json['checkpoint_count']?.toString() ?? '0'),
);
}
String get formattedDuration {
if (durationSeconds <= 0) return 'درس تفاعلي';
final minutes = durationSeconds ~/ 60;
final seconds = durationSeconds % 60;
return '$minutes دقيقة ${seconds > 0 ? '$seconds ثانية' : ''}';
}
}
/// Real Child Profile & Academic Progress Model for Guardian
class GuardianChildModel {
final int id;
final String uuid;
final String name;
final String? nationalId;
final String? gradeLevel;
final String? stream;
final String? schoolName;
final double readinessScore;
final int examsPassed;
final int examsTotal;
GuardianChildModel({
required this.id,
required this.uuid,
required this.name,
this.nationalId,
this.gradeLevel,
this.stream,
this.schoolName,
this.readinessScore = 0.0,
this.examsPassed = 0,
this.examsTotal = 0,
});
factory GuardianChildModel.fromJson(Map<String, dynamic> 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,
);
}
}
@@ -0,0 +1,67 @@
/// Real User Profile Model (Student / Guardian / Teacher)
class UserModel {
final int id;
final String uuid;
final String name;
final String phone;
final String role;
final String? nationalId;
final String? gradeLevel;
final String? stream;
final double? readinessScore;
final int? schoolId;
final bool isCompleted;
UserModel({
required this.id,
required this.uuid,
required this.name,
required this.phone,
required this.role,
this.nationalId,
this.gradeLevel,
this.stream,
this.readinessScore,
this.schoolId,
this.isCompleted = true,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? '0') ?? 0,
uuid: json['uuid']?.toString() ?? '',
name: json['name']?.toString() ?? json['full_name']?.toString() ?? 'مستخدم صَقِل',
phone: json['phone']?.toString() ?? '',
role: json['role']?.toString() ?? 'student',
nationalId: json['national_id']?.toString(),
gradeLevel: json['grade_level']?.toString(),
stream: json['stream']?.toString(),
readinessScore: json['readiness_score'] != null
? double.tryParse(json['readiness_score'].toString())
: null,
schoolId: json['school_id'] != null
? int.tryParse(json['school_id'].toString())
: null,
isCompleted: json['is_completed'] ?? true,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'uuid': uuid,
'name': name,
'phone': phone,
'role': role,
'national_id': nationalId,
'grade_level': gradeLevel,
'stream': stream,
'readiness_score': readinessScore,
'school_id': schoolId,
'is_completed': isCompleted,
};
}
bool get isStudent => role == 'student';
bool get isGuardian => role == 'guardian';
}
@@ -0,0 +1,178 @@
import '../../core/config/app_config.dart';
import '../../core/network/api_client.dart';
import '../../core/services/storage_service.dart';
import '../models/user_model.dart';
import '../models/lesson_model.dart';
/// Authentication Repository (OTP, National ID, me, logout)
class AuthRepository {
final ApiClient _api;
final StorageService _storage;
AuthRepository({ApiClient? api, StorageService? storage})
: _api = api ?? ApiClient(),
_storage = storage ?? StorageService();
Future<Map<String, dynamic>> requestOtp(String phoneNumber, {String role = 'student'}) async {
final res = await _api.post(
AppConfig.otpRequestEndpoint,
body: {
'phone_number': phoneNumber,
'role': role,
},
requiresAuth: false,
);
return res is Map<String, dynamic> ? res : {};
}
Future<Map<String, dynamic>> verifyOtp(String phoneNumber, String otp, {String role = 'student'}) async {
final res = await _api.post(
AppConfig.otpVerifyEndpoint,
body: {
'phone_number': phoneNumber,
'otp': otp,
'role': role,
},
requiresAuth: false,
);
if (res is Map<String, dynamic>) {
final data = res['data'];
if (data != null && data is Map) {
if (data['token'] != null) {
final token = data['token'].toString();
await _storage.saveToken(token);
await _storage.saveActiveRole(role);
if (data['user'] != null) {
final user = UserModel.fromJson(Map<String, dynamic>.from(data['user']));
await _storage.saveUser(user);
}
} else if (data['identity_token'] != null) {
await _storage.saveIdentityToken(data['identity_token'].toString());
}
}
return res;
}
return {};
}
Future<Map<String, dynamic>> verifyNationalId(String identityToken, String nationalId) async {
final res = await _api.post(
AppConfig.loginNationalIdEndpoint,
body: {
'identity_token': identityToken,
'national_id': nationalId,
},
requiresAuth: false,
);
if (res is Map<String, dynamic> && res['data'] != null) {
final data = res['data'];
if (data['token'] != null) {
final token = data['token'].toString();
await _storage.saveToken(token);
await _storage.saveActiveRole('student');
if (data['user'] != null) {
final user = UserModel.fromJson(Map<String, dynamic>.from(data['user']));
await _storage.saveUser(user);
}
}
return res;
}
throw ApiException('فشل التحقق من الرقم الوطني');
}
Future<UserModel> setupStudentProfile({
required String identityToken,
required String fullName,
required String nationalId,
String gradeLevel = 'tawjihi_2008',
String stream = 'scientific',
}) async {
final res = await _api.post(
AppConfig.studentProfileSetupEndpoint,
body: {
'identity_token': identityToken,
'full_name': fullName,
'national_id': nationalId,
'grade_level': gradeLevel,
'stream': stream,
},
requiresAuth: false,
);
if (res is Map<String, dynamic> && res['data'] != null) {
final data = res['data'];
if (data['token'] != null) {
final token = data['token'].toString();
await _storage.saveToken(token);
await _storage.saveActiveRole('student');
}
if (data['user'] != null) {
final user = UserModel.fromJson(Map<String, dynamic>.from(data['user']));
await _storage.saveUser(user);
return user;
}
}
throw ApiException('فشل استكمال فتح الملف الأكاديمي');
}
Future<UserModel?> getMe() async {
try {
final res = await _api.get(AppConfig.meEndpoint);
if (res is Map && res['data'] != null) {
final user = UserModel.fromJson(Map<String, dynamic>.from(res['data']));
await _storage.saveUser(user);
return user;
}
} catch (_) {
return null;
}
return null;
}
Future<void> logout() async {
try {
await _api.post(AppConfig.logoutEndpoint);
} catch (_) {}
await _storage.clearSession();
}
}
/// Student Repository (Lessons, Curriculum, Progress)
class StudentRepository {
final ApiClient _api;
StudentRepository({ApiClient? api}) : _api = api ?? ApiClient();
Future<List<LessonModel>> getLessons({String? gradeLevel, String? stream}) async {
final queryParams = <String, dynamic>{};
if (gradeLevel != null) queryParams['grade_level'] = gradeLevel;
if (stream != null) queryParams['stream'] = stream;
final res = await _api.get(AppConfig.studentLessonsEndpoint, queryParams: queryParams);
if (res is Map && res['data'] is List) {
return (res['data'] as List)
.map((item) => LessonModel.fromJson(Map<String, dynamic>.from(item)))
.toList();
}
return [];
}
}
/// Guardian Repository (Dashboard, Linked Children, Real Progress)
class GuardianRepository {
final ApiClient _api;
GuardianRepository({ApiClient? api}) : _api = api ?? ApiClient();
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)
.map((item) => GuardianChildModel.fromJson(Map<String, dynamic>.from(item)))
.toList();
}
return [];
}
}