Update Saqel Platform: 2026-09-10 12:26:22
This commit is contained in:
@@ -387,6 +387,7 @@ class TeacherRepository {
|
||||
required String gradeLevel,
|
||||
required String subject,
|
||||
required String curriculumKey,
|
||||
required String videoVersionId,
|
||||
String? fileName,
|
||||
double? fileSizeMb,
|
||||
String? filePath,
|
||||
@@ -410,6 +411,7 @@ class TeacherRepository {
|
||||
'grade_level': gradeLevel,
|
||||
'subject': subject,
|
||||
'curriculum_key': curriculumKey,
|
||||
'video_version_id': videoVersionId,
|
||||
});
|
||||
AppLogger.request(
|
||||
method: 'POST',
|
||||
@@ -420,6 +422,7 @@ class TeacherRepository {
|
||||
'grade_level': gradeLevel,
|
||||
'subject': subject,
|
||||
'curriculum_key': curriculumKey,
|
||||
'video_version_id': videoVersionId,
|
||||
'file_name': fileName,
|
||||
'file_size_mb': fileSizeMb
|
||||
},
|
||||
@@ -468,6 +471,42 @@ class TeacherRepository {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> preflightSubmission({
|
||||
required String curriculumLessonId,
|
||||
required String idempotencyKey,
|
||||
bool replacement = false,
|
||||
String? submissionId,
|
||||
}) async {
|
||||
final uri = Uri.parse('$baseUrl/api/teacher/submissions/preflight');
|
||||
try {
|
||||
final headers = await _authHeaders();
|
||||
headers['Idempotency-Key'] = idempotencyKey;
|
||||
final response = await _client
|
||||
.post(
|
||||
uri,
|
||||
headers: headers,
|
||||
body: json.encode({
|
||||
'curriculum_lesson_id': curriculumLessonId,
|
||||
'replacement': replacement,
|
||||
if (submissionId != null) 'submission_id': submissionId,
|
||||
}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
final decoded = json.decode(response.body);
|
||||
if (decoded is! Map) {
|
||||
throw StateError('استجاب الخادم بصيغة غير صالحة عند تجهيز الحصة.');
|
||||
}
|
||||
final result = Map<String, dynamic>.from(decoded);
|
||||
if (response.statusCode == 201 || response.statusCode == 200 || response.statusCode == 409 || response.statusCode == 400 || response.statusCode == 404) {
|
||||
return result;
|
||||
}
|
||||
throw StateError(result['message']?.toString() ?? 'تعذر تجهيز نسخة الحصة.');
|
||||
} catch (error) {
|
||||
if (error is StateError) rethrow;
|
||||
throw StateError('تعذر تجهيز نسخة الحصة: $error');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getCurriculumTree() async {
|
||||
final headers = await _authHeaders();
|
||||
final response = await _client
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import '../../data/models/teacher_models.dart';
|
||||
import '../../data/repositories/teacher_repository.dart';
|
||||
@@ -267,6 +268,20 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
state.unitKey,
|
||||
state.lessonKey
|
||||
].where((e) => e.isNotEmpty).join('/');
|
||||
|
||||
String? get selectedCurriculumLessonId {
|
||||
final lessons = _units[state.unitKey]?['lessons'];
|
||||
if (lessons is! List) return null;
|
||||
for (final item in lessons) {
|
||||
if (item is Map && item['id']?.toString() == state.lessonKey) {
|
||||
final id = item['curriculum_lesson_id']?.toString();
|
||||
return id != null && id.isNotEmpty ? id : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _newIdempotencyKey() => 'teacher_${DateTime.now().microsecondsSinceEpoch}_${Random.secure().nextInt(1 << 32)}';
|
||||
void reportError(String message) =>
|
||||
emit(state.copyWith(errorMessage: message));
|
||||
|
||||
@@ -300,6 +315,11 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
errorMessage: 'اختر الصف والمبحث والفصل والوحدة والدرس أولاً.'));
|
||||
return;
|
||||
}
|
||||
if (selectedCurriculumLessonId == null) {
|
||||
emit(state.copyWith(
|
||||
errorMessage: 'هذا الدرس غير معتمد للنشر بعد، لذلك لا يمكن رفع حصة له.'));
|
||||
return;
|
||||
}
|
||||
await uploadAndPublishLesson();
|
||||
}
|
||||
|
||||
@@ -316,12 +336,31 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
clearError: true,
|
||||
));
|
||||
try {
|
||||
final preflight = await repository.preflightSubmission(
|
||||
curriculumLessonId: selectedCurriculumLessonId!,
|
||||
idempotencyKey: _newIdempotencyKey(),
|
||||
);
|
||||
if (preflight['status']?.toString() != 'ready_for_upload') {
|
||||
emit(state.copyWith(
|
||||
isAuditing: false,
|
||||
isUploading: false,
|
||||
uploadProgress: 0.0,
|
||||
uploadPhase: 'idle',
|
||||
errorMessage: preflight['message']?.toString() ?? 'تعذر تجهيز نسخة الحصة للرفع.',
|
||||
));
|
||||
return false;
|
||||
}
|
||||
final videoVersionId = preflight['video_version_id']?.toString();
|
||||
if (videoVersionId == null || videoVersionId.isEmpty) {
|
||||
throw StateError('لم ينشئ الخادم نسخة فيديو صالحة للرفع.');
|
||||
}
|
||||
final res = await repository.uploadLesson(
|
||||
title: state.lessonTitle,
|
||||
durationMinutes: state.durationMinutes,
|
||||
gradeLevel: state.gradeLevel,
|
||||
subject: selectedSubjectName,
|
||||
curriculumKey: curriculumKey,
|
||||
videoVersionId: videoVersionId,
|
||||
fileName: state.selectedFileName!,
|
||||
fileSizeMb: state.selectedFileSizeMb,
|
||||
filePath: state.selectedFilePath,
|
||||
@@ -368,7 +407,7 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
return false;
|
||||
}
|
||||
|
||||
final msg = res['message']?.toString() ?? 'تم اعتماد الحصة ورفعها بنجاح.';
|
||||
final msg = res['message']?.toString() ?? 'تم رفع الحصة إلى طابور المراجعة.';
|
||||
emit(state.copyWith(
|
||||
isAuditing: false,
|
||||
isUploading: false,
|
||||
|
||||
Reference in New Issue
Block a user