Files
saqel/apps/admin_app/lib/presentation/screens/school_principal_screen.dart
T

1430 lines
59 KiB
Dart

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../data/models/directorate_models.dart';
import '../../services/directorate_api_service.dart';
class SchoolPrincipalScreen extends StatefulWidget {
const SchoolPrincipalScreen({super.key});
@override
State<SchoolPrincipalScreen> createState() => _SchoolPrincipalScreenState();
}
class _SchoolPrincipalScreenState extends State<SchoolPrincipalScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
bool _isLoading = true;
Map<String, dynamic>? _dashboardData;
// Recording State
bool _isRecording = false;
int _recordDurationSeconds = 0;
Timer? _recordTimer;
double _recordedSizeMb = 0.0;
String _selectedTeacher = 'أحمد المجالي';
String _selectedSubject = 'الفيزياء';
final TextEditingController _lessonTitleController =
TextEditingController(text: 'قوانين نيوتن في الحركة والمصاعد');
RecordedLessonResult? _lastResult;
// Exam state
bool _examPushedToLab = false;
bool _isPanoramicActive = false;
double _panoramicSizeMb = 16.5;
// Enterprise Integrity, Parent Reports & Roster State
bool _isRunningIntegrityAudit = false;
Map<String, dynamic>? _integrityResult;
bool _isDispatchingParentReports = false;
bool _isImportingRoster = false;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_loadData();
}
Future<void> _loadData() async {
setState(() => _isLoading = true);
final data = await DirectorateApiService.fetchSchoolDashboard();
if (mounted) {
setState(() {
_dashboardData = data;
_isLoading = false;
});
}
}
void _startRecording() {
setState(() {
_isRecording = true;
_recordDurationSeconds = 0;
_recordedSizeMb = 0.0;
_lastResult = null;
});
_recordTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) return;
setState(() {
_recordDurationSeconds++;
// Calculate simulated file size at 720p ~ 1.1 Mbps (approx 0.14 MB per second)
_recordedSizeMb = (_recordDurationSeconds * 0.14).clamp(0.0, 395.0);
});
});
}
Future<void> _stopAndAuditRecording() async {
_recordTimer?.cancel();
setState(() => _isRecording = false);
// Show quick processing dialog
showCupertinoDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => const Center(
child: CupertinoActivityIndicator(radius: 20),
),
);
final result = await DirectorateApiService.recordLesson(
teacherName: _selectedTeacher,
subject: _selectedSubject,
lessonTitle: _lessonTitleController.text.trim(),
fileSizeMb: _recordedSizeMb > 0 ? _recordedSizeMb : 340.0,
durationMinutes: (_recordDurationSeconds / 60).ceil().clamp(15, 45),
);
if (mounted) {
Navigator.of(context).pop(); // dismiss loading
setState(() {
_lastResult = result;
});
}
}
void _showDualFormsPreview() async {
await DirectorateApiService.fetchDualForms();
if (!mounted) return;
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (ctx) => Directionality(
textDirection: TextDirection.rtl,
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
decoration: const BoxDecoration(
color: Color(0xFF0F172A),
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
border: Border(top: BorderSide(color: Color(0xFF8B5CF6), width: 2)),
),
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: const Color(0xFF334155),
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 14),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'معاينة النماذج الموحدة (نموذج أ ونموذج ب)',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w800,
color: Colors.white,
),
),
IconButton(
icon: const Icon(CupertinoIcons.xmark, color: Colors.white70),
onPressed: () => Navigator.of(context).pop(),
),
],
),
const SizedBox(height: 10),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF1E1B4B),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF8B5CF6).withOpacity(0.3)),
),
child: const Text(
'المخطط الهجين: 70% أسئلة موضوعية مبرمجة على محطات الحاسوب + 30% خطوات إنشائية ورقية مطبوعة مع باركود تسلسلي مشفر.',
style: TextStyle(fontSize: 12, color: Color(0xFFC4B5FD), height: 1.4),
),
),
const SizedBox(height: 14),
Expanded(
child: Row(
children: [
// Form A Card
Expanded(
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF161F30),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF10B981).withOpacity(0.5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(CupertinoIcons.doc_checkmark_fill, color: Color(0xFF10B981), size: 16),
SizedBox(width: 6),
Text('النموذج (أ) — الفا', style: TextStyle(fontWeight: FontWeight.w800, color: Color(0xFF34D399), fontSize: 13)),
],
),
const SizedBox(height: 8),
const Text('الباركود التسلسلي:\nSAQEL-EXAM-A-ALPHA', style: TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
const Divider(color: Color(0xFF1E293B)),
const Text('• 20 مسألة موضوعية (70 علامة)\n• مسألتان إنشائيتان (30 علامة)\n• باركود موجه لكاميرا المشرف', style: TextStyle(fontSize: 11.5, color: Colors.white70, height: 1.6)),
],
),
),
),
const SizedBox(width: 10),
// Form B Card
Expanded(
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF161F30),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF38BDF8).withOpacity(0.5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(CupertinoIcons.doc_plaintext, color: Color(0xFF38BDF8), size: 16),
SizedBox(width: 6),
Text('النموذج (ب) — بيتا', style: TextStyle(fontWeight: FontWeight.w800, color: Color(0xFF38BDF8), fontSize: 13)),
],
),
const SizedBox(height: 8),
const Text('الباركود التسلسلي:\nSAQEL-EXAM-B-BETA', style: TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
const Divider(color: Color(0xFF1E293B)),
const Text('• خلط كامل لترتيب الأسئلة\n• تغيير معطيات الأرقام للمقاعد المتجاورة لمنع النقل', style: TextStyle(fontSize: 11.5, color: Colors.white70, height: 1.6)),
],
),
),
),
],
),
),
const SizedBox(height: 14),
ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('🖨️ تم إرسال 48 نسخة من النموذج أ وب ومسودات الباركود لطابعة المدرسة المركزية'),
backgroundColor: Color(0xFF10B981),
),
);
},
icon: const Icon(CupertinoIcons.printer_fill, size: 16),
label: const Text('أمر طباعة النماذج والباركود الآن'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8B5CF6),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
),
],
),
),
),
);
}
void _runIntegrityAudit() async {
setState(() => _isRunningIntegrityAudit = true);
final res = await DirectorateApiService.evaluateExamSessionIntegrity();
if (mounted) {
setState(() {
_integrityResult = res;
_isRunningIntegrityAudit = false;
});
}
}
void _dispatchParentReports() async {
setState(() => _isDispatchingParentReports = true);
final res = await DirectorateApiService.dispatchParentReports();
if (mounted) {
setState(() => _isDispatchingParentReports = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(res['message'] ?? 'تم إرسال التقارير لأولياء الأمور بنجاح'),
backgroundColor: const Color(0xFF10B981),
),
);
}
}
void _importSchoolRoster() async {
setState(() => _isImportingRoster = true);
final res = await DirectorateApiService.importSchoolRoster();
if (mounted) {
setState(() => _isImportingRoster = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('تم استيراد وتشفير ${res['imported_count']} طالباً بنجاح عبر AES-256-GCM السيادي 🔒'),
backgroundColor: const Color(0xFF0284C7),
),
);
}
}
@override
void dispose() {
_tabController.dispose();
_recordTimer?.cancel();
_lessonTitleController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Scaffold(
backgroundColor: Color(0xFF070B12),
body: Center(
child: CupertinoActivityIndicator(color: Color(0xFF8B5CF6), radius: 18),
),
);
}
final school = _dashboardData?['school'] ?? {};
final teachers = (_dashboardData?['teachers'] as List? ?? [])
.map((t) => TeacherItem.fromJson(t))
.toList();
return Scaffold(
backgroundColor: const Color(0xFF070B12),
appBar: AppBar(
backgroundColor: const Color(0xFF0E1626),
elevation: 0,
centerTitle: false,
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
school['name'] ?? 'إدارة المدرسة العسكرية',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 2),
Row(
children: [
const Icon(CupertinoIcons.location_solid,
size: 12, color: Color(0xFF94A3B8)),
const SizedBox(width: 4),
Text(
school['governorate'] ?? 'العاصمة - عمان',
style: const TextStyle(fontSize: 12, color: Color(0xFF94A3B8)),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'الفئة (ج) كبرى · 980 طالباً',
style: TextStyle(fontSize: 11, color: Color(0xFFA78BFA)),
),
)
],
)
],
),
bottom: TabBar(
controller: _tabController,
indicatorColor: const Color(0xFF8B5CF6),
indicatorWeight: 3,
labelColor: Colors.white,
unselectedLabelColor: const Color(0xFF64748B),
labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13.5),
tabs: const [
Tab(
icon: Icon(CupertinoIcons.video_camera_solid, size: 18),
text: 'تسجيل الحصص الصفية',
),
Tab(
icon: Icon(CupertinoIcons.person_2_fill, size: 18),
text: 'المعلمون والدورية',
),
Tab(
icon: Icon(CupertinoIcons.doc_text_fill, size: 18),
text: 'الامتحانات ومختبر الحاسوب',
),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
_buildLessonRecordingTab(teachers),
_buildTeachersQuotaTab(teachers),
_buildUnifiedExamsTab(),
],
),
);
}
// ---------------------------------------------------------------------------
// TAB 1: Classroom Video Recording & Pedagogical Quality Audit
// ---------------------------------------------------------------------------
Widget _buildLessonRecordingTab(List<TeacherItem> teachers) {
return SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Quota Banner
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [const Color(0xFF1E1B4B), const Color(0xFF0F172A)],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF4338CA).withOpacity(0.5)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFF4F46E5).withOpacity(0.2),
shape: BoxShape.circle,
),
child: const Icon(CupertinoIcons.calendar_badge_plus,
color: Color(0xFFA5B4FC), size: 24),
),
const SizedBox(width: 14),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'دورية التدقيق الميداني المعتمدة',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
SizedBox(height: 2),
Text(
'حصة مرئية واحدة لكل معلم كل أسبوعين (الحد الأقصى للحجم: 400 ميجابايت بدقة 720p)',
style: TextStyle(fontSize: 12, color: Color(0xFFCBD5E1)),
),
],
),
),
],
),
),
const SizedBox(height: 18),
// Camera Viewport Simulation Card
Container(
height: 240,
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: _isRecording
? const Color(0xFFEF4444)
: const Color(0xFF1E293B),
width: _isRecording ? 2 : 1,
),
boxShadow: _isRecording
? [
BoxShadow(
color: const Color(0xFFEF4444).withOpacity(0.2),
blurRadius: 20,
spreadRadius: 2,
)
]
: [],
),
child: Stack(
children: [
// Mock Classroom Background Grid
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
CupertinoIcons.videocam_fill,
size: 54,
color: _isRecording
? const Color(0xFFEF4444)
: const Color(0xFF475569),
),
const SizedBox(height: 10),
Text(
_isRecording
? 'جاري تصوير وقائع الحصة الصفية داخل الغرفة...'
: 'كاميرا المشرف جاهزة للتسجيل الصفي',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: _isRecording ? Colors.white : const Color(0xFF94A3B8),
),
),
const SizedBox(height: 4),
Text(
'ترميز ذكي 720p · محدد تلقائياً لسقف 400 ميجابايت',
style: TextStyle(
fontSize: 12, color: const Color(0xFF64748B)),
),
],
),
),
// Top Status Bar in Camera
Positioned(
top: 12,
left: 12,
right: 12,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (_isRecording)
Row(
children: [
Container(
width: 10,
height: 10,
decoration: const BoxDecoration(
color: Color(0xFFEF4444),
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
const Text(
'تسجيل نشط (REC)',
style: TextStyle(
color: Color(0xFFEF4444),
fontWeight: FontWeight.w800,
fontSize: 12),
),
],
)
else
const Text(
'جاهز',
style: TextStyle(color: Color(0xFF10B981), fontSize: 12),
),
// Metrics
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6),
borderRadius: BorderRadius.circular(20),
),
child: Text(
_isRecording
? '${_formatDuration(_recordDurationSeconds)} | ${_recordedSizeMb.toStringAsFixed(1)} MB / 400 MB'
: '00:00 | 0.0 MB',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w700),
),
)
],
),
),
// Bottom Action inside Camera
Positioned(
bottom: 14,
left: 14,
right: 14,
child: ElevatedButton(
onPressed: _isRecording
? _stopAndAuditRecording
: _startRecording,
style: ElevatedButton.styleFrom(
backgroundColor: _isRecording
? const Color(0xFFDC2626)
: const Color(0xFF8B5CF6),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
_isRecording
? CupertinoIcons.stop_fill
: CupertinoIcons.circle_fill,
size: 18),
const SizedBox(width: 8),
Text(
_isRecording
? 'إنهاء الحصة وإرسالها للتدقيق بالذكاء الاصطناعي 🚀'
: 'بدء تصوير الحصة الصفية 🎥',
style: const TextStyle(
fontSize: 15, fontWeight: FontWeight.w700),
),
],
),
),
),
],
),
),
const SizedBox(height: 18),
// Lesson Details Form
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF1E293B)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'بيانات الحصة والمعلم المستهدف:',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
value: _selectedTeacher,
dropdownColor: const Color(0xFF1E293B),
style: const TextStyle(color: Colors.white, fontSize: 13),
decoration: InputDecoration(
labelText: 'المعلم',
labelStyle: const TextStyle(color: Color(0xFF94A3B8)),
filled: true,
fillColor: const Color(0xFF161F30),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)),
),
items: teachers
.map((t) => DropdownMenuItem(
value: t.name, child: Text(t.name)))
.toList(),
onChanged: (val) {
if (val != null) setState(() => _selectedTeacher = val);
},
),
),
const SizedBox(width: 12),
Expanded(
child: DropdownButtonFormField<String>(
value: _selectedSubject,
dropdownColor: const Color(0xFF1E293B),
style: const TextStyle(color: Colors.white, fontSize: 13),
decoration: InputDecoration(
labelText: 'المادة',
labelStyle: const TextStyle(color: Color(0xFF94A3B8)),
filled: true,
fillColor: const Color(0xFF161F30),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)),
),
items: const [
DropdownMenuItem(value: 'الفيزياء', child: Text('الفيزياء')),
DropdownMenuItem(value: 'الرياضيات', child: Text('الرياضيات')),
DropdownMenuItem(value: 'الكيمياء', child: Text('الكيمياء')),
DropdownMenuItem(
value: 'اللغة الإنجليزية',
child: Text('اللغة الإنجليزية')),
],
onChanged: (val) {
if (val != null) setState(() => _selectedSubject = val);
},
),
),
],
),
const SizedBox(height: 12),
TextField(
controller: _lessonTitleController,
style: const TextStyle(color: Colors.white, fontSize: 13.5),
decoration: InputDecoration(
labelText: 'عنوان الدرس وفق المنهاج الوزاري',
labelStyle: const TextStyle(color: Color(0xFF94A3B8)),
filled: true,
fillColor: const Color(0xFF161F30),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10)),
),
),
],
),
),
const SizedBox(height: 18),
// AI Evaluation Card (if available)
if (_lastResult != null) _buildAiResultCard(_lastResult!),
],
),
);
}
Widget _buildAiResultCard(RecordedLessonResult res) {
return Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: const Color(0xFF111827),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF10B981).withOpacity(0.6)),
boxShadow: [
BoxShadow(
color: const Color(0xFF10B981).withOpacity(0.1),
blurRadius: 20,
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Row(
children: [
Icon(CupertinoIcons.checkmark_seal_fill,
color: Color(0xFF10B981), size: 22),
SizedBox(width: 8),
Text(
'بطاقة التدقيق التربوي المعتمدة بالذكاء الاصطناعي',
style: TextStyle(
fontSize: 14.5,
fontWeight: FontWeight.w700,
color: Colors.white),
),
],
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF10B981).withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${res.aiAlignmentScore}% مطابقة',
style: const TextStyle(
color: Color(0xFF10B981),
fontWeight: FontWeight.w800,
fontSize: 13),
),
),
],
),
const SizedBox(height: 12),
Text(
res.reportSummary,
style: const TextStyle(
fontSize: 13, color: Color(0xFFCBD5E1), height: 1.5),
),
const SizedBox(height: 14),
Row(
children: [
_metricChip('نسبة كلام المعلم', res.teacherTalkRatio),
const SizedBox(width: 8),
_metricChip('مدة الحصة', '${res.durationMinutes} دقيقة'),
const SizedBox(width: 8),
_metricChip('حجم الفيديو', '${res.fileSizeMb.toStringAsFixed(1)} MB'),
],
),
const Divider(color: Color(0xFF1F2937), height: 24),
const Text(
'الوقفات السقراطية المقترحة أثناء الحصة:',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: Color(0xFFA78BFA)),
),
const SizedBox(height: 8),
...res.socraticCheckpoints.map((cp) => Container(
margin: const EdgeInsets.only(bottom: 6),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Text(
cp['timestamp'] ?? '',
style: const TextStyle(
color: Color(0xFFF59E0B),
fontWeight: FontWeight.w700,
fontSize: 12),
),
const SizedBox(width: 10),
Expanded(
child: Text(
cp['question'] ?? '',
style:
const TextStyle(color: Colors.white, fontSize: 12.5),
),
),
],
),
)),
],
),
);
}
// ---------------------------------------------------------------------------
// TAB 2: Teachers & Bi-Weekly Quota Tracking
// ---------------------------------------------------------------------------
Widget _buildTeachersQuotaTab(List<TeacherItem> teachers) {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: teachers.length,
itemBuilder: (ctx, idx) {
final t = teachers[idx];
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFF1E293B)),
),
child: Row(
children: [
CircleAvatar(
radius: 22,
backgroundColor: t.isCompleted
? const Color(0xFF10B981).withOpacity(0.2)
: const Color(0xFFF59E0B).withOpacity(0.2),
child: Text(
t.name.split(' ').first,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: t.isCompleted
? const Color(0xFF10B981)
: const Color(0xFFF59E0B),
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
t.name,
style: const TextStyle(
fontSize: 14.5,
fontWeight: FontWeight.w700,
color: Colors.white),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 1),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(4),
),
child: Text(
t.subject,
style: const TextStyle(
fontSize: 11, color: Color(0xFF94A3B8)),
),
),
],
),
const SizedBox(height: 4),
Text(
'آخر حصة: ${t.lastLesson}',
style:
const TextStyle(fontSize: 12, color: Color(0xFF64748B)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: t.isCompleted
? const Color(0xFF065F46)
: const Color(0xFF78350F),
borderRadius: BorderRadius.circular(6),
),
child: Text(
t.isCompleted ? 'مكتمل الدورية' : 'متبقي هذا الأسبوع',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: t.isCompleted
? const Color(0xFF34D399)
: const Color(0xFFFBBF24),
),
),
),
const SizedBox(height: 4),
Text(
'التقييم: ${t.aiScore}%',
style: const TextStyle(
fontSize: 11.5,
color: Color(0xFFA78BFA),
fontWeight: FontWeight.w600),
),
],
),
],
),
);
},
);
}
// ---------------------------------------------------------------------------
// TAB 3: Unified Exams, Lab Dispatch & Smart Panoramic Surveillance
// ---------------------------------------------------------------------------
Widget _buildUnifiedExamsTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Active Exam Card
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [const Color(0xFF1E293B), const Color(0xFF0F172A)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF8B5CF6).withOpacity(0.5)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Row(
children: [
Icon(CupertinoIcons.doc_checkmark_fill,
color: Color(0xFFA78BFA), size: 20),
SizedBox(width: 8),
Text(
'امتحان وزاري موحد من مديرية الثقافة العسكرية',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.white),
),
],
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF8B5CF6).withOpacity(0.2),
borderRadius: BorderRadius.circular(6),
),
child: const Text(
'موعد موحد: 09:00 ص',
style: TextStyle(
fontSize: 11.5,
color: Color(0xFFA78BFA),
fontWeight: FontWeight.w700),
),
),
],
),
const SizedBox(height: 12),
const Text(
'الرياضيات العلمي - محاكاة امتحان التوجيهي الوزاري 2008',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
color: Colors.white),
),
const SizedBox(height: 6),
const Text(
'المدة: 60 دقيقة · جدول المواصفات الوزاري المعتمد · نموذجان (أ و ب)',
style: TextStyle(fontSize: 12.5, color: Color(0xFF94A3B8)),
),
const SizedBox(height: 16),
// Lab Push & Print Buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () async {
final ok = await DirectorateApiService.pushExamToLab(
'EX-MC-2026-01');
if (ok && mounted) {
setState(() => _examPushedToLab = true);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'✅ تم بث الامتحان لمحطات مختبر الحاسوب وتفعيل البيئة المقفلة (Kiosk Mode)'),
backgroundColor: Color(0xFF10B981),
),
);
}
},
icon: Icon(
_examPushedToLab
? CupertinoIcons.checkmark_alt_circle_fill
: CupertinoIcons.desktopcomputer,
size: 16),
label: Text(
_examPushedToLab
? 'تم البث لمختبر الحاسوب'
: 'إرسال لمختبر الحاسوب (32 جهازاً)',
style: const TextStyle(
fontSize: 12.5, fontWeight: FontWeight.w700),
),
style: ElevatedButton.styleFrom(
backgroundColor: _examPushedToLab
? const Color(0xFF059669)
: const Color(0xFF4F46E5),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: _showDualFormsPreview,
icon: const Icon(CupertinoIcons.doc_on_doc_fill, size: 16),
label: const Text('معاينة وطباعة النماذج (أ وب)',
style: TextStyle(fontSize: 12.5)),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8B5CF6),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
),
],
),
],
),
),
const SizedBox(height: 18),
// Smart Panoramic Surveillance Viewport
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: _isPanoramicActive
? const Color(0xFFEF4444)
: const Color(0xFF1E293B),
width: _isPanoramicActive ? 2 : 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: _isPanoramicActive
? const Color(0xFFEF4444)
: const Color(0xFF64748B),
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
const Text(
'الرقابة البانورامية بتقنية التقطيع الزمني الذكي',
style: TextStyle(
fontSize: 13.5,
fontWeight: FontWeight.w700,
color: Colors.white),
),
],
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'الحجم: ${_panoramicSizeMb.toStringAsFixed(1)} MB فقط',
style: const TextStyle(
fontSize: 11.5,
color: Color(0xFF38BDF8),
fontWeight: FontWeight.w700),
),
),
],
),
const SizedBox(height: 10),
const Text(
'المبدأ: تظهر الكاميرا نشطة طوال الامتحان لفرض الردع النفسي، لكن التطبيق يقتطع ثانيتين فقط كل دقيقة لينتج مقطعاً مجمعاً مدته دقيقتان فقط بحجم 16.5 ميجابايت.',
style: TextStyle(
fontSize: 12, color: Color(0xFF94A3B8), height: 1.5),
),
const SizedBox(height: 14),
// Toggle Panoramic Button
ElevatedButton.icon(
onPressed: () {
setState(() => _isPanoramicActive = !_isPanoramicActive);
if (_isPanoramicActive) {
DirectorateApiService.uploadPanoramicSample(
fileSizeMb: _panoramicSizeMb,
durationSeconds: 120,
);
}
},
icon: Icon(
_isPanoramicActive
? CupertinoIcons.eye_slash_fill
: CupertinoIcons.eye_fill,
size: 16),
label: Text(
_isPanoramicActive
? 'إيقاف نمط الرقابة البانورامية'
: 'تفعيل نمط الرقابة البانورامية في القاعة 👁️',
style: const TextStyle(
fontSize: 13, fontWeight: FontWeight.w700),
),
style: ElevatedButton.styleFrom(
backgroundColor: _isPanoramicActive
? const Color(0xFFB91C1C)
: const Color(0xFF0284C7),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
),
),
],
),
),
const SizedBox(height: 18),
// Anomaly Indicator Summary & Statistical Detection
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF1E293B)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'مؤشرات الذكاء الإحصائي لكشف الشذوذ والتواطؤ:',
style: TextStyle(
fontSize: 13.5,
fontWeight: FontWeight.w700,
color: Colors.white),
),
ElevatedButton.icon(
onPressed: _isRunningIntegrityAudit ? null : _runIntegrityAudit,
icon: _isRunningIntegrityAudit
? const SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
: const Icon(CupertinoIcons.waveform_path_ecg, size: 15),
label: Text(
_isRunningIntegrityAudit ? 'جاري الفحص...' : 'فحص النزاهة اللحظي ⚡',
style: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.w800),
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0284C7),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
],
),
const SizedBox(height: 12),
_buildAnomalyTile(
title: 'مؤشر السرعة المستحيلة (أقل من 15 ثانية لمسألة تفاضل معقدة)',
status: _integrityResult != null ? 'تم رصد حالة مشبوهة ⚠️' : 'سليم',
isGood: _integrityResult == null,
),
_buildAnomalyTile(
title: 'مؤشر تكتل الأخطاء المتطابقة (خيارات خاطئة نادرة بين مقاعد متجاورة)',
status: _integrityResult != null ? 'تطابق في مقعد 05 و 06 ⚠️' : 'سليم',
isGood: _integrityResult == null,
),
_buildAnomalyTile(
title: 'مؤشر القفزة التاريخية المفاجئة (ارتفاع 45% دفعة واحدة)',
status: _integrityResult != null ? 'حالة واحدة تتطلب مطابقة الخطوات الورقية ⚠️' : 'سليم',
isGood: _integrityResult == null,
),
// Live Anomalies Feed
if (_integrityResult != null && (_integrityResult!['anomalies'] as List? ?? []).isNotEmpty) ...[
const Divider(color: Color(0xFF1E293B), height: 20),
const Text('🚨 تنبيهات الشذوذ المكتشفة ومقترحات المشرف:', style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800, color: Color(0xFFEF4444))),
const SizedBox(height: 8),
...((_integrityResult!['anomalies'] as List).take(2).map((anom) => Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFF7F1D1D).withOpacity(0.25),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFEF4444).withOpacity(0.4)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${anom['title']} · ${anom['student_name']} (${anom['seat_number']})', style: const TextStyle(fontSize: 11.5, fontWeight: FontWeight.w800, color: Color(0xFFFCA5A5))),
const SizedBox(height: 4),
Text(anom['details'] ?? '', style: const TextStyle(fontSize: 11, color: Colors.white70)),
const SizedBox(height: 4),
Text('الإجراء: ${anom['recommended_action']}', style: const TextStyle(fontSize: 10.5, color: Color(0xFFFBBF24), fontWeight: FontWeight.w700)),
],
),
))),
],
],
),
),
const SizedBox(height: 18),
// Monthly Parent Reports Dispatch Card (Nabeh WhatsApp Gateway)
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: const Color(0xFF10B981).withOpacity(0.4)),
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFF10B981).withOpacity(0.2),
shape: BoxShape.circle,
),
child: const Icon(CupertinoIcons.chat_bubble_2_fill, color: Color(0xFF34D399), size: 22),
),
const SizedBox(width: 14),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'بث تقارير أولياء الأمور عبر الواتساب (بوابة نبيه) 📲',
style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w800, color: Colors.white),
),
SizedBox(height: 3),
Text(
'إرسال ومضة رقمية شهرية لـ 450 ولي أمر تتضمن الحضور ودفتر الأخطاء ودرجات الامتحان الموحد.',
style: TextStyle(fontSize: 11, color: Color(0xFF94A3B8), height: 1.3),
),
],
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _isDispatchingParentReports ? null : _dispatchParentReports,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF10B981),
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: _isDispatchingParentReports
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black))
: const Text('بث الدفعة الآن', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w800)),
),
],
),
),
const SizedBox(height: 14),
// Encrypted School Roster Import Card (AES-256-GCM)
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF0F172A),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF1E293B)),
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: const Color(0xFF38BDF8).withOpacity(0.2),
shape: BoxShape.circle,
),
child: const Icon(CupertinoIcons.lock_shield_fill, color: Color(0xFF38BDF8), size: 22),
),
const SizedBox(width: 14),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'استيراد كشف المدرسة وتشفير الأرقام الوطنية 🔒',
style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w800, color: Colors.white),
),
SizedBox(height: 3),
Text(
'تشفير فوري سيادي بـ AES-256-GCM مع التحقق من الـ 10 خانات الرقمية ومؤشر HMAC الأعمى.',
style: TextStyle(fontSize: 11, color: Color(0xFF94A3B8), height: 1.3),
),
],
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: _isImportingRoster ? null : _importSchoolRoster,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0284C7),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: _isImportingRoster
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
: const Text('استيراد الكشف', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w800)),
),
],
),
),
],
),
);
}
Widget _buildAnomalyTile(
{required String title, required String status, required bool isGood}) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
Icon(
isGood
? CupertinoIcons.check_mark_circled_solid
: CupertinoIcons.exclamationmark_triangle_fill,
color: isGood ? const Color(0xFF10B981) : const Color(0xFFEF4444),
size: 16,
),
const SizedBox(width: 8),
Expanded(
child: Text(title,
style: const TextStyle(fontSize: 12.5, color: Colors.white)),
),
Text(
status,
style: TextStyle(
fontSize: 11.5,
fontWeight: FontWeight.w600,
color: isGood ? const Color(0xFF34D399) : const Color(0xFFF87171),
),
),
],
),
);
}
Widget _metricChip(String label, String value) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(6),
),
child: Row(
children: [
Text('$label: ',
style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
Text(value,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: Colors.white)),
],
),
);
}
String _formatDuration(int seconds) {
final m = seconds ~/ 60;
final s = seconds % 60;
return '${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}';
}
}