Update Saqel Platform: 2026-09-04 21:26:55
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - DESIGN SYSTEM & THEME
|
||||
* ==============================================================================
|
||||
*
|
||||
* نسق التصميم الداكن الفاخر (Apple Cupertino Dark Luxury) المعتمد في استوديو المعلم:
|
||||
* - تدرجات الزمرد الأخضر الملكي (Emerald Green) للدلالة على الاعتماد الأكاديمي.
|
||||
* - ألوان النمط السيادي الموحد لشبكة صاقل 2.0.
|
||||
*/
|
||||
class TeacherTheme {
|
||||
static const Color backgroundDark = Color(0xFF070B12);
|
||||
static const Color surfaceDark = Color(0xFF0E1626);
|
||||
static const Color surfaceCard = Color(0xFF0F172A);
|
||||
static const Color surfaceBorder = Color(0xFF1E293B);
|
||||
|
||||
static const Color emeraldPrimary = Color(0xFF10B981);
|
||||
static const Color emeraldLight = Color(0xFF34D399);
|
||||
static const Color emeraldDark = Color(0xFF059669);
|
||||
|
||||
static const Color cyberCyan = Color(0xFF38BDF8);
|
||||
static const Color royalGold = Color(0xFFFBBF24);
|
||||
static const Color crimsonRed = Color(0xFFEF4444);
|
||||
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: backgroundDark,
|
||||
fontFamily: '-apple-system',
|
||||
fontFamilyFallback: const [
|
||||
'SF Pro Display',
|
||||
'SF Pro Text',
|
||||
'SF Arabic',
|
||||
'Cairo',
|
||||
'system-ui',
|
||||
],
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: emeraldPrimary,
|
||||
surface: surfaceCard,
|
||||
background: backgroundDark,
|
||||
),
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: surfaceDark,
|
||||
elevation: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - DATA MODELS
|
||||
* ==============================================================================
|
||||
*
|
||||
* نماذج البيانات الخاصة باستوديو المعلم:
|
||||
* - نتائج فحص الجودة الإدراكي والأكاديمي (Quality Gate & MIT 20-25 Min Limit)
|
||||
* - بيانات التسييل المالي وحسابات الحصص (55% المعلم / 15% المديرية / 30% المنصة)
|
||||
* - أوراق العمل والواجبات المدرسية المصححة آلياً
|
||||
* - استفسارات وأسئلة الطلبة الميدانية (غرفة الشكوك) والردود السقراطية المسجلة.
|
||||
*/
|
||||
|
||||
class TeacherLessonAuditModel {
|
||||
final String lessonTitle;
|
||||
final String subject;
|
||||
final double durationMinutes;
|
||||
final bool durationGatePassed;
|
||||
final String? durationWarning;
|
||||
final int qualityScore;
|
||||
final String approvalStatus;
|
||||
final String curriculumAlignment;
|
||||
final String audioClarity;
|
||||
final int socraticStopsCount;
|
||||
final String decision;
|
||||
|
||||
const TeacherLessonAuditModel({
|
||||
required this.lessonTitle,
|
||||
required this.subject,
|
||||
required this.durationMinutes,
|
||||
required this.durationGatePassed,
|
||||
this.durationWarning,
|
||||
required this.qualityScore,
|
||||
required this.approvalStatus,
|
||||
required this.curriculumAlignment,
|
||||
required this.audioClarity,
|
||||
required this.socraticStopsCount,
|
||||
required this.decision,
|
||||
});
|
||||
|
||||
factory TeacherLessonAuditModel.fromJson(Map<String, dynamic> json) {
|
||||
return TeacherLessonAuditModel(
|
||||
lessonTitle: json['lesson_title'] ?? '',
|
||||
subject: json['subject'] ?? 'الفيزياء',
|
||||
durationMinutes: (json['duration_minutes'] as num?)?.toDouble() ?? 22.5,
|
||||
durationGatePassed: json['duration_gate_passed'] ?? true,
|
||||
durationWarning: json['duration_warning'],
|
||||
qualityScore: json['quality_score'] ?? 92,
|
||||
approvalStatus: json['approval_status'] ?? 'approved_for_broadcast',
|
||||
curriculumAlignment: json['curriculum_alignment'] ?? '96% تطابق مع مخرجات المنهاج الوزاري',
|
||||
audioClarity: json['audio_clarity'] ?? '95% نقاء صوتي ممتاز',
|
||||
socraticStopsCount: json['socratic_stops_count'] ?? 3,
|
||||
decision: json['decision'] ?? 'الحصة معتمدة ومؤهلة للبث المشفر والعرض للبيع خارج الثقافة العسكرية 🚀',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TeacherHomeworkModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String className;
|
||||
final String submitted;
|
||||
final String dueDate;
|
||||
final String averageScore;
|
||||
final String status; // 'active', 'completed'
|
||||
|
||||
const TeacherHomeworkModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.className,
|
||||
required this.submitted,
|
||||
required this.dueDate,
|
||||
required this.averageScore,
|
||||
required this.status,
|
||||
});
|
||||
}
|
||||
|
||||
class StudentDoubtModel {
|
||||
final String id;
|
||||
final String studentName;
|
||||
final String className;
|
||||
final String question;
|
||||
final String time;
|
||||
final bool replied;
|
||||
final bool voiceReply;
|
||||
|
||||
const StudentDoubtModel({
|
||||
required this.id,
|
||||
required this.studentName,
|
||||
required this.className,
|
||||
required this.question,
|
||||
required this.time,
|
||||
required this.replied,
|
||||
required this.voiceReply,
|
||||
});
|
||||
|
||||
StudentDoubtModel copyWith({
|
||||
bool? replied,
|
||||
bool? voiceReply,
|
||||
}) {
|
||||
return StudentDoubtModel(
|
||||
id: id,
|
||||
studentName: studentName,
|
||||
className: className,
|
||||
question: question,
|
||||
time: time,
|
||||
replied: replied ?? this.replied,
|
||||
voiceReply: voiceReply ?? this.voiceReply,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TeacherCourseModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String grade;
|
||||
final int totalLessons;
|
||||
final int militaryStudents;
|
||||
final int externalSubscribers;
|
||||
final double priceJod;
|
||||
final double netRevenueJod;
|
||||
|
||||
const TeacherCourseModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.grade,
|
||||
required this.totalLessons,
|
||||
required this.militaryStudents,
|
||||
required this.externalSubscribers,
|
||||
required this.priceJod,
|
||||
required this.netRevenueJod,
|
||||
});
|
||||
}
|
||||
|
||||
class TeacherPayoutRequestModel {
|
||||
final String cliqAlias;
|
||||
final double amountJod;
|
||||
final String requestedAt;
|
||||
final String status; // 'queued', 'approved', 'completed'
|
||||
|
||||
const TeacherPayoutRequestModel({
|
||||
required this.cliqAlias,
|
||||
required this.amountJod,
|
||||
required this.requestedAt,
|
||||
required this.status,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../models/teacher_models.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - REPOSITORY
|
||||
* ==============================================================================
|
||||
*
|
||||
* طبقة البيانات المركزية لاستوديو المعلم:
|
||||
* - فحص جودة فيديوهات الحصص الأكاديمية (Quality Gate & MIT 20-25 Min Limit).
|
||||
* - تسجيل طلبات السحب المالي الفوري عبر شبكة كليك (CliQ Payout Queue).
|
||||
* - جلب بيانات الواجبات المدرسية وغرفة التساؤلات والشكوك.
|
||||
* - دعم وضع العرض الحضوري غير المتصل (Offline-First Resilience).
|
||||
*/
|
||||
class TeacherRepository {
|
||||
final String baseUrl;
|
||||
final http.Client? client;
|
||||
|
||||
TeacherRepository({
|
||||
this.baseUrl = 'http://127.0.0.1:8000',
|
||||
this.client,
|
||||
});
|
||||
|
||||
http.Client get _client => client ?? http.Client();
|
||||
|
||||
Future<TeacherLessonAuditModel> auditStudioVideo({
|
||||
required String title,
|
||||
required double durationMinutes,
|
||||
required String subject,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _client.post(
|
||||
Uri.parse('$baseUrl/api/teacher/audit-studio-video'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
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);
|
||||
if (decoded['status'] == 'success') {
|
||||
return TeacherLessonAuditModel.fromJson(decoded['data']);
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// Fallback local high-fidelity simulation
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
final bool isValid = durationMinutes >= 15.0 && durationMinutes <= 25.0;
|
||||
return TeacherLessonAuditModel(
|
||||
lessonTitle: title,
|
||||
subject: subject,
|
||||
durationMinutes: durationMinutes,
|
||||
durationGatePassed: isValid,
|
||||
durationWarning: durationMinutes > 25.0
|
||||
? 'تنبيه إدراكي: مدة الحصة تتجاوز 25 دقيقة. أثبتت أبحاث معهد ماساتشوستس (MIT) أن التركيز الذهني يهبط بعد الدقيقة 18. يُوصى بتقسيم الحصة إلى جزأين.'
|
||||
: null,
|
||||
qualityScore: 92,
|
||||
approvalStatus: 'approved_for_broadcast',
|
||||
curriculumAlignment: '96% تطابق مع مخرجات المنهاج الوزاري',
|
||||
audioClarity: '95% نقاء صوتي ممتاز',
|
||||
socraticStopsCount: 3,
|
||||
decision: 'الحصة معتمدة ومؤهلة للبث المشفر والعرض للبيع خارج الثقافة العسكرية 🚀',
|
||||
);
|
||||
}
|
||||
|
||||
Future<TeacherPayoutRequestModel> requestCliqPayout({
|
||||
required String cliqAlias,
|
||||
required double amountJod,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _client.post(
|
||||
Uri.parse('$baseUrl/api/v1/cliq/payout'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: json.encode({
|
||||
'cliq_alias': cliqAlias,
|
||||
'amount_jod': amountJod,
|
||||
'teacher_id': 1,
|
||||
}),
|
||||
).timeout(const Duration(seconds: 4));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = json.decode(response.body);
|
||||
return TeacherPayoutRequestModel(
|
||||
cliqAlias: cliqAlias,
|
||||
amountJod: amountJod,
|
||||
requestedAt: 'اليوم، 10:30 صباحاً',
|
||||
status: decoded['status'] ?? 'queued',
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
// Fallback
|
||||
}
|
||||
|
||||
return TeacherPayoutRequestModel(
|
||||
cliqAlias: cliqAlias,
|
||||
amountJod: amountJod,
|
||||
requestedAt: 'الآن',
|
||||
status: 'queued',
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<TeacherHomeworkModel>> getHomeworks() async {
|
||||
return const [
|
||||
TeacherHomeworkModel(
|
||||
id: 'hw-1',
|
||||
title: 'ورقة عمل: قوانين نيوتن وتطبيقات المصاعد',
|
||||
className: 'الأول ثانوي العلمي (شعبة أ)',
|
||||
submitted: '38 من 42 طالباً',
|
||||
dueDate: 'غداً الساعة 08:00 مساءً',
|
||||
averageScore: '86%',
|
||||
status: 'active',
|
||||
),
|
||||
TeacherHomeworkModel(
|
||||
id: 'hw-2',
|
||||
title: 'واجب بيتي: مسائل تحليل المتجهات والضرب القياسي',
|
||||
className: 'الأول ثانوي العلمي (شعبة ب)',
|
||||
submitted: '41 من 41 طالباً',
|
||||
dueDate: 'مكتمل التسليم والتصحيح الآلي',
|
||||
averageScore: '92%',
|
||||
status: 'completed',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<StudentDoubtModel>> getStudentDoubts() async {
|
||||
return const [
|
||||
StudentDoubtModel(
|
||||
id: 'doubt-1',
|
||||
studentName: 'محمد طارق الحنيطي',
|
||||
className: 'الأول ثانوي العلمي',
|
||||
question: 'أستاذ، لماذا ينعدم الوزن الظاهري في المصعد الساقط سقوطاً حراً؟ هل تفقد الأجسام كتلتها؟',
|
||||
time: 'منذ 25 دقيقة',
|
||||
replied: false,
|
||||
voiceReply: false,
|
||||
),
|
||||
StudentDoubtModel(
|
||||
id: 'doubt-2',
|
||||
studentName: 'سيف الدين الرواشدة',
|
||||
className: 'الأول ثانوي العلمي',
|
||||
question: 'هل قوة الاحتكاك السكوني دائماً تساوي القوة المؤثرة حتى نصل للقيمة العظمى؟',
|
||||
time: 'منذ ساعتين',
|
||||
replied: true,
|
||||
voiceReply: true,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<TeacherCourseModel>> getPublishedCourses() async {
|
||||
return const [
|
||||
TeacherCourseModel(
|
||||
id: 'c-1',
|
||||
title: 'الفيزياء الأساسية والمتقدمة — التوجيهي العلمي',
|
||||
grade: 'الصف الثاني عشر (التوجيهي)',
|
||||
totalLessons: 48,
|
||||
militaryStudents: 1420,
|
||||
externalSubscribers: 380,
|
||||
priceJod: 20.0,
|
||||
netRevenueJod: 4180.0,
|
||||
),
|
||||
TeacherCourseModel(
|
||||
id: 'c-2',
|
||||
title: 'الميكانيكا والمقذوفات — الأول ثانوي العلمي',
|
||||
grade: 'الصف الحادي عشر',
|
||||
totalLessons: 32,
|
||||
militaryStudents: 980,
|
||||
externalSubscribers: 140,
|
||||
priceJod: 15.0,
|
||||
netRevenueJod: 1155.0,
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../data/models/teacher_models.dart';
|
||||
import '../../data/repositories/teacher_repository.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - MONETIZATION CUBIT
|
||||
* ==============================================================================
|
||||
*
|
||||
* إدارة المحفظة المالية والتسييل وسحوبات كليك (CliQ Payouts):
|
||||
* - حساب عوائد النموذج المزدوج (1,420 مجاني مدعوم مقابل 380 مدفوع خارجي)
|
||||
* - تقسيم الحصص: 55% للمعلم / 15% لمديرية الثقافة / 30% للمنصة
|
||||
* - إرسال واعتماد طلبات السحب عبر كليك.
|
||||
*/
|
||||
|
||||
class TeacherMonetizationState {
|
||||
final double studentSubscribers;
|
||||
final double pricePerCourse;
|
||||
final List<TeacherCourseModel> courses;
|
||||
final List<TeacherPayoutRequestModel> payoutRequests;
|
||||
final bool isSubmittingPayout;
|
||||
|
||||
const TeacherMonetizationState({
|
||||
required this.studentSubscribers,
|
||||
required this.pricePerCourse,
|
||||
required this.courses,
|
||||
required this.payoutRequests,
|
||||
required this.isSubmittingPayout,
|
||||
});
|
||||
|
||||
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? studentSubscribers,
|
||||
double? pricePerCourse,
|
||||
List<TeacherCourseModel>? courses,
|
||||
List<TeacherPayoutRequestModel>? payoutRequests,
|
||||
bool? isSubmittingPayout,
|
||||
}) {
|
||||
return TeacherMonetizationState(
|
||||
studentSubscribers: studentSubscribers ?? this.studentSubscribers,
|
||||
pricePerCourse: pricePerCourse ?? this.pricePerCourse,
|
||||
courses: courses ?? this.courses,
|
||||
payoutRequests: payoutRequests ?? this.payoutRequests,
|
||||
isSubmittingPayout: isSubmittingPayout ?? this.isSubmittingPayout,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TeacherMonetizationCubit extends Cubit<TeacherMonetizationState> {
|
||||
final TeacherRepository repository;
|
||||
|
||||
TeacherMonetizationCubit({required this.repository})
|
||||
: super(const TeacherMonetizationState(
|
||||
studentSubscribers: 380,
|
||||
pricePerCourse: 20.0,
|
||||
courses: [],
|
||||
payoutRequests: [
|
||||
TeacherPayoutRequestModel(
|
||||
cliqAlias: 'AHMAD@CLIQ',
|
||||
amountJod: 2400.0,
|
||||
requestedAt: 'الأسبوع الماضي',
|
||||
status: 'completed',
|
||||
),
|
||||
],
|
||||
isSubmittingPayout: false,
|
||||
));
|
||||
|
||||
Future<void> loadMonetization() async {
|
||||
final courses = await repository.getPublishedCourses();
|
||||
emit(state.copyWith(courses: courses));
|
||||
}
|
||||
|
||||
void updateSubscribers(double subscribers) {
|
||||
emit(state.copyWith(studentSubscribers: subscribers));
|
||||
}
|
||||
|
||||
Future<void> submitCliqPayout({
|
||||
required String cliqAlias,
|
||||
required double amountJod,
|
||||
}) async {
|
||||
emit(state.copyWith(isSubmittingPayout: true));
|
||||
try {
|
||||
final req = await repository.requestCliqPayout(
|
||||
cliqAlias: cliqAlias,
|
||||
amountJod: amountJod,
|
||||
);
|
||||
final updatedList = List<TeacherPayoutRequestModel>.from(state.payoutRequests)..insert(0, req);
|
||||
emit(state.copyWith(
|
||||
payoutRequests: updatedList,
|
||||
isSubmittingPayout: false,
|
||||
));
|
||||
} catch (_) {
|
||||
emit(state.copyWith(isSubmittingPayout: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../data/models/teacher_models.dart';
|
||||
import '../../data/repositories/teacher_repository.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - ASSIGNMENTS & Q&A CUBIT
|
||||
* ==============================================================================
|
||||
*
|
||||
* إدارة الواجبات المدرسية وأوراق العمل وغرفة تساؤلات الطلبة:
|
||||
* - توليد وإسناد أوراق عمل مؤتمتة التصحيح بنقرة واحدة
|
||||
* - تسجيل الردود الصوتية السقراطية (Socratic Voice Replies 🎙️)
|
||||
* - تحديث فوري لحالات الشكوك والتساؤلات.
|
||||
*/
|
||||
|
||||
class TeacherQnAState {
|
||||
final List<TeacherHomeworkModel> homeworks;
|
||||
final List<StudentDoubtModel> doubts;
|
||||
final bool isLoading;
|
||||
|
||||
const TeacherQnAState({
|
||||
required this.homeworks,
|
||||
required this.doubts,
|
||||
required this.isLoading,
|
||||
});
|
||||
|
||||
TeacherQnAState copyWith({
|
||||
List<TeacherHomeworkModel>? homeworks,
|
||||
List<StudentDoubtModel>? doubts,
|
||||
bool? isLoading,
|
||||
}) {
|
||||
return TeacherQnAState(
|
||||
homeworks: homeworks ?? this.homeworks,
|
||||
doubts: doubts ?? this.doubts,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TeacherQnACubit extends Cubit<TeacherQnAState> {
|
||||
final TeacherRepository repository;
|
||||
|
||||
TeacherQnACubit({required this.repository})
|
||||
: super(const TeacherQnAState(
|
||||
homeworks: [],
|
||||
doubts: [],
|
||||
isLoading: false,
|
||||
));
|
||||
|
||||
Future<void> loadQnA() async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final hwList = await repository.getHomeworks();
|
||||
final doubtsList = await repository.getStudentDoubts();
|
||||
emit(state.copyWith(
|
||||
homeworks: hwList,
|
||||
doubts: doubtsList,
|
||||
isLoading: false,
|
||||
));
|
||||
}
|
||||
|
||||
void dispatchNewHomework({
|
||||
required String title,
|
||||
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));
|
||||
}
|
||||
|
||||
void recordVoiceReply(String doubtId) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../data/models/teacher_models.dart';
|
||||
import '../../data/repositories/teacher_repository.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - STUDIO UPLOAD CUBIT
|
||||
* ==============================================================================
|
||||
*
|
||||
* إدارة حالة استوديو رفع الحصص وفحص الجودة الإدراكي:
|
||||
* - التحقق من معيار معهد ماساتشوستس (20 - 25 دقيقة)
|
||||
* - استدعاء التدقيق الآلي للدرس ومخرجات المنهاج الوزاري.
|
||||
*/
|
||||
|
||||
class TeacherStudioState {
|
||||
final String lessonTitle;
|
||||
final double durationMinutes;
|
||||
final bool isAuditing;
|
||||
final TeacherLessonAuditModel? auditResult;
|
||||
|
||||
const TeacherStudioState({
|
||||
required this.lessonTitle,
|
||||
required this.durationMinutes,
|
||||
required this.isAuditing,
|
||||
this.auditResult,
|
||||
});
|
||||
|
||||
TeacherStudioState copyWith({
|
||||
String? lessonTitle,
|
||||
double? durationMinutes,
|
||||
bool? isAuditing,
|
||||
TeacherLessonAuditModel? auditResult,
|
||||
}) {
|
||||
return TeacherStudioState(
|
||||
lessonTitle: lessonTitle ?? this.lessonTitle,
|
||||
durationMinutes: durationMinutes ?? this.durationMinutes,
|
||||
isAuditing: isAuditing ?? this.isAuditing,
|
||||
auditResult: auditResult ?? this.auditResult,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TeacherStudioCubit extends Cubit<TeacherStudioState> {
|
||||
final TeacherRepository repository;
|
||||
|
||||
TeacherStudioCubit({required this.repository})
|
||||
: super(const TeacherStudioState(
|
||||
lessonTitle: 'شرح قاعدة لنتز والحث الكهرومغناطيسي — فيزياء 2008',
|
||||
durationMinutes: 22.5,
|
||||
isAuditing: false,
|
||||
auditResult: null,
|
||||
));
|
||||
|
||||
void updateLessonTitle(String title) {
|
||||
emit(state.copyWith(lessonTitle: title));
|
||||
}
|
||||
|
||||
void updateDuration(double duration) {
|
||||
emit(state.copyWith(durationMinutes: duration));
|
||||
}
|
||||
|
||||
Future<void> runQualityGate() async {
|
||||
emit(state.copyWith(isAuditing: true));
|
||||
try {
|
||||
final result = await repository.auditStudioVideo(
|
||||
title: state.lessonTitle,
|
||||
durationMinutes: state.durationMinutes,
|
||||
subject: 'الفيزياء',
|
||||
);
|
||||
emit(state.copyWith(
|
||||
isAuditing: false,
|
||||
auditResult: result,
|
||||
));
|
||||
} catch (_) {
|
||||
emit(state.copyWith(isAuditing: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-1547
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,329 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../core/theme/teacher_theme.dart';
|
||||
import '../../../data/models/teacher_models.dart';
|
||||
import '../../../logic/cubits/teacher_qna_cubit.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - TAB 4: ASSIGNMENTS & Q&A (VOICE REPLIES)
|
||||
* ==============================================================================
|
||||
*
|
||||
* إدارة الواجبات المدرسية الموجهة وغرفة الشكوك والاستفسارات:
|
||||
* - إسناد أوراق عمل مؤتمتة التصحيح بنقرة واحدة
|
||||
* - تسجيل الردود الصوتية السقراطية (Socratic Voice Replies 🎙️)
|
||||
* - مؤشرات تسليم الطلاب ومعدلات الشعب المدرسية.
|
||||
*/
|
||||
class TeacherAssignmentsQnATab extends StatefulWidget {
|
||||
const TeacherAssignmentsQnATab({super.key});
|
||||
|
||||
@override
|
||||
State<TeacherAssignmentsQnATab> createState() => _TeacherAssignmentsQnATabState();
|
||||
}
|
||||
|
||||
class _TeacherAssignmentsQnATabState extends State<TeacherAssignmentsQnATab> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
context.read<TeacherQnACubit>().loadQnA();
|
||||
}
|
||||
|
||||
void _dispatchNewHomework() {
|
||||
showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: CupertinoAlertDialog(
|
||||
title: const Text('إسناد ورقة عمل تفاعلية جديدة 📝'),
|
||||
content: const Text(
|
||||
'سيقوم محرك صَقِل بتوليد ورقة عمل مكونة من 5 مسائل تدريبية متدرجة من بنك الأسئلة الوزاري وإرسالها فوراً لشعبتك مع التصحيح التلقائي.',
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: const Text('إلغاء'),
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
onPressed: () {
|
||||
Navigator.of(ctx).pop();
|
||||
context.read<TeacherQnACubit>().dispatchNewHomework(
|
||||
title: 'ورقة عمل: الشغل والطاقة الميكانيكية',
|
||||
className: 'الأول ثانوي العلمي (شعبة أ وب)',
|
||||
dueDate: 'الخميس القادم',
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('تم إسناد ورقة العمل وإشعار الطلبة بنجاح 🚀'),
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('إسناد للطلبة الآن'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _recordVoiceReply(String doubtId) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (ctx) => Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
border: Border(top: BorderSide(color: TeacherTheme.emeraldPrimary, width: 2)),
|
||||
),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(CupertinoIcons.mic_circle_fill, size: 56, color: TeacherTheme.emeraldPrimary),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'تسجيل رد صوتي سقراطي للطالب 🎙️',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'سيصل التسجيل الصوتي بجودة عالية داخل تطبيق الطالب مع التوجيه المفاهيمي.',
|
||||
style: TextStyle(fontSize: 12, color: Color(0xFF94A3B8)),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
context.read<TeacherQnACubit>().recordVoiceReply(doubtId);
|
||||
Navigator.of(ctx).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('تم إرسال الرد الصوتي للطالب بنجاح 🎧'),
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.paperplane_fill, size: 16),
|
||||
label: const Text('إنهاء وإرسال التسجيل الصوتي (00:42)'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
foregroundColor: Colors.black,
|
||||
minimumSize: const Size(double.infinity, 44),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TeacherQnACubit, TeacherQnAState>(
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(child: CupertinoActivityIndicator(color: TeacherTheme.emeraldPrimary));
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Top Action Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF064E3B), Color(0xFF0F172A)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: TeacherTheme.emeraldPrimary.withOpacity(0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'أوراق العمل والواجبات الوزارية 📝',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 3),
|
||||
Text(
|
||||
'تصحيح مؤتمت بنسبة 100% مرتبط ببنك الأسئلة',
|
||||
style: TextStyle(fontSize: 11.5, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
],
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _dispatchNewHomework,
|
||||
icon: const Icon(CupertinoIcons.plus_circle_fill, size: 16),
|
||||
label: const Text('إسناد واجب جديد', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w800)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
foregroundColor: Colors.black,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Homeworks List
|
||||
...state.homeworks.map((hw) => _buildHomeworkCard(hw)),
|
||||
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Student Doubts Section
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.bubble_left_bubble_right_fill, color: TeacherTheme.cyberCyan, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'استفسارات وأسئلة الطلبة الميدانية (غرفة الشكوك) 💬',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// Doubts List
|
||||
...state.doubts.map((doubt) => _buildDoubtCard(doubt)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHomeworkCard(TeacherHomeworkModel hw) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: TeacherTheme.surfaceBorder),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(hw.title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w800, color: Colors.white)),
|
||||
const SizedBox(height: 4),
|
||||
Text('${hw.className} · ${hw.dueDate}', style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.emeraldPrimary.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
hw.submitted,
|
||||
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w700, color: TeacherTheme.emeraldLight),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text('معدل الشعبة: ${hw.averageScore}', style: const TextStyle(fontSize: 10.5, color: Color(0xFF64748B))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDoubtCard(StudentDoubtModel d) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: d.replied ? TeacherTheme.emeraldPrimary.withOpacity(0.3) : TeacherTheme.cyberCyan.withOpacity(0.4),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${d.studentName} · ${d.className}',
|
||||
style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800, color: TeacherTheme.cyberCyan),
|
||||
),
|
||||
Text(d.time, style: const TextStyle(fontSize: 11, color: Color(0xFF64748B))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
d.question,
|
||||
style: const TextStyle(fontSize: 13, color: Colors.white, height: 1.4),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (d.replied)
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.checkmark_seal_fill, color: TeacherTheme.emeraldLight, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'تم إرسال الرد الصوتي للطالب 🎧',
|
||||
style: TextStyle(fontSize: 11.5, color: Color(0xFF6EE7B7), fontWeight: FontWeight.w700),
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.clock_fill, color: TeacherTheme.royalGold, size: 14),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'بانتظار إجابة المعلم',
|
||||
style: TextStyle(fontSize: 11.5, color: TeacherTheme.royalGold),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!d.replied)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _recordVoiceReply(d.id),
|
||||
icon: const Icon(CupertinoIcons.mic_fill, size: 14),
|
||||
label: const Text('تسجيل رد صوتي 🎙️', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF0284C7),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../core/theme/teacher_theme.dart';
|
||||
import '../../../logic/cubits/teacher_monetization_cubit.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - TAB 2: MONETIZATION & CLIQ PAYOUT
|
||||
* ==============================================================================
|
||||
*
|
||||
* إدارة التسييل المالي وتقاسم العوائد الثلاثي:
|
||||
* - 55% حصة المعلم المباشرة
|
||||
* - 15% حصة مديرية الثقافة العسكرية
|
||||
* - 30% حصة المنصة وتكاليف البنية التحتية
|
||||
* - نموذج التسعير المزدوج: 1,420 طالب مؤسسي مجاناً مقابل 380 طالباً مشتركاً مدفوعاً
|
||||
* - سحب فوري عبر كليك (CliQ Payout Queue).
|
||||
*/
|
||||
class TeacherMonetizationTab extends StatefulWidget {
|
||||
const TeacherMonetizationTab({super.key});
|
||||
|
||||
@override
|
||||
State<TeacherMonetizationTab> createState() => _TeacherMonetizationTabState();
|
||||
}
|
||||
|
||||
class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
context.read<TeacherMonetizationCubit>().loadMonetization();
|
||||
}
|
||||
|
||||
void _showCliqPayoutDialog(BuildContext ctx, double availableBalance) {
|
||||
final TextEditingController cliqController = TextEditingController(text: 'AHMAD@CLIQ');
|
||||
final TextEditingController amountController = TextEditingController(text: availableBalance.toInt().toString());
|
||||
|
||||
showModalBottomSheet(
|
||||
context: ctx,
|
||||
backgroundColor: TeacherTheme.surfaceCard,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
builder: (bCtx) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(bCtx).viewInsets.bottom + 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 24,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.bolt_fill, color: TeacherTheme.emeraldPrimary, size: 22),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'طلب سحب فوري عبر كليك (CliQ)',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(bCtx),
|
||||
icon: const Icon(CupertinoIcons.xmark_circle_fill, color: Color(0xFF64748B)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const Text(
|
||||
'تتم معالجة الحوالة فورياً وإدراجها في طابور السحب الآلي لحسابك البنكي.',
|
||||
style: TextStyle(fontSize: 12, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: cliqController,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'معرّف كليك (CliQ Alias) أو رقم الهاتف',
|
||||
labelStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF1E293B),
|
||||
prefixIcon: const Icon(CupertinoIcons.person_crop_circle, color: TeacherTheme.emeraldPrimary, size: 20),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: amountController,
|
||||
keyboardType: TextInputType.number,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'المبلغ المطلوب سحبه (دينار أردني)',
|
||||
labelStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF1E293B),
|
||||
prefixIcon: const Icon(CupertinoIcons.money_dollar, color: TeacherTheme.emeraldPrimary, size: 20),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
final double? amt = double.tryParse(amountController.text);
|
||||
if (amt != null && amt > 0) {
|
||||
ctx.read<TeacherMonetizationCubit>().submitCliqPayout(
|
||||
cliqAlias: cliqController.text,
|
||||
amountJod: amt,
|
||||
);
|
||||
}
|
||||
Navigator.pop(bCtx);
|
||||
ScaffoldMessenger.of(ctx).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('تم إدراج طلب السحب بمبلغ ${amountController.text} د.أ في طابور كليك بنجاح ⚡'),
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.paperplane_fill, size: 16),
|
||||
label: const Text('تأكيد وإرسال لطابور السحب الفوري (CliQ Queue)'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TeacherMonetizationCubit, TeacherMonetizationState>(
|
||||
builder: (context, state) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Dual Pricing Audience Breakdown Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: TeacherTheme.surfaceBorder),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'تصنيف قاعدة الطلبة (المؤسسي مقابل الخارجي) 👥',
|
||||
style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
'تحديث الكشوفات: اليوم',
|
||||
style: TextStyle(fontSize: 11, color: Color(0xFF64748B)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF064E3B).withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: TeacherTheme.emeraldPrimary.withOpacity(0.3)),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'طلبة الثقافة العسكرية والمدارس الشريكة',
|
||||
style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.w700, color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
'مشمولون برسم (0.00 د.أ) ضمن العقد المؤسسي',
|
||||
style: TextStyle(fontSize: 11, color: TeacherTheme.emeraldLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'1,420 طالباً 🎖️',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: TeacherTheme.emeraldLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0369A1).withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFF0284C7).withOpacity(0.3)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'الطلبة المستقلون (سوق صَقِل المفتوح)',
|
||||
style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.w700, color: Colors.white),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
'مشتركون عبر نظام الدفع الفوري كليك (CliQ)',
|
||||
style: TextStyle(fontSize: 11, color: TeacherTheme.cyberCyan),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'${state.studentSubscribers.toInt()} طالباً 💳',
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: TeacherTheme.cyberCyan),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Wallet Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF064E3B), Color(0xFF062E25)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: TeacherTheme.emeraldPrimary.withOpacity(0.4)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: TeacherTheme.emeraldPrimary.withOpacity(0.15),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'الرصيد المتاح للسحب (حصة المعلم 55%):',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF6EE7B7),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Text(
|
||||
'CliQ & IBAN',
|
||||
style: TextStyle(fontSize: 11, color: TeacherTheme.emeraldLight, fontWeight: FontWeight.w700),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'${state.teacherShare.toStringAsFixed(0)} دينار أردني',
|
||||
style: const TextStyle(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
_showCliqPayoutDialog(context, state.teacherShare);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.bolt_fill, size: 16),
|
||||
label: const Text(
|
||||
'سحب فوري عبر كليك (CliQ)',
|
||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w800),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('تم تقديم طلب التحويل لحسابك البنكي IBAN بنجاح.'),
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.building_2_fill, size: 16, color: Colors.white),
|
||||
label: const Text('تحويل بنكي', style: TextStyle(fontSize: 12, color: Colors.white)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
side: BorderSide(color: Colors.white.withOpacity(0.3)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Interactive Revenue Share Formula
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: TeacherTheme.surfaceBorder),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'نموذج تقاسم العوائد الثلاثي المعتمد (الفصل السابع):',
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
|
||||
// Split Bars
|
||||
_splitRow(
|
||||
'حصة المعلم المباشرة (55%):',
|
||||
'${state.teacherShare.toStringAsFixed(0)} د.أ',
|
||||
TeacherTheme.emeraldPrimary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_splitRow(
|
||||
'حصة مديرية الثقافة العسكرية (15%):',
|
||||
'${state.directorateShare.toStringAsFixed(0)} د.أ',
|
||||
TeacherTheme.royalGold,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_splitRow(
|
||||
'حصة منصة صَقِل للبنية والسيرفرات (30%):',
|
||||
'${state.platformShare.toStringAsFixed(0)} د.أ',
|
||||
TeacherTheme.cyberCyan,
|
||||
),
|
||||
|
||||
const Divider(color: TeacherTheme.surfaceBorder, height: 24),
|
||||
|
||||
// Subscribers Slider
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'محاكي المشتركين خارج الثقافة:',
|
||||
style: TextStyle(fontSize: 12, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
Text(
|
||||
'${state.studentSubscribers.toInt()} طالب مشترك',
|
||||
style: const TextStyle(fontSize: 13.5, fontWeight: FontWeight.w800, color: TeacherTheme.emeraldLight),
|
||||
),
|
||||
],
|
||||
),
|
||||
Slider(
|
||||
value: state.studentSubscribers,
|
||||
min: 50,
|
||||
max: 2000,
|
||||
divisions: 39,
|
||||
activeColor: TeacherTheme.emeraldPrimary,
|
||||
inactiveColor: TeacherTheme.surfaceBorder,
|
||||
onChanged: (val) {
|
||||
context.read<TeacherMonetizationCubit>().updateSubscribers(val);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Active Courses Selling
|
||||
const Text(
|
||||
'الدورات المنشورة في سوق صَقِل الخارجي:',
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
||||
_courseEarningCard(
|
||||
title: 'الفيزياء للتوجيهي العلمي (الفصل الأول)',
|
||||
students: 240,
|
||||
revenue: '2,640 د.أ',
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_courseEarningCard(
|
||||
title: 'المكثف الشامل لقوانين نيوتن وحفظ الطاقة',
|
||||
students: 140,
|
||||
revenue: '1,540 د.أ',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _splitRow(String label, String amount, Color color) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(width: 8, height: 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
|
||||
const SizedBox(width: 8),
|
||||
Text(label, style: const TextStyle(fontSize: 12.5, color: Colors.white)),
|
||||
],
|
||||
),
|
||||
Text(amount, style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w900, color: color)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _courseEarningCard({
|
||||
required String title,
|
||||
required int students,
|
||||
required String revenue,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: TeacherTheme.surfaceBorder),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w800, color: Colors.white)),
|
||||
const SizedBox(height: 3),
|
||||
Text('$students طالب مشترك · رسوم 20 د.أ', style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(revenue, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: TeacherTheme.emeraldPrimary)),
|
||||
const Text('صافي أرباحك', style: TextStyle(fontSize: 10, color: Color(0xFF64748B))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/teacher_theme.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - TAB 3: REPUTATION & CERTIFICATION SCORECARD
|
||||
* ==============================================================================
|
||||
*
|
||||
* بطاقة تقييم الأداء والاعتماد الأكاديمي للمعلم:
|
||||
* - وسام المعلم المعتمد رسمياً فوق 90% (Saqel Certified Teacher)
|
||||
* - رادار الجودة التربوية (نتاجات المنهاج، الفواصل السقراطية، السلامة اللغوية، والتركيز 20-25 دقيقة)
|
||||
* - توجيهات الذكاء الاصطناعي الأسبوعية للتحسين المستمر.
|
||||
*/
|
||||
class TeacherReputationScorecardTab extends StatelessWidget {
|
||||
const TeacherReputationScorecardTab({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Top Merit Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF1E1B4B), Color(0xFF0F172A)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: const Color(0xFF8B5CF6).withOpacity(0.5)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const CircleAvatar(
|
||||
radius: 32,
|
||||
backgroundColor: Color(0xFF8B5CF6),
|
||||
child: Icon(CupertinoIcons.rosette, size: 36, color: Colors.white),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'أ. أحمد المجالي',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.emeraldPrimary.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Text(
|
||||
'🏅 معلم معتمد رسمياً من منصة صَقِل (فوق 90%)',
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: TeacherTheme.emeraldLight,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_ScoreStat(label: 'التقييم العام', value: '4.92 / 5.0'),
|
||||
_ScoreStat(label: 'الطلاب المخدومون', value: '1,240 طالب'),
|
||||
_ScoreStat(label: 'نسبة النجاح', value: '98.2%'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Pedagogical Metrics Breakdown
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
border: Border.all(color: TeacherTheme.surfaceBorder),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'رادار الجودة التربوية المعتمد:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
_metricProgress('الالتزام بالمنهاج والنتاجات الوزارية', 0.96, TeacherTheme.emeraldPrimary),
|
||||
const SizedBox(height: 12),
|
||||
_metricProgress('التفاعل السقراطي وإثارة التفكير', 0.89, TeacherTheme.cyberCyan),
|
||||
const SizedBox(height: 12),
|
||||
_metricProgress('الوضوح الصوتي وسلامة اللغة', 0.94, TeacherTheme.royalGold),
|
||||
const SizedBox(height: 12),
|
||||
_metricProgress('إدارة الوقت والتركيز الإدراكي (20-25 دقيقة)', 0.92, const Color(0xFF8B5CF6)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// AI Growth Insights Box
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1E293B),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: const Color(0xFF334155)),
|
||||
),
|
||||
child: const Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(CupertinoIcons.lightbulb_fill, color: TeacherTheme.royalGold, size: 20),
|
||||
SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'توجيه الذكاء الاصطناعي الأسبوعي: نسبة الالتزام الوزاري ممتازة (96%). يُوصى بإضافة وقفة سقراطية استنتاجية في الدقيقة 14 من الدرس القادم لتعزيز تفاعل الطلبة.',
|
||||
style: TextStyle(fontSize: 12, color: Color(0xFFCBD5E1), height: 1.4),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _metricProgress(String title, double value, Color color) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(title, style: const TextStyle(fontSize: 12, color: Colors.white)),
|
||||
Text('${(value * 100).toInt()}%', style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800, color: color)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: value,
|
||||
minHeight: 6,
|
||||
backgroundColor: const Color(0xFF1E293B),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ScoreStat extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
|
||||
const _ScoreStat({required this.label, required this.value});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(value, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w900, color: Colors.white)),
|
||||
const SizedBox(height: 2),
|
||||
Text(label, style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../core/theme/teacher_theme.dart';
|
||||
import '../../../data/models/teacher_models.dart';
|
||||
import '../../../logic/cubits/teacher_studio_cubit.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - TAB 1: LESSON UPLOAD & QUALITY GATE
|
||||
* ==============================================================================
|
||||
*
|
||||
* بوابة جودة الحصص واستوديو الرفع:
|
||||
* - التحقق الإدراكي من معيار معهد ماساتشوستس للتركيز الذهني (20-25 دقيقة)
|
||||
* - فحص نتاجات المنهاج، نقاء الصوت، والمحطات السقراطية الذكية
|
||||
* - زر الاعتماد والنشر المشفر في سوق التسييل.
|
||||
*/
|
||||
class TeacherStudioUploadTab extends StatefulWidget {
|
||||
const TeacherStudioUploadTab({super.key});
|
||||
|
||||
@override
|
||||
State<TeacherStudioUploadTab> createState() => _TeacherStudioUploadTabState();
|
||||
}
|
||||
|
||||
class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
|
||||
late final TextEditingController _titleController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_titleController = TextEditingController(
|
||||
text: context.read<TeacherStudioCubit>().state.lessonTitle,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_titleController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TeacherStudioCubit, TeacherStudioState>(
|
||||
builder: (context, state) {
|
||||
final bool isDurationGood = state.durationMinutes >= 15.0 && state.durationMinutes <= 25.0;
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Banner
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF064E3B), Color(0xFF0F172A)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: TeacherTheme.emeraldPrimary.withOpacity(0.4)),
|
||||
),
|
||||
child: const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.sparkles, color: TeacherTheme.emeraldLight, size: 20),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'بوابة جودة حصص الأستوديو الرقمية (صَقِل 2.0)',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
Text(
|
||||
'تخضع كل حصة رقمية لمعايير التركيز الإدراكي (20-25 دقيقة كحد أقصى) وفحص الذكاء الاصطناعي بنسبة قبول لا تقل عن 85% قبل نشرها وتسييلها.',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF94A3B8),
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Upload Details Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: TeacherTheme.surfaceBorder),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'عنوان الحصة الصفية الرقمية:',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _titleController,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13.5),
|
||||
onChanged: (val) => context.read<TeacherStudioCubit>().updateLessonTitle(val),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF161F30),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Color(0xFF334155)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Duration Slider
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'مدة الشرح الرقمي:',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isDurationGood
|
||||
? TeacherTheme.emeraldPrimary.withOpacity(0.2)
|
||||
: TeacherTheme.crimsonRed.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'${state.durationMinutes.toStringAsFixed(1)} دقيقة',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: isDurationGood ? TeacherTheme.emeraldLight : TeacherTheme.crimsonRed,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Slider(
|
||||
value: state.durationMinutes,
|
||||
min: 5.0,
|
||||
max: 45.0,
|
||||
divisions: 40,
|
||||
activeColor: isDurationGood ? TeacherTheme.emeraldPrimary : TeacherTheme.crimsonRed,
|
||||
inactiveColor: TeacherTheme.surfaceBorder,
|
||||
onChanged: (val) {
|
||||
context.read<TeacherStudioCubit>().updateDuration(val);
|
||||
},
|
||||
),
|
||||
|
||||
// Cognitive Indicator Message
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: isDurationGood
|
||||
? const Color(0xFF064E3B).withOpacity(0.2)
|
||||
: const Color(0xFF7F1D1D).withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: isDurationGood
|
||||
? TeacherTheme.emeraldPrimary.withOpacity(0.4)
|
||||
: TeacherTheme.crimsonRed.withOpacity(0.4),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isDurationGood
|
||||
? CupertinoIcons.check_mark_circled_solid
|
||||
: CupertinoIcons.exclamationmark_triangle_fill,
|
||||
size: 18,
|
||||
color: isDurationGood ? TeacherTheme.emeraldLight : TeacherTheme.crimsonRed,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
isDurationGood
|
||||
? 'مثالي: الشرح ضمن المدى الإدراكي القياسي (20 إلى 25 دقيقة) لمعادلة تركيز الحصة الصفية.'
|
||||
: 'تحذير إدراكي: الشرح يتجاوز 25 دقيقة! ينخفض الاستيعاب بعد الدقيقة 18، يرجى اختصار المقطع أو تقسيمه.',
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
color: isDurationGood ? const Color(0xFF6EE7B7) : const Color(0xFFFCA5A5),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// Run Audit Button
|
||||
ElevatedButton.icon(
|
||||
onPressed: state.isAuditing
|
||||
? null
|
||||
: () {
|
||||
context.read<TeacherStudioCubit>().runQualityGate();
|
||||
},
|
||||
icon: state.isAuditing
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black),
|
||||
)
|
||||
: const Icon(CupertinoIcons.checkmark_shield_fill, size: 18),
|
||||
label: Text(
|
||||
state.isAuditing
|
||||
? 'جاري التدقيق التربوي عبر محرك صَقِل...'
|
||||
: 'فحص الحصة عبر بوابة الجودة الذكية (عتبة 85%) ⚡',
|
||||
style: const TextStyle(fontSize: 13.5, fontWeight: FontWeight.w800),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
foregroundColor: Colors.black,
|
||||
minimumSize: const Size(double.infinity, 44),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Quality Audit Result Card
|
||||
if (state.auditResult != null) _buildAuditResultCard(context, state.auditResult!),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAuditResultCard(BuildContext context, TeacherLessonAuditModel res) {
|
||||
final int score = res.qualityScore;
|
||||
final bool isApproved = score >= 85;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.surfaceCard,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: isApproved ? TeacherTheme.emeraldPrimary : TeacherTheme.crimsonRed,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isApproved ? CupertinoIcons.checkmark_seal_fill : CupertinoIcons.xmark_seal_fill,
|
||||
color: isApproved ? TeacherTheme.emeraldPrimary : TeacherTheme.crimsonRed,
|
||||
size: 22,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
isApproved ? 'حصة معتمدة للبث والتسييل 🏅' : 'تجميد الحصة للمراجعة ⚠️',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: isApproved ? TeacherTheme.emeraldPrimary : TeacherTheme.crimsonRed,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isApproved
|
||||
? TeacherTheme.emeraldPrimary.withOpacity(0.2)
|
||||
: TeacherTheme.crimsonRed.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'درجة التقييم: $score%',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: isApproved ? TeacherTheme.emeraldLight : TeacherTheme.crimsonRed,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
res.decision,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Colors.white,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
const Divider(color: TeacherTheme.surfaceBorder, height: 20),
|
||||
|
||||
// Breakdown
|
||||
_rowMetric('التطابق مع نتاجات المنهاج:', res.curriculumAlignment),
|
||||
const SizedBox(height: 6),
|
||||
_rowMetric('نقاء الصوت ومخارج الحروف:', res.audioClarity),
|
||||
const SizedBox(height: 6),
|
||||
_rowMetric('الفواصل السقراطية التفاعلية:', '${res.socraticStopsCount} محطات تفكيرية إلزامية'),
|
||||
const SizedBox(height: 14),
|
||||
|
||||
if (isApproved)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('تم ترحيل الحصة لتقطيع البث المشفر ونشرها في سوق صَقِل! 🚀'),
|
||||
backgroundColor: TeacherTheme.emeraldPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.cloud_upload_fill, size: 16),
|
||||
label: const Text('نشر الحصة في سوق التسييل التجاري 💰'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: TeacherTheme.emeraldDark,
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 42),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _rowMetric(String label, dynamic value) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontSize: 12, color: Color(0xFF94A3B8))),
|
||||
Text('$value', style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w700, color: Color(0xFFCBD5E1))),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../core/theme/teacher_theme.dart';
|
||||
import 'tabs/teacher_studio_upload_tab.dart';
|
||||
import 'tabs/teacher_monetization_tab.dart';
|
||||
import 'tabs/teacher_reputation_scorecard_tab.dart';
|
||||
import 'tabs/teacher_assignments_qna_tab.dart';
|
||||
|
||||
/**
|
||||
* ==============================================================================
|
||||
* SAQEL TEACHER STUDIO - MAIN SHELL
|
||||
* ==============================================================================
|
||||
*
|
||||
* الهيكل التنفيذي الرئيسي لاستوديو المعلم المعتمد:
|
||||
* - شريط علوي يحمل هوية المعلم، المدرسة، ودرجة الاعتماد الأكاديمي (94%)
|
||||
* - تنقل سفلي مرن بين استوديو الرفع، محفظة التسييل، رادار الأداء، وغرفة الأسئلة.
|
||||
*/
|
||||
class TeacherMainShell extends StatefulWidget {
|
||||
const TeacherMainShell({super.key});
|
||||
|
||||
@override
|
||||
State<TeacherMainShell> createState() => _TeacherMainShellState();
|
||||
}
|
||||
|
||||
class _TeacherMainShellState extends State<TeacherMainShell> {
|
||||
int _currentTabIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: Scaffold(
|
||||
backgroundColor: TeacherTheme.backgroundDark,
|
||||
appBar: AppBar(
|
||||
backgroundColor: TeacherTheme.surfaceDark,
|
||||
elevation: 0,
|
||||
title: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [TeacherTheme.emeraldPrimary, TeacherTheme.emeraldDark],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Center(
|
||||
child: Text(
|
||||
'م',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'استوديو المعلم المعتمد',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Icon(CupertinoIcons.checkmark_seal_fill, color: TeacherTheme.emeraldPrimary, size: 16),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'أ. أحمد المجالي · فيزياء التوجيهي (الثقافة العسكرية)',
|
||||
style: TextStyle(fontSize: 11, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: TeacherTheme.emeraldPrimary.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: TeacherTheme.emeraldPrimary.withOpacity(0.4)),
|
||||
),
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.star_fill, color: TeacherTheme.royalGold, size: 14),
|
||||
SizedBox(width: 4),
|
||||
Text(
|
||||
'معتمد 94%',
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: TeacherTheme.emeraldLight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: IndexedStack(
|
||||
index: _currentTabIndex,
|
||||
children: const [
|
||||
TeacherStudioUploadTab(),
|
||||
TeacherMonetizationTab(),
|
||||
TeacherReputationScorecardTab(),
|
||||
TeacherAssignmentsQnATab(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: TeacherTheme.surfaceDark,
|
||||
border: Border(top: BorderSide(color: TeacherTheme.surfaceBorder)),
|
||||
),
|
||||
child: BottomNavigationBar(
|
||||
currentIndex: _currentTabIndex,
|
||||
onTap: (idx) => setState(() => _currentTabIndex = idx),
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedItemColor: TeacherTheme.emeraldPrimary,
|
||||
unselectedItemColor: const Color(0xFF64748B),
|
||||
selectedLabelStyle: const TextStyle(fontWeight: FontWeight.w800, fontSize: 11),
|
||||
unselectedLabelStyle: const TextStyle(fontSize: 10.5),
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 0,
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(CupertinoIcons.videocam_circle_fill),
|
||||
label: 'استوديو الحصص',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(CupertinoIcons.money_dollar_circle_fill),
|
||||
label: 'محفظة التسييل',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(CupertinoIcons.chart_bar_circle_fill),
|
||||
label: 'رادار الأداء',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(CupertinoIcons.chat_bubble_2_fill),
|
||||
label: 'الواجبات والأسئلة',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user