Update Saqel Platform: 2026-09-08 13:43:36

This commit is contained in:
Hamza-Ayed
2026-09-08 13:43:36 +03:00
parent 0f424062fe
commit bb37b31cab
76 changed files with 3617 additions and 3059 deletions
@@ -79,7 +79,14 @@ class _TeacherAuthScreenState extends State<TeacherAuthScreen> {
SaqelToast.showError(context, 'يرجى إدخال رمز التحقق المكون من 6 أرقام');
return;
}
context.read<TeacherAuthCubit>().verifyOtp(phone, otp, fullName: _fullNameController.text.trim());
context.read<TeacherAuthCubit>().verifyOtp(
phone,
otp,
fullName: _fullNameController.text.trim(),
specialization: _selectedSubject,
schoolName: _schoolController.text.trim(),
gradesTaught: _selectedGrades.toList(),
);
}
@override
@@ -109,6 +116,7 @@ class _TeacherAuthScreenState extends State<TeacherAuthScreen> {
},
builder: (context, state) {
final isLoading = state is TeacherAuthLoading;
final isOtpStep = _isEnteringOtp || state is TeacherAuthOtpSent;
return Container(
padding: const EdgeInsets.all(28),
@@ -217,7 +225,7 @@ class _TeacherAuthScreenState extends State<TeacherAuthScreen> {
),
const SizedBox(height: 24),
if (!_isEnteringOtp) ...[
if (!isOtpStep) ...[
// Step 1: Teacher Info & Phone
const Text(
'اسم المعلم المعتمد:',
@@ -338,20 +346,46 @@ class _TeacherAuthScreenState extends State<TeacherAuthScreen> {
),
),
] else ...[
// Step 2: OTP Input
Text(
'تم إرسال رمز التحقق المكون من 6 أرقام إلى الرقم ${_phoneController.text}',
style: const TextStyle(color: Colors.white70, fontSize: 13),
textAlign: TextAlign.center,
// Step 2: OTP Input (WhatsApp Gateway)
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0C241D),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF25D366).withOpacity(0.5)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(CupertinoIcons.chat_bubble_2_fill, color: Color(0xFF25D366), size: 20),
SizedBox(width: 8),
Text(
'تم إرسال الرمز عبر الواتساب بنجاح 📲',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
),
],
),
const SizedBox(height: 6),
Text(
'أدخل رمز التحقق المرسل إلى: ${_phoneController.text}',
style: const TextStyle(color: TeacherTheme.emeraldLight, fontSize: 13, fontWeight: FontWeight.w700),
textAlign: TextAlign.center,
),
],
),
),
const SizedBox(height: 20),
_buildTextField(
controller: _otpController,
hint: 'أدخل رمز الـ OTP (مثال: 123456)',
icon: CupertinoIcons.lock_shield_fill,
keyboardType: TextInputType.number,
const SizedBox(height: 22),
const Text(
'رمز التحقق المعتمد (OTP):',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
),
const SizedBox(height: 8),
_buildOtpField(controller: _otpController),
const SizedBox(height: 20),
ElevatedButton(
onPressed: isLoading ? null : _onVerifyOtp,
style: ElevatedButton.styleFrom(
@@ -359,6 +393,7 @@ class _TeacherAuthScreenState extends State<TeacherAuthScreen> {
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
elevation: 0,
),
child: isLoading
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(color: Colors.black, strokeWidth: 2))
@@ -367,10 +402,25 @@ class _TeacherAuthScreenState extends State<TeacherAuthScreen> {
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w900),
),
),
const SizedBox(height: 12),
TextButton(
onPressed: () => setState(() => _isEnteringOtp = false),
child: const Text('تعديل رقم الهاتف والبيانات', style: TextStyle(color: TeacherTheme.emeraldLight)),
const SizedBox(height: 14),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton.icon(
onPressed: isLoading ? null : _onSendOtp,
icon: const Icon(CupertinoIcons.refresh_circled, size: 16, color: Color(0xFF25D366)),
label: const Text('إعادة إرسال الرمز 📲', style: TextStyle(color: Color(0xFF25D366), fontSize: 12, fontWeight: FontWeight.bold)),
),
TextButton.icon(
onPressed: () {
setState(() => _isEnteringOtp = false);
context.read<TeacherAuthCubit>().resetToPhone();
},
icon: const Icon(CupertinoIcons.pencil_ellipsis_rectangle, size: 16, color: Color(0xFF94A3B8)),
label: const Text('تعديل رقم الهاتف ✏️', style: TextStyle(color: Color(0xFF94A3B8), fontSize: 12)),
),
],
),
],
],
@@ -412,4 +462,47 @@ class _TeacherAuthScreenState extends State<TeacherAuthScreen> {
),
);
}
Widget _buildOtpField({
required TextEditingController controller,
}) {
return Container(
decoration: BoxDecoration(
color: TeacherTheme.surfaceCard,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: TeacherTheme.emeraldPrimary.withOpacity(0.6), width: 1.5),
boxShadow: const [
BoxShadow(
color: Color(0x2210B981),
blurRadius: 16,
offset: Offset(0, 4),
),
],
),
child: TextField(
controller: controller,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 6,
style: const TextStyle(
color: TeacherTheme.emeraldLight,
fontSize: 24,
fontWeight: FontWeight.w900,
letterSpacing: 10,
),
decoration: const InputDecoration(
counterText: '',
prefixIcon: Icon(CupertinoIcons.lock_shield_fill, color: TeacherTheme.emeraldPrimary, size: 22),
hintText: '• • • • • •',
hintStyle: TextStyle(
color: Color(0xFF475569),
fontSize: 20,
letterSpacing: 6,
),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
),
),
);
}
}
@@ -12,7 +12,7 @@ import '../../../logic/cubits/teacher_qna_cubit.dart';
* ==============================================================================
*
* غرفة استفسارات الطلبة الفعلية والبث الصوتي المباشر:
* - استفسارات وأسئلة الطلبة الميدانية (طالبات الصف العاشر مثل سما حمزة)
* - استفسارات وأسئلة الطلبة الميدانية المرتبطة بالحسابات الحقيقية
* - تسجيل الردود الصوتية السقراطية (Socratic Voice Notes 🎙️)
* - بث مقاطع صوتية توجيهية لكافة طلبة الشعبة عبر الويب سوكت (0ms Broadcast)
* - إسناد ومتابعة أوراق العمل المؤتمتة.
@@ -2,6 +2,7 @@ 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_monetization_cubit.dart';
/**
@@ -30,9 +31,13 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
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());
void _showCliqPayoutDialog(BuildContext ctx, double availableBalance, String defaultAlias) {
final TextEditingController cliqController = TextEditingController(
text: defaultAlias.isNotEmpty ? defaultAlias : '0798583052@CLIQ',
);
final TextEditingController amountController = TextEditingController(
text: availableBalance > 0 ? availableBalance.toInt().toString() : '50',
);
showModalBottomSheet(
context: ctx,
@@ -109,7 +114,7 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
final double? amt = double.tryParse(amountController.text);
if (amt != null && amt > 0) {
ctx.read<TeacherMonetizationCubit>().submitCliqPayout(
cliqAlias: cliqController.text,
cliqAlias: cliqController.text.trim(),
amountJod: amt,
);
}
@@ -136,6 +141,114 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
);
}
void _showIbanPayoutDialog(BuildContext ctx, double availableBalance) {
final TextEditingController ibanController = TextEditingController(text: 'JO94MEPA0000000000000000000000');
final TextEditingController amountController = TextEditingController(
text: availableBalance > 0 ? availableBalance.toInt().toString() : '100',
);
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.building_2_fill, color: TeacherTheme.cyberCyan, size: 22),
SizedBox(width: 8),
Text(
'طلب تحويل بنكي رسمي (IBAN Transfer)',
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: ibanController,
style: const TextStyle(color: Colors.white, fontSize: 13),
decoration: InputDecoration(
labelText: 'رقم الحساب المصرفي الدولي (IBAN)',
labelStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 12),
filled: true,
fillColor: const Color(0xFF1E293B),
prefixIcon: const Icon(CupertinoIcons.creditcard, color: TeacherTheme.cyberCyan, 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.cyberCyan, 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: ibanController.text.trim(),
amountJod: amt,
);
}
Navigator.pop(bCtx);
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(
content: Text('تم تسجيل أمر التحويل المصرفي بمبلغ ${amountController.text} د.أ بنجاح 🏦'),
backgroundColor: TeacherTheme.cyberCyan,
),
);
},
icon: const Icon(CupertinoIcons.checkmark_seal_fill, size: 16),
label: const Text('تأكيد أمر التحويل البنكي'),
style: ElevatedButton.styleFrom(
backgroundColor: TeacherTheme.cyberCyan,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<TeacherMonetizationCubit, TeacherMonetizationState>(
@@ -177,10 +290,10 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
borderRadius: BorderRadius.circular(12),
border: Border.all(color: TeacherTheme.emeraldPrimary.withOpacity(0.3)),
),
child: const Row(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
@@ -195,8 +308,8 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
],
),
Text(
'1,420 طالباً 🎖️',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: TeacherTheme.emeraldLight),
'${state.institutionalStudents.toInt()} طالباً 🎖️',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w900, color: TeacherTheme.emeraldLight),
),
],
),
@@ -286,7 +399,7 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
),
const SizedBox(height: 8),
Text(
'${state.teacherShare.toStringAsFixed(0)} دينار أردني',
'${state.availableBalance.toStringAsFixed(1)} دينار أردني',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.w900,
@@ -294,13 +407,20 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
letterSpacing: 0.5,
),
),
if (state.totalWithdrawn > 0) ...[
const SizedBox(height: 4),
Text(
'إجمالي المبالغ المسحوبة سابقاً: ${state.totalWithdrawn.toStringAsFixed(0)} د.أ',
style: const TextStyle(fontSize: 11.5, color: Color(0xFF6EE7B7)),
),
],
const SizedBox(height: 14),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
_showCliqPayoutDialog(context, state.teacherShare);
_showCliqPayoutDialog(context, state.availableBalance, state.cliqAlias);
},
icon: const Icon(CupertinoIcons.bolt_fill, size: 16),
label: const Text(
@@ -318,12 +438,7 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
const SizedBox(width: 8),
OutlinedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('تم تقديم طلب التحويل لحسابك البنكي IBAN بنجاح.'),
backgroundColor: TeacherTheme.emeraldPrimary,
),
);
_showIbanPayoutDialog(context, state.availableBalance);
},
icon: const Icon(CupertinoIcons.building_2_fill, size: 16, color: Colors.white),
label: const Text('تحويل بنكي', style: TextStyle(fontSize: 12, color: Colors.white)),
@@ -419,17 +534,39 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
),
const SizedBox(height: 10),
_courseEarningCard(
title: 'الفيزياء للتوجيهي العلمي (الفصل الأول)',
students: 240,
revenue: '2,640 د.أ',
),
const SizedBox(height: 8),
_courseEarningCard(
title: 'المكثف الشامل لقوانين نيوتن وحفظ الطاقة',
students: 140,
revenue: '1,540 د.أ',
),
if (state.courses.isNotEmpty)
...state.courses.map((course) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: _courseEarningCard(
title: course.title,
students: course.militaryStudents + course.externalSubscribers,
revenue: '${(course.priceJod * (course.externalSubscribers > 0 ? course.externalSubscribers : 1) * 0.55).toStringAsFixed(0)} د.أ',
),
)).toList()
else ...[
_courseEarningCard(
title: 'الفيزياء والعلوم التطبيقية — الصف العاشر الأساسي',
students: state.institutionalStudents.toInt(),
revenue: '${state.teacherShare.toStringAsFixed(0)} د.أ',
),
],
// Real CliQ & IBAN Payout History Queue
if (state.payoutRequests.isNotEmpty) ...[
const SizedBox(height: 18),
const Row(
children: [
Icon(CupertinoIcons.arrow_right_arrow_left, size: 16, color: TeacherTheme.emeraldPrimary),
SizedBox(width: 8),
Text(
'سجل عمليات وسحوبات كليك (CliQ & IBAN Queue):',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w800, color: Colors.white),
),
],
),
const SizedBox(height: 10),
...state.payoutRequests.map((p) => _payoutRow(p)).toList(),
],
],
),
);
@@ -437,6 +574,70 @@ class _TeacherMonetizationTabState extends State<TeacherMonetizationTab> {
);
}
Widget _payoutRow(TeacherPayoutRequestModel payout) {
final isDone = payout.status == 'completed' || payout.status == 'approved';
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: TeacherTheme.surfaceCard,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isDone ? TeacherTheme.emeraldPrimary.withOpacity(0.3) : TeacherTheme.surfaceBorder,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(
isDone ? CupertinoIcons.checkmark_circle_fill : CupertinoIcons.clock_fill,
size: 20,
color: isDone ? TeacherTheme.emeraldPrimary : TeacherTheme.royalGold,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
payout.cliqAlias,
style: const TextStyle(fontSize: 12.5, fontWeight: FontWeight.w700, color: Colors.white),
),
const SizedBox(height: 2),
Text(
payout.requestedAt,
style: const TextStyle(fontSize: 11, color: Color(0xFF64748B)),
),
],
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${payout.amountJod.toStringAsFixed(0)} د.أ',
style: TextStyle(
fontSize: 13.5,
fontWeight: FontWeight.w900,
color: isDone ? TeacherTheme.emeraldPrimary : TeacherTheme.royalGold,
),
),
Text(
isDone ? 'تم التحويل بنجاح' : 'في طابور المقاصة ⚡',
style: TextStyle(
fontSize: 10,
color: isDone ? TeacherTheme.emeraldLight : TeacherTheme.royalGold,
),
),
],
),
],
),
);
}
Widget _splitRow(String label, String amount, Color color) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/theme/teacher_theme.dart';
import '../../../logic/cubits/teacher_auth_cubit.dart';
import '../../../data/repositories/teacher_repository.dart';
/**
* ==============================================================================
* SAQEL TEACHER STUDIO - TAB 3: REPUTATION & CERTIFICATION SCORECARD
@@ -14,132 +16,185 @@ import '../../../logic/cubits/teacher_auth_cubit.dart';
* - رادار الجودة التربوية (نتاجات المنهاج، الفواصل السقراطية، السلامة اللغوية، والتركيز 20-25 دقيقة)
* - توجيهات الذكاء الاصطناعي الأسبوعية للتحسين المستمر.
*/
class TeacherReputationScorecardTab extends StatelessWidget {
class TeacherReputationScorecardTab extends StatefulWidget {
const TeacherReputationScorecardTab({super.key});
@override
State<TeacherReputationScorecardTab> createState() => _TeacherReputationScorecardTabState();
}
class _TeacherReputationScorecardTabState extends State<TeacherReputationScorecardTab> {
final TeacherRepository _repository = TeacherRepository();
bool _isLoading = true;
int _studentsEnrolled = 83;
double _compositeMerit = 96.5;
double _starRating = 4.92;
double _successRate = 98.2;
double _curriculumAlignment = 0.96;
double _socraticInteraction = 0.89;
double _audioClarity = 0.94;
double _cognitiveFocus = 0.92;
String _reputationTier = 'معلم نخبوي معتمد 💎';
String _aiGuidance = 'توجيه الذكاء الاصطناعي الأسبوعي: نسبة الالتزام الوزاري ممتازة (96%). يُوصى بإضافة وقفة سقراطية استنتاجية في الدقيقة 14 من الدرس القادم لتعزيز تفاعل الطلبة.';
@override
void initState() {
super.initState();
_loadReputation();
}
Future<void> _loadReputation() async {
try {
final data = await _repository.getMyReputation();
if (mounted) {
setState(() {
_studentsEnrolled = (data['total_students_enrolled'] as num?)?.toInt() ?? 83;
_compositeMerit = (data['composite_merit_score'] as num?)?.toDouble() ?? 96.5;
_starRating = (data['star_equivalent'] as num?)?.toDouble() ?? 4.92;
_successRate = (data['success_rate_percentage'] as num?)?.toDouble() ?? 98.2;
_curriculumAlignment = ((data['curriculum_alignment_pct'] as num?)?.toDouble() ?? 96.0) / 100.0;
_socraticInteraction = ((data['socratic_interaction_pct'] as num?)?.toDouble() ?? 89.0) / 100.0;
_audioClarity = ((data['audio_clarity_pct'] as num?)?.toDouble() ?? 94.0) / 100.0;
_cognitiveFocus = ((data['cognitive_focus_pct'] as num?)?.toDouble() ?? 92.0) / 100.0;
_reputationTier = data['reputation_tier']?.toString() ?? 'معلم نخبوي معتمد 💎';
_aiGuidance = data['ai_recommendation']?.toString() ?? _aiGuidance;
_isLoading = false;
});
}
} catch (_) {
if (mounted) setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
final authState = context.watch<TeacherAuthCubit>().state;
final teacherName = (authState is TeacherAuthenticated) ? authState.profile.name : 'المهندس حمزة الغويريين';
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,
return RefreshIndicator(
onRefresh: _loadReputation,
color: TeacherTheme.emeraldPrimary,
backgroundColor: TeacherTheme.surfaceCard,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
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)),
),
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),
Text(
teacherName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
color: Colors.white,
child: Column(
children: [
const CircleAvatar(
radius: 32,
backgroundColor: Color(0xFF8B5CF6),
child: Icon(CupertinoIcons.rosette, size: 36, 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: 12),
Text(
teacherName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w900,
color: Colors.white,
),
),
),
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: 4),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: TeacherTheme.emeraldPrimary.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'🏅 $_reputationTier (${_compositeMerit.toStringAsFixed(1)}%)',
style: const TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.w800,
color: TeacherTheme.emeraldLight,
),
),
),
),
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),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_ScoreStat(label: 'التقييم العام', value: '${_starRating.toStringAsFixed(2)} / 5.0'),
_ScoreStat(label: 'الطلاب المخدومون', value: '$_studentsEnrolled طالب'),
_ScoreStat(label: 'نسبة النجاح', value: '${_successRate.toStringAsFixed(1)}%'),
],
),
),
],
],
),
),
),
],
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('الالتزام بالمنهاج والنتاجات الوزارية', _curriculumAlignment, TeacherTheme.emeraldPrimary),
const SizedBox(height: 12),
_metricProgress('التفاعل السقراطي وإثارة التفكير', _socraticInteraction, TeacherTheme.cyberCyan),
const SizedBox(height: 12),
_metricProgress('الوضوح الصوتي وسلامة اللغة', _audioClarity, TeacherTheme.royalGold),
const SizedBox(height: 12),
_metricProgress('إدارة الوقت والتركيز الإدراكي (20-25 دقيقة)', _cognitiveFocus, 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: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Icon(CupertinoIcons.lightbulb_fill, color: TeacherTheme.royalGold, size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
_aiGuidance,
style: const TextStyle(fontSize: 12, color: Color(0xFFCBD5E1), height: 1.4),
),
),
],
),
),
],
),
),
);
}
@@ -1,6 +1,8 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:file_picker/file_picker.dart';
import '../../../core/theme/teacher_theme.dart';
import '../../../data/models/teacher_models.dart';
import '../../../logic/cubits/teacher_studio_cubit.dart';
@@ -39,11 +41,29 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
super.dispose();
}
Future<void> _pickVideo(BuildContext ctx) async {
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: const ['mp4', 'mov', 'webm'],
withData: kIsWeb,
);
if (!mounted || result == null || result.files.isEmpty) return;
final file = result.files.single;
ctx.read<TeacherStudioCubit>().selectVideoFile(
file.name,
file.size / (1024 * 1024),
ctx.read<TeacherStudioCubit>().state.durationMinutes,
path: file.path,
bytes: file.bytes,
);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<TeacherStudioCubit, TeacherStudioState>(
builder: (context, state) {
final bool isDurationGood = state.durationMinutes >= 15.0 && state.durationMinutes <= 25.0;
final bool isDurationGood = state.durationMinutes <= 25.0;
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
@@ -81,7 +101,7 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
),
SizedBox(height: 6),
Text(
'تخضع كل حصة رقمية لمعايير التركيز الإدراكي (20-25 دقيقة كحد أقصى) وفحص الذكاء الاصطناعي بنسبة قبول لا تقل عن 85% قبل نشرها وتسييلها.',
'تخضع كل حصة رقمية لمعايير التركيز الإدراكي (حتى 25 دقيقة) وفحص الذكاء الاصطناعي بنسبة قبول لا تقل عن 85% قبل نشرها وتسييلها.',
style: TextStyle(
fontSize: 12,
color: Color(0xFF94A3B8),
@@ -128,12 +148,83 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
),
const SizedBox(height: 16),
// Video Attachment Dropzone Card
const Text(
'ملف فيديو الحصة للشرح الرقمي (1080p):',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF161F30),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: state.selectedFileName != null ? TeacherTheme.emeraldPrimary : const Color(0xFF334155),
width: state.selectedFileName != null ? 1.5 : 1.0,
),
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: (state.selectedFileName != null ? TeacherTheme.emeraldPrimary : const Color(0xFF334155)).withOpacity(0.2),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
CupertinoIcons.videocam_fill,
color: state.selectedFileName != null ? TeacherTheme.emeraldLight : const Color(0xFF94A3B8),
size: 24,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.selectedFileName ?? 'لم يتم اختيار ملف فيديو بعد',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13),
),
const SizedBox(height: 2),
Text(
state.selectedFileName != null
? 'الحجم: ${state.selectedFileSizeMb ?? 48.2} MB • المدة المستخرجة: ${state.durationMinutes.toStringAsFixed(1)} دقيقة'
: 'الصيغ المدعومة: MP4, MOV, WebM (جودة 1080p)',
style: const TextStyle(color: Color(0xFF94A3B8), fontSize: 11),
),
],
),
),
ElevatedButton.icon(
onPressed: () => _pickVideo(context),
icon: const Icon(CupertinoIcons.folder_badge_plus, size: 15),
label: Text(state.selectedFileName != null ? 'تغيير الملف' : 'اختيار ملف 📁'),
style: ElevatedButton.styleFrom(
backgroundColor: state.selectedFileName != null ? const Color(0xFF1E293B) : TeacherTheme.emeraldPrimary,
foregroundColor: state.selectedFileName != null ? Colors.white : Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
textStyle: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.bold),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
],
),
),
const SizedBox(height: 16),
// Duration Slider
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'مدة الشرح الرقمي:',
'مدة الشرح الرقمي المستخرجة:',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
@@ -198,8 +289,8 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
Expanded(
child: Text(
isDurationGood
? 'مثالي: الشرح ضمن المدى الإدراكي القياسي (20 إلى 25 دقيقة) لمعادلة تركيز الحصة الصفية.'
: 'تحذير إدراكي: الشرح يتجاوز 25 دقيقة! ينخفض الاستيعاب بعد الدقيقة 18، يرجى اختصار المقطع أو تقسيمه.',
? 'مدة الشرح مثالية ومناسبة للاستيعاب الإدراكي المركز للطلبة (ضمن المدى المعتمد حتى 25 دقيقة).'
: 'تحذير إدراكي: الشرح يتجاوز 25 دقيقة! ينخفض استيعاب الطلبة تدريجياً، يرجى اختصار المقطع أو تقسيمه.',
style: TextStyle(
fontSize: 11.5,
color: isDurationGood ? const Color(0xFF6EE7B7) : const Color(0xFFFCA5A5),
@@ -246,7 +337,7 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
const SizedBox(height: 16),
// Quality Audit Result Card
if (state.auditResult != null) _buildAuditResultCard(context, state.auditResult!),
if (state.auditResult != null) _buildAuditResultCard(context, state, state.auditResult!),
],
),
);
@@ -254,7 +345,7 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
);
}
Widget _buildAuditResultCard(BuildContext context, TeacherLessonAuditModel res) {
Widget _buildAuditResultCard(BuildContext context, TeacherStudioState state, TeacherLessonAuditModel res) {
final int score = res.qualityScore;
final bool isApproved = score >= 85;
@@ -283,7 +374,7 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
),
const SizedBox(width: 8),
Text(
isApproved ? 'حصة معتمدة للبث والتسييل 🏅' : 'تجميد الحصة للمراجعة ⚠️',
isApproved ? 'جاهزة لرفع الملف والتحليل' : 'بيانات الحصة بحاجة للمراجعة',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w900,
@@ -301,7 +392,7 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
borderRadius: BorderRadius.circular(8),
),
child: Text(
'درجة التقييم: $score%',
'اكتمال فحص ما قبل الرفع: $score%',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w900,
@@ -329,27 +420,72 @@ class _TeacherStudioUploadTabState extends State<TeacherStudioUploadTab> {
_rowMetric('نقاء الصوت ومخارج الحروف:', res.audioClarity),
const SizedBox(height: 6),
_rowMetric('الفواصل السقراطية التفاعلية:', '${res.socraticStopsCount} محطات تفكيرية إلزامية'),
const SizedBox(height: 14),
const SizedBox(height: 16),
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)),
if (isApproved) ...[
if (state.isUploading)
Container(
padding: const EdgeInsets.symmetric(vertical: 14),
alignment: Alignment.center,
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(width: 18, height: 18, child: CircularProgressIndicator(color: TeacherTheme.emeraldPrimary, strokeWidth: 2)),
SizedBox(width: 12),
Text(
'جاري رفع الفيديو وبدء معالجة HLS والتحليل السقراطي... ⏳',
style: TextStyle(color: TeacherTheme.emeraldLight, fontSize: 12.5, fontWeight: FontWeight.bold),
),
],
),
)
else if (state.isUploaded)
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF064E3B).withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: TeacherTheme.emeraldPrimary),
),
child: Row(
children: [
const Icon(CupertinoIcons.checkmark_seal_fill, color: TeacherTheme.emeraldLight, size: 22),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
state.uploadSuccessMessage ?? 'تم رفع ونشر الحصة بنجاح في المنهاج وسوق صَقِل! 🚀',
style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w900),
),
const SizedBox(height: 2),
const Text(
'الحصة متاحة الآن لطلبة الصف العاشر وسوق التسييل التجاري 🎖️',
style: TextStyle(color: TeacherTheme.emeraldLight, fontSize: 11),
),
],
),
),
],
),
)
else
ElevatedButton.icon(
onPressed: () {
context.read<TeacherStudioCubit>().uploadAndPublishLesson();
},
icon: const Icon(CupertinoIcons.cloud_upload_fill, size: 18),
label: const Text('رفع ونشر الحصة في المنهاج وسوق التسييل 🚀', style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w900)),
style: ElevatedButton.styleFrom(
backgroundColor: TeacherTheme.emeraldDark,
foregroundColor: Colors.white,
minimumSize: const Size(double.infinity, 46),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 0,
),
),
),
],
],
),
);