Update Saqel Platform: 2026-09-08 13:43:36
This commit is contained in:
@@ -56,6 +56,10 @@ class TeacherAuthCubit extends Cubit<TeacherAuthState> {
|
||||
}
|
||||
}
|
||||
|
||||
void resetToPhone() {
|
||||
emit(TeacherUnauthenticated());
|
||||
}
|
||||
|
||||
Future<void> sendOtp(String phoneNumber) async {
|
||||
emit(TeacherAuthLoading());
|
||||
try {
|
||||
@@ -70,11 +74,29 @@ class TeacherAuthCubit extends Cubit<TeacherAuthState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> verifyOtp(String phoneNumber, String otp, {String? fullName}) async {
|
||||
Future<void> verifyOtp(
|
||||
String phoneNumber,
|
||||
String otp, {
|
||||
String? fullName,
|
||||
String? specialization,
|
||||
String? schoolName,
|
||||
List<String>? gradesTaught,
|
||||
}) async {
|
||||
emit(TeacherAuthLoading());
|
||||
try {
|
||||
final res = await repository.verifyOtp(phoneNumber, otp, fullName: fullName);
|
||||
if (res['status'] == 'success') {
|
||||
if (specialization != null || schoolName != null || gradesTaught != null) {
|
||||
try {
|
||||
await repository.setupProfile(
|
||||
fullName: fullName ?? 'المعلم المعتمد',
|
||||
specialization: specialization ?? 'الفيزياء والعلوم التطبيقية',
|
||||
schoolName: schoolName ?? 'مدرسة الملك عبد الله الثاني للتميز',
|
||||
gradesTaught: gradesTaught ?? ['الصف العاشر الأساسي'],
|
||||
bio: 'معلم معتمد في منصة صقل للتعليم التفاعلي',
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
final profile = await repository.getProfileStatus();
|
||||
emit(TeacherAuthenticated(profile: profile));
|
||||
} else {
|
||||
|
||||
@@ -14,38 +14,69 @@ import '../../data/repositories/teacher_repository.dart';
|
||||
*/
|
||||
|
||||
class TeacherMonetizationState {
|
||||
final double institutionalStudents;
|
||||
final double studentSubscribers;
|
||||
final double pricePerCourse;
|
||||
final double grossRevenue;
|
||||
final double teacherShare;
|
||||
final double directorateShare;
|
||||
final double platformShare;
|
||||
final double availableBalance;
|
||||
final double totalWithdrawn;
|
||||
final String cliqAlias;
|
||||
final List<TeacherCourseModel> courses;
|
||||
final List<TeacherPayoutRequestModel> payoutRequests;
|
||||
final bool isSubmittingPayout;
|
||||
final bool isLoading;
|
||||
|
||||
const TeacherMonetizationState({
|
||||
required this.institutionalStudents,
|
||||
required this.studentSubscribers,
|
||||
required this.pricePerCourse,
|
||||
required this.grossRevenue,
|
||||
required this.teacherShare,
|
||||
required this.directorateShare,
|
||||
required this.platformShare,
|
||||
required this.availableBalance,
|
||||
required this.totalWithdrawn,
|
||||
required this.cliqAlias,
|
||||
required this.courses,
|
||||
required this.payoutRequests,
|
||||
required this.isSubmittingPayout,
|
||||
this.isLoading = false,
|
||||
});
|
||||
|
||||
double get grossRevenue => studentSubscribers * pricePerCourse;
|
||||
double get teacherShare => grossRevenue * 0.55;
|
||||
double get directorateShare => grossRevenue * 0.15;
|
||||
double get platformShare => grossRevenue * 0.30;
|
||||
|
||||
TeacherMonetizationState copyWith({
|
||||
double? institutionalStudents,
|
||||
double? studentSubscribers,
|
||||
double? pricePerCourse,
|
||||
double? grossRevenue,
|
||||
double? teacherShare,
|
||||
double? directorateShare,
|
||||
double? platformShare,
|
||||
double? availableBalance,
|
||||
double? totalWithdrawn,
|
||||
String? cliqAlias,
|
||||
List<TeacherCourseModel>? courses,
|
||||
List<TeacherPayoutRequestModel>? payoutRequests,
|
||||
bool? isSubmittingPayout,
|
||||
bool? isLoading,
|
||||
}) {
|
||||
return TeacherMonetizationState(
|
||||
institutionalStudents: institutionalStudents ?? this.institutionalStudents,
|
||||
studentSubscribers: studentSubscribers ?? this.studentSubscribers,
|
||||
pricePerCourse: pricePerCourse ?? this.pricePerCourse,
|
||||
grossRevenue: grossRevenue ?? this.grossRevenue,
|
||||
teacherShare: teacherShare ?? this.teacherShare,
|
||||
directorateShare: directorateShare ?? this.directorateShare,
|
||||
platformShare: platformShare ?? this.platformShare,
|
||||
availableBalance: availableBalance ?? this.availableBalance,
|
||||
totalWithdrawn: totalWithdrawn ?? this.totalWithdrawn,
|
||||
cliqAlias: cliqAlias ?? this.cliqAlias,
|
||||
courses: courses ?? this.courses,
|
||||
payoutRequests: payoutRequests ?? this.payoutRequests,
|
||||
isSubmittingPayout: isSubmittingPayout ?? this.isSubmittingPayout,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -55,27 +86,84 @@ class TeacherMonetizationCubit extends Cubit<TeacherMonetizationState> {
|
||||
|
||||
TeacherMonetizationCubit({required this.repository})
|
||||
: super(const TeacherMonetizationState(
|
||||
studentSubscribers: 380,
|
||||
institutionalStudents: 83,
|
||||
studentSubscribers: 0,
|
||||
pricePerCourse: 20.0,
|
||||
grossRevenue: 0.0,
|
||||
teacherShare: 0.0,
|
||||
directorateShare: 0.0,
|
||||
platformShare: 0.0,
|
||||
availableBalance: 0.0,
|
||||
totalWithdrawn: 0.0,
|
||||
cliqAlias: '0798583052@CLIQ',
|
||||
courses: [],
|
||||
payoutRequests: [
|
||||
TeacherPayoutRequestModel(
|
||||
cliqAlias: 'AHMAD@CLIQ',
|
||||
amountJod: 2400.0,
|
||||
requestedAt: 'الأسبوع الماضي',
|
||||
status: 'completed',
|
||||
),
|
||||
],
|
||||
payoutRequests: [],
|
||||
isSubmittingPayout: false,
|
||||
isLoading: false,
|
||||
));
|
||||
|
||||
Future<void> loadMonetization() async {
|
||||
final courses = await repository.getPublishedCourses();
|
||||
emit(state.copyWith(courses: courses));
|
||||
emit(state.copyWith(isLoading: true));
|
||||
try {
|
||||
final data = await repository.getMonetizationDashboard();
|
||||
final courses = await repository.getPublishedCourses();
|
||||
|
||||
final audience = data['audience_breakdown'] as Map<String, dynamic>? ?? {};
|
||||
final inst = audience['institutional_students'] as Map<String, dynamic>? ?? {};
|
||||
final mkt = audience['marketplace_students'] as Map<String, dynamic>? ?? {};
|
||||
final wallet = data['wallet'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
final instCount = (inst['count'] as num?)?.toDouble() ?? 83.0;
|
||||
final paidCount = (mkt['count'] as num?)?.toDouble() ?? 0.0;
|
||||
final gross = (data['gross_revenue_jod'] as num?)?.toDouble() ?? (paidCount * 20.0);
|
||||
final tShare = gross * 0.55;
|
||||
final dShare = gross * 0.15;
|
||||
final pShare = gross * 0.30;
|
||||
final avail = (wallet['available_balance_jod'] as num?)?.toDouble() ?? tShare;
|
||||
final withdrawn = (wallet['total_withdrawn_jod'] as num?)?.toDouble() ?? 0.0;
|
||||
final alias = wallet['cliq_payout_alias']?.toString() ?? '0798583052@CLIQ';
|
||||
|
||||
List<TeacherPayoutRequestModel> payouts = [];
|
||||
if (wallet['recent_payouts'] is List) {
|
||||
payouts = (wallet['recent_payouts'] as List).map((p) {
|
||||
return TeacherPayoutRequestModel(
|
||||
cliqAlias: p['teacher_cliq_alias']?.toString() ?? alias,
|
||||
amountJod: (p['amount_jod'] as num?)?.toDouble() ?? 0.0,
|
||||
requestedAt: p['created_at']?.toString() ?? 'اليوم',
|
||||
status: p['status']?.toString() ?? 'queued',
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
institutionalStudents: instCount,
|
||||
studentSubscribers: paidCount,
|
||||
grossRevenue: gross,
|
||||
teacherShare: tShare,
|
||||
directorateShare: dShare,
|
||||
platformShare: pShare,
|
||||
availableBalance: avail,
|
||||
totalWithdrawn: withdrawn,
|
||||
cliqAlias: alias,
|
||||
courses: courses,
|
||||
payoutRequests: payouts,
|
||||
isLoading: false,
|
||||
));
|
||||
} catch (_) {
|
||||
emit(state.copyWith(isLoading: false));
|
||||
}
|
||||
}
|
||||
|
||||
void updateSubscribers(double subscribers) {
|
||||
emit(state.copyWith(studentSubscribers: subscribers));
|
||||
final gross = subscribers * state.pricePerCourse;
|
||||
emit(state.copyWith(
|
||||
studentSubscribers: subscribers,
|
||||
grossRevenue: gross,
|
||||
teacherShare: gross * 0.55,
|
||||
directorateShare: gross * 0.15,
|
||||
platformShare: gross * 0.30,
|
||||
availableBalance: gross * 0.55,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> submitCliqPayout({
|
||||
@@ -89,8 +177,13 @@ class TeacherMonetizationCubit extends Cubit<TeacherMonetizationState> {
|
||||
amountJod: amountJod,
|
||||
);
|
||||
final updatedList = List<TeacherPayoutRequestModel>.from(state.payoutRequests)..insert(0, req);
|
||||
final newAvail = (state.availableBalance - amountJod).clamp(0.0, double.infinity);
|
||||
final newWithdrawn = state.totalWithdrawn + amountJod;
|
||||
|
||||
emit(state.copyWith(
|
||||
payoutRequests: updatedList,
|
||||
availableBalance: newAvail,
|
||||
totalWithdrawn: newWithdrawn,
|
||||
isSubmittingPayout: false,
|
||||
));
|
||||
} catch (_) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import '../../data/repositories/teacher_repository.dart';
|
||||
* ==============================================================================
|
||||
*
|
||||
* إدارة الواجبات المدرسية وغرفة استفسارات الطلبة والبث الفوري عبر الويب سوكت:
|
||||
* - محادثات حية ومباشرة مع الطلبة المسجلين (مثل سما حمزة)
|
||||
* - محادثات حية ومباشرة مع الطلبة المسجلين
|
||||
* - تسجيل وإرسال الردود الصوتية السقراطية (Socratic Voice Replies 🎙️)
|
||||
* - بث مقاطع صوتية توجيهية لكافة طلبة الشعبة عبر الويب سوكت (0ms Broadcast).
|
||||
*/
|
||||
@@ -75,37 +75,26 @@ class TeacherQnACubit extends Cubit<TeacherQnAState> {
|
||||
required String className,
|
||||
required String dueDate,
|
||||
}) {
|
||||
final newHw = TeacherHomeworkModel(
|
||||
id: 'hw-${DateTime.now().millisecondsSinceEpoch}',
|
||||
title: title,
|
||||
className: className,
|
||||
submitted: '0 من 83 طالباً',
|
||||
dueDate: dueDate,
|
||||
averageScore: 'بانتظار الإجابات',
|
||||
status: 'active',
|
||||
);
|
||||
|
||||
final updated = List<TeacherHomeworkModel>.from(state.homeworks)..insert(0, newHw);
|
||||
emit(state.copyWith(homeworks: updated));
|
||||
throw StateError('إنشاء الواجبات ينتظر واجهة حفظ حقيقية على الخادم.');
|
||||
}
|
||||
|
||||
Future<void> sendVoiceReply(String doubtId, {String? voiceText}) async {
|
||||
final updated = state.doubts.map((doubt) {
|
||||
if (doubt.id == doubtId) {
|
||||
return doubt.copyWith(replied: true, voiceReply: true);
|
||||
}
|
||||
return doubt;
|
||||
}).toList();
|
||||
|
||||
emit(state.copyWith(doubts: updated));
|
||||
|
||||
// Also send actual message to student via API
|
||||
final targetIndex = state.doubts.indexWhere((doubt) => doubt.id == doubtId);
|
||||
if (targetIndex < 0 || state.doubts[targetIndex].studentId <= 0) {
|
||||
throw StateError('لا يوجد طالب حقيقي مرتبط بهذا الاستفسار.');
|
||||
}
|
||||
final target = state.doubts[targetIndex];
|
||||
final replyText = voiceText?.trim() ?? '';
|
||||
if (replyText.isEmpty) {
|
||||
throw StateError('لا يمكن تسجيل رد صوتي دون ملف صوت حقيقي؛ أرسل رداً نصياً حالياً.');
|
||||
}
|
||||
await repository.sendMessage(
|
||||
receiverId: 2, // Sama Hamza (Real Student)
|
||||
message: voiceText ?? 'رد صوتي سقراطي: تم تسجيل وتوجيه المفاهيم المتعلقة بالسؤال.',
|
||||
messageType: 'voice',
|
||||
mediaUrl: 'https://saqel.intaleqapp.com/assets/audio/voice_reply_socratic.mp3',
|
||||
receiverId: target.studentId,
|
||||
message: replyText,
|
||||
messageType: 'text',
|
||||
);
|
||||
final updated = state.doubts.map((doubt) => doubt.id == doubtId ? doubt.copyWith(replied: true, voiceReply: false) : doubt).toList();
|
||||
emit(state.copyWith(doubts: updated));
|
||||
}
|
||||
|
||||
Future<bool> broadcastAudioAnnouncement({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'dart:typed_data';
|
||||
import '../../data/models/teacher_models.dart';
|
||||
import '../../data/repositories/teacher_repository.dart';
|
||||
|
||||
@@ -15,27 +16,59 @@ import '../../data/repositories/teacher_repository.dart';
|
||||
class TeacherStudioState {
|
||||
final String lessonTitle;
|
||||
final double durationMinutes;
|
||||
final String gradeLevel;
|
||||
final String? selectedFileName;
|
||||
final double? selectedFileSizeMb;
|
||||
final String? selectedFilePath;
|
||||
final Uint8List? selectedFileBytes;
|
||||
final bool isAuditing;
|
||||
final TeacherLessonAuditModel? auditResult;
|
||||
final bool isUploading;
|
||||
final bool isUploaded;
|
||||
final String? uploadSuccessMessage;
|
||||
|
||||
const TeacherStudioState({
|
||||
required this.lessonTitle,
|
||||
required this.durationMinutes,
|
||||
this.gradeLevel = 'الصف العاشر الأساسي',
|
||||
this.selectedFileName,
|
||||
this.selectedFileSizeMb,
|
||||
this.selectedFilePath,
|
||||
this.selectedFileBytes,
|
||||
required this.isAuditing,
|
||||
this.auditResult,
|
||||
this.isUploading = false,
|
||||
this.isUploaded = false,
|
||||
this.uploadSuccessMessage,
|
||||
});
|
||||
|
||||
TeacherStudioState copyWith({
|
||||
String? lessonTitle,
|
||||
double? durationMinutes,
|
||||
String? gradeLevel,
|
||||
String? selectedFileName,
|
||||
double? selectedFileSizeMb,
|
||||
String? selectedFilePath,
|
||||
Uint8List? selectedFileBytes,
|
||||
bool? isAuditing,
|
||||
TeacherLessonAuditModel? auditResult,
|
||||
bool? isUploading,
|
||||
bool? isUploaded,
|
||||
String? uploadSuccessMessage,
|
||||
}) {
|
||||
return TeacherStudioState(
|
||||
lessonTitle: lessonTitle ?? this.lessonTitle,
|
||||
durationMinutes: durationMinutes ?? this.durationMinutes,
|
||||
gradeLevel: gradeLevel ?? this.gradeLevel,
|
||||
selectedFileName: selectedFileName ?? this.selectedFileName,
|
||||
selectedFileSizeMb: selectedFileSizeMb ?? this.selectedFileSizeMb,
|
||||
selectedFilePath: selectedFilePath ?? this.selectedFilePath,
|
||||
selectedFileBytes: selectedFileBytes ?? this.selectedFileBytes,
|
||||
isAuditing: isAuditing ?? this.isAuditing,
|
||||
auditResult: auditResult ?? this.auditResult,
|
||||
isUploading: isUploading ?? this.isUploading,
|
||||
isUploaded: isUploaded ?? this.isUploaded,
|
||||
uploadSuccessMessage: uploadSuccessMessage ?? this.uploadSuccessMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -46,7 +79,7 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
TeacherStudioCubit({required this.repository})
|
||||
: super(const TeacherStudioState(
|
||||
lessonTitle: 'شرح قاعدة لنتز والحث الكهرومغناطيسي — فيزياء 2008',
|
||||
durationMinutes: 22.5,
|
||||
durationMinutes: 13.0,
|
||||
isAuditing: false,
|
||||
auditResult: null,
|
||||
));
|
||||
@@ -59,6 +92,28 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
emit(state.copyWith(durationMinutes: duration));
|
||||
}
|
||||
|
||||
void updateGradeLevel(String grade) {
|
||||
emit(state.copyWith(gradeLevel: grade));
|
||||
}
|
||||
|
||||
void selectVideoFile(
|
||||
String fileName,
|
||||
double sizeMb,
|
||||
double durationMinutes, {
|
||||
String? path,
|
||||
Uint8List? bytes,
|
||||
}) {
|
||||
emit(state.copyWith(
|
||||
selectedFileName: fileName,
|
||||
selectedFileSizeMb: sizeMb,
|
||||
selectedFilePath: path,
|
||||
selectedFileBytes: bytes,
|
||||
durationMinutes: durationMinutes,
|
||||
isUploaded: false,
|
||||
uploadSuccessMessage: null,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> runQualityGate() async {
|
||||
emit(state.copyWith(isAuditing: true));
|
||||
try {
|
||||
@@ -75,4 +130,35 @@ class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
emit(state.copyWith(isAuditing: false));
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> uploadAndPublishLesson() async {
|
||||
if (state.selectedFileName == null ||
|
||||
(state.selectedFilePath == null && state.selectedFileBytes == null)) {
|
||||
return false;
|
||||
}
|
||||
emit(state.copyWith(isUploading: true));
|
||||
try {
|
||||
final res = await repository.uploadLesson(
|
||||
title: state.lessonTitle,
|
||||
durationMinutes: state.durationMinutes,
|
||||
gradeLevel: state.gradeLevel,
|
||||
subject: 'الفيزياء والعلوم التطبيقية',
|
||||
fileName: state.selectedFileName ?? 'lentz_law_physics_2008.mp4',
|
||||
fileSizeMb: state.selectedFileSizeMb ?? 48.2,
|
||||
filePath: state.selectedFilePath,
|
||||
fileBytes: state.selectedFileBytes,
|
||||
);
|
||||
|
||||
final msg = res['message']?.toString() ?? 'تم رفع ونشر الحصة بنجاح في المنهاج الوزاري وسوق صَقِل! 🚀';
|
||||
emit(state.copyWith(
|
||||
isUploading: false,
|
||||
isUploaded: true,
|
||||
uploadSuccessMessage: msg,
|
||||
));
|
||||
return true;
|
||||
} catch (e) {
|
||||
emit(state.copyWith(isUploading: false));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user