Update Saqel Platform: 2026-09-08 18:19:28

This commit is contained in:
Hamza-Ayed
2026-09-08 18:19:28 +03:00
parent 75451ebc3f
commit e5cbd89323
22 changed files with 1537 additions and 595 deletions
@@ -47,16 +47,22 @@ class TeacherRepository {
// ============================================================================
Future<Map<String, dynamic>> requestOtp(String phoneNumber) async {
AppLogger.log('Requesting Teacher OTP for $phoneNumber...', tag: 'TEACHER_REPO');
AppLogger.log('Requesting Teacher OTP for $phoneNumber...',
tag: 'TEACHER_REPO');
try {
final response = await _client.post(
Uri.parse('$baseUrl${AppConfig.otpRequestEndpoint}'),
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
body: json.encode({
'phone_number': phoneNumber,
'role': 'teacher',
}),
).timeout(const Duration(seconds: 30));
final response = await _client
.post(
Uri.parse('$baseUrl${AppConfig.otpRequestEndpoint}'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: json.encode({
'phone_number': phoneNumber,
'role': 'teacher',
}),
)
.timeout(const Duration(seconds: 30));
return json.decode(response.body);
} catch (e) {
@@ -65,20 +71,27 @@ class TeacherRepository {
}
}
Future<Map<String, dynamic>> verifyOtp(String phoneNumber, String otp, {String? fullName}) async {
AppLogger.log('Verifying Teacher OTP for $phoneNumber...', tag: 'TEACHER_REPO');
Future<Map<String, dynamic>> verifyOtp(String phoneNumber, String otp,
{String? fullName}) async {
AppLogger.log('Verifying Teacher OTP for $phoneNumber...',
tag: 'TEACHER_REPO');
try {
final response = await _client.post(
Uri.parse('$baseUrl${AppConfig.otpVerifyEndpoint}'),
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
body: json.encode({
'phone_number': phoneNumber,
'otp': otp,
'role': 'teacher',
if (fullName != null) 'full_name': fullName,
'device_fingerprint': 'saqel_teacher_flutter',
}),
).timeout(const Duration(seconds: 30));
final response = await _client
.post(
Uri.parse('$baseUrl${AppConfig.otpVerifyEndpoint}'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: json.encode({
'phone_number': phoneNumber,
'otp': otp,
'role': 'teacher',
if (fullName != null) 'full_name': fullName,
'device_fingerprint': 'saqel_teacher_flutter',
}),
)
.timeout(const Duration(seconds: 30));
final decoded = json.decode(response.body);
if (decoded['status'] == 'success' && decoded['data']?['token'] != null) {
@@ -95,10 +108,12 @@ class TeacherRepository {
Future<Map<String, dynamic>?> getMe() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.meEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.meEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
return json.decode(response.body);
@@ -114,10 +129,12 @@ class TeacherRepository {
Future<TeacherProfileModel> getProfileStatus() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.profileStatusEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.profileStatusEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
@@ -141,17 +158,19 @@ class TeacherRepository {
}) async {
try {
final headers = await _authHeaders();
final response = await _client.post(
Uri.parse('$baseUrl${AppConfig.profileSetupEndpoint}'),
headers: headers,
body: json.encode({
'full_name': fullName,
'specialization': specialization,
'school_name': schoolName,
'grades_taught': gradesTaught,
'bio': bio,
}),
).timeout(const Duration(seconds: 5));
final response = await _client
.post(
Uri.parse('$baseUrl${AppConfig.profileSetupEndpoint}'),
headers: headers,
body: json.encode({
'full_name': fullName,
'specialization': specialization,
'school_name': schoolName,
'grades_taught': gradesTaught,
'bio': bio,
}),
)
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
@@ -181,10 +200,12 @@ class TeacherRepository {
Future<List<ChatConversationModel>> getConversations() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.chatConversationsEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.chatConversationsEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
@@ -192,7 +213,7 @@ class TeacherRepository {
final list = (decoded['data'] as List)
.map((c) => ChatConversationModel.fromJson(c))
.toList();
if (list.isNotEmpty) return list;
return list;
}
}
} catch (_) {}
@@ -203,14 +224,18 @@ class TeacherRepository {
Future<List<ChatMessageModel>> getMessages(int otherUserId) async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.chatMessagesEndpoint}?other_user_id=$otherUserId'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse(
'$baseUrl${AppConfig.chatMessagesEndpoint}?other_user_id=$otherUserId'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
if (decoded['status'] == 'success' && decoded['data']?['messages'] is List) {
if (decoded['status'] == 'success' &&
decoded['data']?['messages'] is List) {
return (decoded['data']['messages'] as List)
.map((m) => ChatMessageModel.fromJson(m))
.toList();
@@ -229,16 +254,18 @@ class TeacherRepository {
}) async {
try {
final headers = await _authHeaders();
final response = await _client.post(
Uri.parse('$baseUrl${AppConfig.chatMessagesEndpoint}'),
headers: headers,
body: json.encode({
'receiver_id': receiverId,
'message': message,
'message_type': messageType,
if (mediaUrl != null) 'media_url': mediaUrl,
}),
).timeout(const Duration(seconds: 5));
final response = await _client
.post(
Uri.parse('$baseUrl${AppConfig.chatMessagesEndpoint}'),
headers: headers,
body: json.encode({
'receiver_id': receiverId,
'message': message,
'message_type': messageType,
if (mediaUrl != null) 'media_url': mediaUrl,
}),
)
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200 || response.statusCode == 201) {
final decoded = json.decode(response.body);
@@ -265,25 +292,28 @@ class TeacherRepository {
}) async {
try {
final headers = await _authHeaders();
final response = await _client.post(
Uri.parse('$baseUrl${AppConfig.broadcastEndpoint}'),
headers: headers,
body: json.encode({
'title': title,
'message': message,
'message_type': messageType,
if (mediaUrl != null) 'media_url': mediaUrl,
'grade_level': gradeLevel,
'course_id': courseId,
}),
).timeout(const Duration(seconds: 5));
final response = await _client
.post(
Uri.parse('$baseUrl${AppConfig.broadcastEndpoint}'),
headers: headers,
body: json.encode({
'title': title,
'message': message,
'message_type': messageType,
if (mediaUrl != null) 'media_url': mediaUrl,
'grade_level': gradeLevel,
'course_id': courseId,
}),
)
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
return decoded['status'] == 'success';
}
} catch (e) {
AppLogger.error('broadcastAnnouncement notice', error: e, tag: 'TEACHER_REPO');
AppLogger.error('broadcastAnnouncement notice',
error: e, tag: 'TEACHER_REPO');
}
return false;
}
@@ -299,15 +329,17 @@ class TeacherRepository {
}) async {
try {
final headers = await _authHeaders();
final response = await _client.post(
Uri.parse('$baseUrl${AppConfig.auditVideoEndpoint}'),
headers: headers,
body: json.encode({
'lesson_title': title,
'duration_minutes': durationMinutes,
'subject': subject,
}),
).timeout(const Duration(seconds: 4));
final response = await _client
.post(
Uri.parse('$baseUrl${AppConfig.auditVideoEndpoint}'),
headers: headers,
body: json.encode({
'lesson_title': title,
'duration_minutes': durationMinutes,
'subject': subject,
}),
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
@@ -325,6 +357,7 @@ class TeacherRepository {
required double durationMinutes,
required String gradeLevel,
required String subject,
required String curriculumKey,
String? fileName,
double? fileSizeMb,
String? filePath,
@@ -343,6 +376,7 @@ class TeacherRepository {
'duration_minutes': durationMinutes.toString(),
'grade_level': gradeLevel,
'subject': subject,
'curriculum_key': curriculumKey,
});
final resolvedName = fileName ?? 'lesson.mp4';
@@ -362,24 +396,49 @@ class TeacherRepository {
throw StateError('لم يتم اختيار ملف فيديو صالح للرفع.');
}
final streamed = await _client.send(request).timeout(const Duration(minutes: 10));
final streamed =
await _client.send(request).timeout(const Duration(minutes: 10));
final response = await http.Response.fromStream(streamed);
if (response.statusCode == 200 || response.statusCode == 201) {
return json.decode(response.body);
}
} catch (_) {}
final decoded = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode == 200 || response.statusCode == 201)
return decoded;
throw StateError(
decoded['message']?.toString() ?? 'رفض الخادم رفع الحصة.');
} on StateError {
rethrow;
} catch (error) {
throw StateError('تعذر رفع الحصة: $error');
}
}
throw StateError('لم يتم رفع الحصة أو نشرها على الخادم.');
Future<Map<String, dynamic>> getCurriculumTree() async {
final headers = await _authHeaders();
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.curriculumTreeEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 30));
final decoded = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode == 200 &&
decoded['status'] == 'success' &&
decoded['data'] is Map) {
return Map<String, dynamic>.from(decoded['data'] as Map);
}
throw StateError(
decoded['message']?.toString() ?? 'تعذر تحميل شجرة المنهاج.');
}
Future<Map<String, dynamic>> getMonetizationDashboard() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.monetizationEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.monetizationEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
@@ -398,21 +457,24 @@ class TeacherRepository {
}) async {
try {
final headers = await _authHeaders();
final response = await _client.post(
Uri.parse('$baseUrl${AppConfig.cliqPayoutEndpoint}'),
headers: headers,
body: json.encode({
'cliq_alias': cliqAlias,
'amount_jod': amountJod,
}),
).timeout(const Duration(seconds: 5));
final response = await _client
.post(
Uri.parse('$baseUrl${AppConfig.cliqPayoutEndpoint}'),
headers: headers,
body: json.encode({
'cliq_alias': cliqAlias,
'amount_jod': amountJod,
}),
)
.timeout(const Duration(seconds: 5));
if (response.statusCode == 200 || response.statusCode == 201) {
final decoded = json.decode(response.body);
return TeacherPayoutRequestModel(
cliqAlias: cliqAlias,
amountJod: amountJod,
requestedAt: 'اليوم، ${DateTime.now().hour}:${DateTime.now().minute.toString().padLeft(2, '0')}',
requestedAt:
'اليوم، ${DateTime.now().hour}:${DateTime.now().minute.toString().padLeft(2, '0')}',
status: decoded['queue_status'] ?? decoded['status'] ?? 'queued',
);
}
@@ -424,22 +486,25 @@ class TeacherRepository {
Future<List<TeacherHomeworkModel>> getHomeworks() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.qnaEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.qnaEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
if (decoded['status'] == 'success' && decoded['data']?['homeworks'] is List) {
if (decoded['status'] == 'success' &&
decoded['data']?['homeworks'] is List) {
return (decoded['data']['homeworks'] as List)
.map((h) => TeacherHomeworkModel(
id: h['id'].toString(),
title: h['title']?.toString() ?? '',
className: h['class_name']?.toString() ?? '',
submitted: h['submitted']?.toString() ?? '0 من 38',
submitted: h['submitted']?.toString() ?? '',
dueDate: h['due_date']?.toString() ?? '',
averageScore: h['average_score']?.toString() ?? '90%',
averageScore: h['average_score']?.toString() ?? '',
status: h['status']?.toString() ?? 'active',
))
.toList();
@@ -453,14 +518,17 @@ class TeacherRepository {
Future<List<StudentDoubtModel>> getStudentDoubts() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.qnaEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.qnaEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
if (decoded['status'] == 'success' && decoded['data']?['doubts'] is List) {
if (decoded['status'] == 'success' &&
decoded['data']?['doubts'] is List) {
return (decoded['data']['doubts'] as List)
.map((d) => StudentDoubtModel(
id: d['id'].toString(),
@@ -483,10 +551,12 @@ class TeacherRepository {
Future<List<TeacherCourseModel>> getPublishedCourses() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.coursesEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.coursesEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
@@ -494,13 +564,15 @@ class TeacherRepository {
return (decoded['data'] as List).map((c) {
return TeacherCourseModel(
id: c['id'].toString(),
title: c['title'] ?? 'الفيزياء — الصف العاشر',
grade: 'الصف العاشر الأساسي',
totalLessons: int.tryParse(c['lessons_total']?.toString() ?? '0') ?? 0,
militaryStudents: 83,
externalSubscribers: 0,
priceJod: double.tryParse(c['price_jod']?.toString() ?? '20.0') ?? 20.0,
netRevenueJod: 0.0,
title: c['title']?.toString() ?? '',
grade: c['grade_level']?.toString() ?? '',
totalLessons:
int.tryParse(c['lessons_total']?.toString() ?? '0') ?? 0,
militaryStudents: (c['military_students'] as num?)?.toInt() ?? 0,
externalSubscribers: (c['external_subscribers'] as num?)?.toInt() ?? 0,
priceJod:
double.tryParse(c['price_jod']?.toString() ?? '') ?? 0,
netRevenueJod: double.tryParse(c['net_revenue_jod']?.toString() ?? '') ?? 0,
);
}).toList();
}
@@ -517,10 +589,12 @@ class TeacherRepository {
Future<Map<String, dynamic>> getMyReputation() async {
try {
final headers = await _authHeaders();
final response = await _client.get(
Uri.parse('$baseUrl${AppConfig.reputationEndpoint}'),
headers: headers,
).timeout(const Duration(seconds: 4));
final response = await _client
.get(
Uri.parse('$baseUrl${AppConfig.reputationEndpoint}'),
headers: headers,
)
.timeout(const Duration(seconds: 4));
if (response.statusCode == 200) {
final decoded = json.decode(response.body);