Update Saqel Platform: 2026-09-05 22:17:15

This commit is contained in:
Hamza-Ayed
2026-09-05 22:17:15 +03:00
parent 68265a5bc6
commit 0e5be7adbb
6 changed files with 3322 additions and 885 deletions
@@ -49,30 +49,49 @@ class EnglishReadingService {
if (_isInitialized) return;
try {
await _tts.setLanguage('en-US');
await _tts.setSpeechRate(0.44); // Standard natural reading speed for learners
await _tts.setLanguage('en-GB');
await _tts.setSpeechRate(0.40); // British English natural learner cadence
await _tts.setVolume(1.0);
await _tts.setPitch(1.0);
// Attempt to find and select enhanced/natural high-fidelity voice
// Select highest fidelity British English (en-GB / RP) voice
try {
final voices = await _tts.getVoices;
if (voices is List) {
Map? preferredVoice;
Map? fallbackGbVoice;
for (var v in voices) {
if (v is Map) {
final name = (v['name'] ?? '').toString().toLowerCase();
final locale = (v['locale'] ?? '').toString().toLowerCase();
if ((locale.contains('en-us') || locale.contains('en_us')) &&
(name.contains('natural') || name.contains('enhanced') || name.contains('samantha') || name.contains('neural'))) {
await _tts.setVoice({'name': v['name'], 'locale': v['locale']});
AppLogger.log('Selected high-quality natural voice: ${v['name']}', tag: 'ENGLISH_AUDIO');
if (locale.contains('en-gb') || locale.contains('en_gb')) {
fallbackGbVoice ??= v;
if (name.contains('natural') ||
name.contains('enhanced') ||
name.contains('neural') ||
name.contains('premium') ||
name.contains('oliver') ||
name.contains('george') ||
name.contains('serena') ||
name.contains('daniel') ||
name.contains('kate')) {
preferredVoice = v;
break;
}
}
}
}
final selectedVoice = preferredVoice ?? fallbackGbVoice;
if (selectedVoice != null) {
await _tts.setVoice({'name': selectedVoice['name'], 'locale': selectedVoice['locale']});
AppLogger.log('Selected British English (en-GB) voice: ${selectedVoice['name']}', tag: 'ENGLISH_AUDIO');
}
}
} catch (e) {
AppLogger.log('Voice auto-tuning warning: $e', tag: 'ENGLISH_AUDIO');
AppLogger.log('British voice auto-tuning warning: $e', tag: 'ENGLISH_AUDIO');
}
_tts.setStartHandler(() {
@@ -104,7 +123,7 @@ class EnglishReadingService {
}
}
/// قراءة فقرة محددة مع إيقاف أي قراءة جارية
/// قراءة فقرة محددة بالنموذج البريطاني النقي مع إيقاف أي قراءة جارية
Future<void> readParagraph({
required String paragraphId,
required String text,
@@ -134,11 +153,12 @@ class EnglishReadingService {
return;
}
await _tts.setSpeechRate(0.44);
await _tts.setLanguage('en-GB');
await _tts.setSpeechRate(0.40);
await _tts.speak(cleanText);
}
/// نطق كلمة واحدة مفردة بوضوح صوتي نقي
/// نطق كلمة واحدة مفردة بمخارج حروف دقيقة جداً (British RP Articulation)
Future<void> speakWord(String word) async {
await initialize();
if (word.trim().contains(' ')) {
@@ -149,11 +169,12 @@ class EnglishReadingService {
if (cleanWord.isEmpty) return;
await _tts.stop();
await _tts.setSpeechRate(0.38); // Slightly slower for distinct phonetic articulation
await _tts.setLanguage('en-GB');
await _tts.setSpeechRate(0.35); // Slowed down slightly for clear phonetic articulation
await _tts.speak(cleanWord);
}
/// نطق جملة أو عبارة تعليمية بوضوح وصوت طبيعي
/// نطق جملة أو عبارة تعليمية باللكنة البريطانية المعتمدة
Future<void> speakSentence(String text) async {
await initialize();
final cleanText = text
@@ -163,7 +184,8 @@ class EnglishReadingService {
if (cleanText.isEmpty) return;
await _tts.stop();
await _tts.setSpeechRate(0.44);
await _tts.setLanguage('en-GB');
await _tts.setSpeechRate(0.40);
await _tts.speak(cleanText);
}
@@ -532,18 +532,18 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.appleBlue.withAlpha(30),
color: const Color(0xFF1E3A8A).withAlpha(140),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.appleBlue.withAlpha(90)),
border: Border.all(color: const Color(0xFF60A5FA).withAlpha(160)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(CupertinoIcons.speaker_2_fill, color: AppColors.appleBlue, size: 12),
SizedBox(width: 4),
Text('🇬🇧', style: TextStyle(fontSize: 12)),
SizedBox(width: 5),
Text(
'صوتيات ذكية (IPA & TTS)',
style: TextStyle(color: AppColors.appleBlue, fontSize: 11, fontWeight: FontWeight.w700),
'صوت بريطاني فصيح (en-GB RP)',
style: TextStyle(color: Color(0xFF93C5FD), fontSize: 11, fontWeight: FontWeight.w700),
),
],
),
@@ -929,11 +929,90 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
// ============================================================================
// TAB 2: PHONETICS & PRONUNCIATION STUDIO (IPA & TTS)
// ============================================================================
// ============================================================================
// TAB 2: PHONETICS & PRONUNCIATION STUDIO (BRITISH RP & TTS)
// ============================================================================
Widget _buildPhoneticsStudioView() {
final activeUnit = _vocabUnits[_selectedVocabUnitIndex];
final words = activeUnit['words'] as List;
final activeWord = words[_selectedWordIndex] as Map<String, dynamic>;
return LayoutBuilder(
builder: (context, constraints) {
final isCompact = constraints.maxWidth < 760;
if (isCompact) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
// Unit selector dropdown
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.darkCardBorder),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
value: _selectedVocabUnitIndex,
isExpanded: true,
dropdownColor: AppColors.darkSurface,
items: List.generate(_vocabUnits.length, (i) {
return DropdownMenuItem(
value: i,
child: Text(
_vocabUnits[i]['unit'] as String,
style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold),
),
);
}),
onChanged: (val) {
if (val != null) {
setState(() {
_selectedVocabUnitIndex = val;
_selectedWordIndex = 0;
});
}
},
),
),
),
const SizedBox(height: 12),
// Words chips list
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(words.length, (index) {
final item = words[index];
final isSelected = _selectedWordIndex == index;
return Padding(
padding: const EdgeInsets.only(left: 8),
child: ChoiceChip(
label: Text(item['word'] as String),
selected: isSelected,
selectedColor: AppColors.saqelCyan,
labelStyle: TextStyle(
color: isSelected ? Colors.black : Colors.white,
fontWeight: FontWeight.w700,
fontSize: 12,
),
backgroundColor: AppColors.darkSurface,
onSelected: (_) => setState(() => _selectedWordIndex = index),
),
);
}),
),
),
const SizedBox(height: 16),
// Phonetics Card
_buildPhoneticsWorkbenchCard(activeWord),
],
);
}
return Row(
children: [
// Left: Units Selector & Words List
@@ -1026,29 +1105,42 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
// Right: Phonetics Audio Workbench
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(28),
child: Column(
padding: const EdgeInsets.all(24),
child: _buildPhoneticsWorkbenchCard(activeWord),
),
),
],
);
},
);
}
Widget _buildPhoneticsWorkbenchCard(Map<String, dynamic> activeWord) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Big Pronunciation Header Card
Container(
padding: const EdgeInsets.all(28),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF0D1B2A), Color(0xFF1B263B)],
colors: [Color(0xFF0B192C), Color(0xFF1E293B)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: AppColors.saqelCyan.withAlpha(80)),
border: Border.all(color: AppColors.saqelCyan.withAlpha(90)),
boxShadow: const [
BoxShadow(color: Colors.black54, blurRadius: 20, offset: Offset(0, 10)),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(16),
@@ -1058,6 +1150,28 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
style: const TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.bold),
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF1E3A8A).withAlpha(160),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF60A5FA).withAlpha(120)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('🇬🇧', style: TextStyle(fontSize: 12)),
SizedBox(width: 4),
Text(
'British RP',
style: TextStyle(color: Color(0xFF93C5FD), fontSize: 11, fontWeight: FontWeight.w700),
),
],
),
),
],
),
const SizedBox(height: 14),
Text(
activeWord['word'] as String,
@@ -1073,7 +1187,7 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
activeWord['ipa'] as String,
style: const TextStyle(
color: AppColors.saqelCyan,
fontSize: 18,
fontSize: 19,
fontFamily: 'Courier',
fontWeight: FontWeight.w600,
),
@@ -1083,7 +1197,7 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
activeWord['ar'] as String,
style: const TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
const SizedBox(height: 18),
// Audio Play Button
ElevatedButton.icon(
@@ -1092,7 +1206,7 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
size: 20,
),
label: Text(
_isPlayingAudio ? 'جاري النطق...' : 'استمع للنطق الصوتي النقي (Audio Pronunciation)',
_isPlayingAudio ? 'جاري النطق البريطاني...' : 'استمع للنطق البريطاني الفصيح (British Audio Pronunciation)',
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 13),
),
style: ElevatedButton.styleFrom(
@@ -1128,6 +1242,7 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
IconButton(
icon: const Icon(CupertinoIcons.speaker_1, color: AppColors.guardianAmber, size: 18),
onPressed: () => _speakWord(activeWord['sentence'] as String),
tooltip: 'استمع للجملة',
),
],
),
@@ -1140,10 +1255,6 @@ class _EnglishInteractiveLabViewState extends State<EnglishInteractiveLabView>
),
),
],
),
),
),
],
);
}
@@ -431,13 +431,43 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
final activeEx = _textbookExercises[_selectedExerciseIndex];
final points = activeEx['points'] as List<Offset>;
return LayoutBuilder(
builder: (context, constraints) {
final isMobile = constraints.maxWidth < 720;
if (isMobile) {
return SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 330,
child: _buildCanvasContainer(points),
),
_buildControlsPanel(),
],
),
);
}
return Row(
children: [
// Left: Cartesian Graph Canvas
Expanded(
flex: 6,
child: Container(
margin: const EdgeInsets.all(16),
child: _buildCanvasContainer(points),
),
Expanded(
flex: 4,
child: _buildControlsPanel(),
),
],
);
},
);
}
Widget _buildCanvasContainer(List<Offset> points) {
return Container(
margin: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFF070C16),
borderRadius: BorderRadius.circular(20),
@@ -469,8 +499,8 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
// Controls Overlay (Zoom)
Positioned(
top: 14,
left: 14,
top: 10,
left: 10,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
@@ -483,12 +513,12 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
children: [
IconButton(
icon: const Icon(CupertinoIcons.zoom_in, color: Colors.white, size: 18),
onPressed: () => setState(() => _scale = (_scale + 4).clamp(12.0, 45.0)),
onPressed: () => setState(() => _scale = (_scale + 4).clamp(10.0, 50.0)),
tooltip: 'تكبير',
),
IconButton(
icon: const Icon(CupertinoIcons.zoom_out, color: Colors.white, size: 18),
onPressed: () => setState(() => _scale = (_scale - 4).clamp(12.0, 45.0)),
onPressed: () => setState(() => _scale = (_scale - 4).clamp(10.0, 50.0)),
tooltip: 'تصغير',
),
IconButton(
@@ -503,12 +533,12 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
// Curves Legend Badge
Positioned(
bottom: 14,
right: 14,
bottom: 10,
right: 10,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.black.withAlpha(200),
color: Colors.black.withAlpha(210),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white12),
),
@@ -518,41 +548,41 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
children: [
Row(
children: [
Container(width: 12, height: 3, color: AppColors.saqelCyan),
const SizedBox(width: 8),
Container(width: 10, height: 3, color: AppColors.saqelCyan),
const SizedBox(width: 6),
Text(
_curve1Type == 0
? 'الدائرة: x² + y² = ${_radiusSquared.toStringAsFixed(1)}'
: 'القطع المكافئ 1: y = ${_a1.toStringAsFixed(1)}x² + ${_c1.toStringAsFixed(1)}',
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.w600),
style: const TextStyle(color: Colors.white, fontSize: 10.5, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 4),
const SizedBox(height: 3),
Row(
children: [
Container(width: 12, height: 3, color: AppColors.guardianAmber),
const SizedBox(width: 8),
Container(width: 10, height: 3, color: AppColors.guardianAmber),
const SizedBox(width: 6),
Text(
_curve2Type == 0
? 'المستقيم: y = ${_m.toStringAsFixed(1)}x + ${_b.toStringAsFixed(1)}'
: 'القطع المكافئ 2: y = x² + ${_c2.toStringAsFixed(1)}',
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.w600),
style: const TextStyle(color: Colors.white, fontSize: 10.5, fontWeight: FontWeight.w600),
),
],
),
const SizedBox(height: 4),
const SizedBox(height: 3),
Row(
children: [
Container(
width: 8,
height: 8,
width: 7,
height: 7,
decoration: const BoxDecoration(color: Color(0xFFFF2D55), shape: BoxShape.circle),
),
const SizedBox(width: 8),
const SizedBox(width: 6),
Text(
'نقاط التقاطع: ${points.isEmpty ? "لا يوجد حل حقيقي (∅)" : points.map((p) => "(${p.dx.toStringAsFixed(1)}, ${p.dy.toStringAsFixed(1)})").join(" , ")}',
style: const TextStyle(color: Color(0xFFFF2D55), fontSize: 11, fontWeight: FontWeight.w700),
style: const TextStyle(color: Color(0xFFFF2D55), fontSize: 10.5, fontWeight: FontWeight.w700),
),
],
),
@@ -563,21 +593,18 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
],
),
),
),
),
);
}
// Right: Sliders & Controls Panel
Expanded(
flex: 4,
child: Container(
margin: const EdgeInsets.only(top: 16, bottom: 16, left: 16),
padding: const EdgeInsets.all(18),
Widget _buildControlsPanel() {
return Container(
margin: const EdgeInsets.all(12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.darkCardBorder),
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -590,7 +617,7 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
'غيّر المعاملات وشاهد كيف تتحرك المنحنيات وتتغير نقاط التقاطع فورياً',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
const Divider(color: AppColors.darkCardBorder, height: 24),
const Divider(color: AppColors.darkCardBorder, height: 20),
// Curve 1 controls
Text(
@@ -615,7 +642,7 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
),
],
const SizedBox(height: 12),
const SizedBox(height: 8),
// Curve 2 controls
Text(
_curve2Type == 0 ? 'معاملات المستقيم (الميل والمقطع)' : 'معاملات القطع المكافئ (الإزاحة الرأسية)',
@@ -670,7 +697,7 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
),
],
const SizedBox(height: 16),
const SizedBox(height: 12),
// Quick Textbook Presets Buttons
Builder(
builder: (context) {
@@ -713,10 +740,6 @@ class _MathInteractiveLabViewState extends State<MathInteractiveLabView>
),
],
),
),
),
),
],
);
}
@@ -1001,18 +1024,20 @@ class _CartesianCanvasPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
if (size.width <= 0 || size.height <= 0) return;
final cx = size.width / 2;
final cy = size.height / 2;
final safeScale = scale.clamp(6.0, 60.0);
// 1. Draw Grid Lines
final gridPaint = Paint()
..color = const Color(0xFF1B263B)
..strokeWidth = 0.8;
for (double x = cx % scale; x < size.width; x += scale) {
for (double x = cx % safeScale; x < size.width; x += safeScale) {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), gridPaint);
}
for (double y = cy % scale; y < size.height; y += scale) {
for (double y = cy % safeScale; y < size.height; y += safeScale) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), gridPaint);
}
@@ -1032,16 +1057,20 @@ class _CartesianCanvasPainter extends CustomPainter {
if (curve1Type == 0) {
// Circle: x² + y² = r²
final rPixel = math.sqrt(radiusSquared) * scale;
final safeR2 = radiusSquared > 0 ? radiusSquared : 0.0;
final rPixel = math.sqrt(safeR2) * safeScale;
if (rPixel.isFinite && rPixel > 0) {
canvas.drawCircle(Offset(cx, cy), rPixel, curve1Paint);
}
} else {
// Parabola: y = a1*x² + c1
final path = Path();
bool started = false;
for (double px = 0; px <= size.width; px += 2) {
final mathX = (px - cx) / scale;
final mathX = (px - cx) / safeScale;
final mathY = a1 * mathX * mathX + c1;
final py = cy - mathY * scale;
final py = cy - mathY * safeScale;
if (!py.isFinite) continue;
if (!started) {
path.moveTo(px, py);
started = true;
@@ -1049,8 +1078,10 @@ class _CartesianCanvasPainter extends CustomPainter {
path.lineTo(px, py);
}
}
if (started) {
canvas.drawPath(path, curve1Paint);
}
}
// 4. Draw Curve 2 (Amber)
final curve2Paint = Paint()
@@ -1060,23 +1091,26 @@ class _CartesianCanvasPainter extends CustomPainter {
if (curve2Type == 0) {
// Line: y = m*x + b
final x1 = (0 - cx) / scale;
final x1 = (0 - cx) / safeScale;
final y1 = m * x1 + b;
final py1 = cy - y1 * scale;
final py1 = cy - y1 * safeScale;
final x2 = (size.width - cx) / scale;
final x2 = (size.width - cx) / safeScale;
final y2 = m * x2 + b;
final py2 = cy - y2 * scale;
final py2 = cy - y2 * safeScale;
if (py1.isFinite && py2.isFinite) {
canvas.drawLine(Offset(0, py1), Offset(size.width, py2), curve2Paint);
}
} else {
// Parabola: y = x² + c2
final path = Path();
bool started = false;
for (double px = 0; px <= size.width; px += 2) {
final mathX = (px - cx) / scale;
final mathX = (px - cx) / safeScale;
final mathY = mathX * mathX + c2;
final py = cy - mathY * scale;
final py = cy - mathY * safeScale;
if (!py.isFinite) continue;
if (!started) {
path.moveTo(px, py);
started = true;
@@ -1084,8 +1118,10 @@ class _CartesianCanvasPainter extends CustomPainter {
path.lineTo(px, py);
}
}
if (started) {
canvas.drawPath(path, curve2Paint);
}
}
// 5. Draw Intersection Points (Red Glow)
final ptGlow = Paint()
@@ -1097,8 +1133,10 @@ class _CartesianCanvasPainter extends CustomPainter {
..style = PaintingStyle.fill;
for (var pt in intersectionPoints) {
final px = cx + pt.dx * scale;
final py = cy - pt.dy * scale;
if (!pt.dx.isFinite || !pt.dy.isFinite) continue;
final px = cx + pt.dx * safeScale;
final py = cy - pt.dy * safeScale;
if (!px.isFinite || !py.isFinite) continue;
canvas.drawCircle(Offset(px, py), 9.0, ptGlow);
canvas.drawCircle(Offset(px, py), 5.0, ptPaint);
File diff suppressed because it is too large Load Diff
@@ -12,6 +12,7 @@ import '../../../logic/cubits/dashboard_cubits.dart';
import '../../widgets/luxury_widgets.dart';
import '../curriculum/subjects_grid_screen.dart';
import '../notebook/smart_error_notebook_screen.dart';
import '../vocational/vocational_training_screen.dart';
class UnifiedHomeScreen extends StatefulWidget {
final UserModel user;
@@ -319,6 +320,71 @@ class _UnifiedHomeScreenState extends State<UnifiedHomeScreen> {
),
const SizedBox(height: 12),
// Vocational Training Corporation (VTC) & EV Lab Gateway
InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const VocationalTrainingScreen()),
);
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF064E3B), Color(0xFF022C22)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFF10B981).withAlpha(160), width: 1.5),
boxShadow: [
BoxShadow(
color: const Color(0xFF10B981).withAlpha(35),
blurRadius: 18,
offset: const Offset(0, 5),
),
],
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF10B981), Color(0xFF059669)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(CupertinoIcons.wrench_fill, color: Colors.black, size: 24),
),
const SizedBox(width: 14),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'مؤسسة التدريب المهني (VTC Hub) ⚡',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 14.5),
),
SizedBox(height: 4),
Text(
'140 مهنة • 8 قطاعات صناعية • مختبر فحص المركبات الكهربائية (EV)',
style: TextStyle(color: Color(0xFF6EE7B7), fontSize: 11.5, fontWeight: FontWeight.w600),
),
],
),
),
const Icon(CupertinoIcons.chevron_back, color: Color(0xFF10B981), size: 18),
],
),
),
),
const SizedBox(height: 12),
// Smart Error Notebook & Remediation Pathway Gateway
InkWell(
onTap: () {
@@ -0,0 +1,934 @@
import 'dart:math' as math;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../widgets/luxury_widgets.dart';
/// ==============================================================================
/// SAQEL ENTERPRISE (EDTECH 2.0) - VOCATIONAL TRAINING CORPORATION (VTC) HUB
/// ==============================================================================
///
/// بوابة منظومة مؤسسة التدريب المهني (VTC) والتعليم التقني والتدريب المهني:
/// 1. دليل الـ 140 مهنة وحرفة موزعة على 8 قطاعات إنتاجية وصناعية وطنية.
/// 2. تخصص صيانة وتشخيص المركبات الكهربائية والهجينة (EV & Hybrid Specialist).
/// 3. محاكي فحص بطاريات الجهد العالي ونظام العزل (HVIL & BMS 120 FPS Lab).
/// 4. نظام الكفايات المهنية (CBT) ومعايير السلامة المهنية للمركبات الكهربائية (NFPA 70E).
class VocationalTrainingScreen extends StatefulWidget {
const VocationalTrainingScreen({super.key});
@override
State<VocationalTrainingScreen> createState() => _VocationalTrainingScreenState();
}
class _VocationalTrainingScreenState extends State<VocationalTrainingScreen>
with SingleTickerProviderStateMixin {
int _selectedSectorIndex = 0;
int _activeTabIndex = 0; // 0 = 140 Trades Directory, 1 = EV Diagnostic Lab, 2 = CBT Modules
// EV Lab Parameters
double _batterySoc = 82.0; // State of Charge %
double _packVoltage = 384.0; // Volts DC
double _isolationResistance = 5.2; // Mega-Ohms (Threshold > 0.5 MΩ)
bool _hvilInterlockClosed = true; // High Voltage Interlock Loop
bool _contactorPrechargeActive = true;
String _activeDtcCode = 'P0000: No Faults Detected (Normal Operation)';
bool _simulationRunning = true;
late final AnimationController _pulseController;
final List<Map<String, dynamic>> _sectors = [
{
'title': 'المركبات والطاقة المتجددة',
'icon': CupertinoIcons.bolt_car,
'color': Color(0xFF10B981),
'tradesCount': 18,
'featured': 'ميكانيك وتشخيص المركبات الكهربائية والهجينة (EV)',
'description': 'فحص أنظمة الجهد العالي، بطاريات الليثيوم، ومحولات التيار (Inverters).',
'trades': [
'تشخيص وصيانة المركبات الكهربائية والهجينة',
'كهرباء وإلكترونيات السيارات الحديثة',
'ميكانيك محركات الديزل والبنزين المتقدم',
'تركيب وصيانة أنظمة الطاقة الشمسية الكهروضوئية',
'صيانة طواحين الرياح وأنظمة التوليد الهجينة',
'فحص بطاريات الجهد العالي وإعادة تدوير الخلايا',
],
},
{
'title': 'التكنولوجيا والتحول الرقمي',
'icon': CupertinoIcons.device_laptop,
'color': Color(0xFF00F5D4),
'tradesCount': 22,
'featured': 'أمن الشبكات والدعم الفني السحابي',
'description': 'هندسة الشبكات المحلية، الألياف الضوئية، وصيانة الخوادم والأنظمة الذكية.',
'trades': [
'فني صيانة شبكات الحاسوب والألياف الضوئية',
'الدعم الفني لأنظمة الحوسبة السحابية',
'فني صيانة الهواتف الذكية والأجهزة اللوحية',
'تشغيل وبرمجة أنظمة التحكم الرقمي (CNC)',
'فني تركيب كاميرات المراقبة وأنظمة الأمان الذكية',
],
},
{
'title': 'الصناعات الهندسية والميكانيكية',
'icon': CupertinoIcons.wrench_fill,
'color': Color(0xFFF59E0B),
'tradesCount': 28,
'featured': 'اللحام وتشكيل المعادن المتقدم (TIG/MIG)',
'description': 'الخراطة، تشكيل المعادن، واللحام بالأرغون وخطوط الإنتاج المؤتمتة.',
'trades': [
'لحام الأنابيب الصناعية وضغط الغاز (TIG/MIG)',
'الخراطة والتفريز وتشكيل المعادن المبرمج',
'صيانة خطوط الإنتاج والآلات الصناعية',
'التمديدات الكهروميكانيكية للمصانع',
'تشكيل وهيكلة الألمنيوم والواجهات المعمارية',
],
},
{
'title': 'الضيافة والسياحة والفندقة',
'icon': CupertinoIcons.building_2_fill,
'color': Color(0xFFEC4899),
'tradesCount': 16,
'featured': 'فنون الطهي وإنتاج الأغذية الفندقية',
'description': 'إدارة المكاتب الأمامية، فنون الطهي الدولي، وخدمة الغرف والضيافة.',
'trades': [
'إنتاج الأغذية والطهي الشرقي والغربي',
'فنون الحلويات والمخبوزات الفرنسية',
'إدارة المطاعم وخدمة الضيوف الراقية',
'الإشراف الداخلي وإدارة الغرف الفندقية',
],
},
{
'title': 'الزراعة الحديثة والري الذكي',
'icon': CupertinoIcons.leaf_arrow_circlepath,
'color': Color(0xFF22C55E),
'tradesCount': 14,
'featured': 'الزراعة المائية والبيوت البلاستيكية الذكية',
'description': 'أنظمة الزراعة بدون تربة (Hydroponics)، الري المحوسب، وتنسيق الحدائق.',
'trades': [
'تقنيات الزراعة المائية والعمودية المغلقة',
'إدارة وصيانة شبكات الري بالتنقيط المؤتمتة',
'إنتاج النباتات الطبية والعطرية العضوية',
'تنسيق وتصميم الحدائق والمتنزهات الخضراء',
],
},
{
'title': 'الصناعات الدوائية والكيميائية',
'icon': CupertinoIcons.lab_flask_solid,
'color': Color(0xFF8B5CF6),
'tradesCount': 12,
'featured': 'تشغيل خطوط التعبئة والتغليف الدوائي',
'description': 'تطبيق معايير ممارسات التصنيع الجيد (GMP) ومراقبة جودة الإنتاج الكيميائي.',
'trades': [
'فني تشغيل آلات التصنيع الدوائي المعقم',
'مساعد مراقبة الجودة والتحاليل الكيميائية',
'صيانة المضخات والمفاعلات الكيميائية',
],
},
{
'title': 'الحلاقة والتجميل والعناية',
'icon': CupertinoIcons.scissors,
'color': Color(0xFFF43F5E),
'tradesCount': 15,
'featured': 'تصفيف الشعر والتجميل الاحترافي',
'description': 'تقنيات العناية بالبشرة، التصميم المسرحي، وإدارة الصالونات الحديثة.',
'trades': [
'الحلاقة وتصفيف الشعر الرجالي المتقدم',
'التجميل وتصفيف الشعر النسائي',
'العناية بالبشرة والمساج العلاجي',
],
},
{
'title': 'الحرف التقليدية والمشغولات اليدوية',
'icon': CupertinoIcons.cube_box_fill,
'color': Color(0xFFD97706),
'tradesCount': 15,
'featured': 'صياغة الذهب والمجوهرات والفسيفساء',
'description': 'إحياء التراث الحرفي الأردني، فن الفسيفساء المادبي، وحفر الخشب التراثي.',
'trades': [
'صياغة الحلي والمجوهرات وتركيب الأحجار الكريمة',
'فن الفسيفساء والترميم الأثري الحرفي',
'النجارة التراثية والحفر على الخشب والزخرفة',
'الخزف والفخار الفني والتشكيل اليدوي',
],
},
];
@override
void initState() {
super.initState();
_pulseController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
}
@override
void dispose() {
_pulseController.dispose();
super.dispose();
}
void _triggerFault(String faultCode, String description, double rIso, bool hvilState) {
setState(() {
_activeDtcCode = '$faultCode: $description';
_isolationResistance = rIso;
_hvilInterlockClosed = hvilState;
if (!hvilState || rIso < 0.5) {
_contactorPrechargeActive = false;
}
});
}
void _clearFaults() {
setState(() {
_activeDtcCode = 'P0000: No Faults Detected (Normal Operation)';
_isolationResistance = 5.2;
_hvilInterlockClosed = true;
_contactorPrechargeActive = true;
});
}
@override
Widget build(BuildContext context) {
final activeSector = _sectors[_selectedSectorIndex];
return Scaffold(
backgroundColor: AppColors.darkBackground,
appBar: AppBar(
backgroundColor: const Color(0xFF091620),
elevation: 0,
title: Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: const Color(0xFF10B981).withAlpha(30),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFF10B981).withAlpha(100)),
),
child: const Icon(CupertinoIcons.wrench_fill, color: Color(0xFF10B981), size: 18),
),
const SizedBox(width: 10),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'مؤسسة التدريب المهني (VTC Hub)',
style: TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w800),
),
Text(
'140 مهنة وحرفة معتمدة • الشراكة الاستراتيجية للتحول المهني',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
],
),
],
),
),
body: Directionality(
textDirection: TextDirection.rtl,
child: Column(
children: [
// Top Executive Stats Strip
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: const BoxDecoration(
color: Color(0xFF0A1E1D),
border: Border(bottom: BorderSide(color: Color(0xFF10B981), width: 0.8)),
),
child: Row(
children: [
_buildExecutiveStat('140 مهنة', 'دليل المهن الوزاري', CupertinoIcons.square_grid_2x2_fill),
_buildVerticalDivider(),
_buildExecutiveStat('8 قطاعات', 'سلاسل القيمة الصناعية', CupertinoIcons.layers_fill),
_buildVerticalDivider(),
_buildExecutiveStat('EV 384V', 'مختبر فحص الهايبرد والكهرباء', CupertinoIcons.bolt_car_fill),
_buildVerticalDivider(),
_buildExecutiveStat('CBT Level 3', 'الكفايات والسلامة الدولية', CupertinoIcons.checkmark_seal_fill),
],
),
),
// Navigation Tabs (Directory / EV Lab / CBT)
Container(
color: AppColors.darkSurface,
child: Row(
children: [
_buildTopTabItem(0, 'دليل الـ 140 مهنة (8 قطاعات) 📋', CupertinoIcons.list_bullet),
_buildTopTabItem(1, 'مختبر فحص المركبات الكهربائية ⚡', CupertinoIcons.bolt_fill),
_buildTopTabItem(2, 'شهادات الكفايات المهنية (CBT) 🎓', CupertinoIcons.rosette),
],
),
),
// Tab View Body
Expanded(
child: IndexedStack(
index: _activeTabIndex,
children: [
_buildTradesDirectoryTab(activeSector),
_buildEvDiagnosticLabTab(),
_buildCbtCompetenciesTab(),
],
),
),
],
),
),
);
}
Widget _buildTopTabItem(int index, String label, IconData icon) {
final isSelected = _activeTabIndex == index;
return Expanded(
child: GestureDetector(
onTap: () => setState(() => _activeTabIndex = index),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: isSelected ? const Color(0xFF10B981) : Colors.transparent,
width: 3,
),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 16, color: isSelected ? const Color(0xFF10B981) : AppColors.textSecondaryDark),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
color: isSelected ? Colors.white : AppColors.textSecondaryDark,
fontSize: 11.5,
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
);
}
Widget _buildExecutiveStat(String title, String subtitle, IconData icon) {
return Expanded(
child: Row(
children: [
Icon(icon, color: const Color(0xFF10B981), size: 18),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 12.5)),
Text(subtitle, style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 9.5), maxLines: 1, overflow: TextOverflow.ellipsis),
],
),
),
],
),
);
}
Widget _buildVerticalDivider() {
return Container(
width: 1,
height: 24,
margin: const EdgeInsets.symmetric(horizontal: 8),
color: Colors.white12,
);
}
// ===========================================================================
// TAB 1: 140 VOCATIONAL TRADES DIRECTORY & 8 SECTORS
// ===========================================================================
Widget _buildTradesDirectoryTab(Map<String, dynamic> activeSector) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
// Sector Pill Chips Row
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _sectors.asMap().entries.map((entry) {
final idx = entry.key;
final sec = entry.value;
final isSelected = _selectedSectorIndex == idx;
final secColor = sec['color'] as Color;
return GestureDetector(
onTap: () => setState(() => _selectedSectorIndex = idx),
child: Container(
margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? secColor.withAlpha(40) : AppColors.darkSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: isSelected ? secColor : AppColors.darkCardBorder,
width: 1.5,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(sec['icon'] as IconData, size: 15, color: isSelected ? secColor : Colors.white70),
const SizedBox(width: 6),
Text(
sec['title'] as String,
style: TextStyle(
color: isSelected ? Colors.white : Colors.white70,
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500,
fontSize: 12,
),
),
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: secColor.withAlpha(60),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${sec['tradesCount']}',
style: TextStyle(color: secColor, fontSize: 10, fontWeight: FontWeight.w900),
),
),
],
),
),
);
}).toList(),
),
),
const SizedBox(height: 16),
// Featured Specialization Spotlight Card
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
(activeSector['color'] as Color).withAlpha(40),
const Color(0xFF0F1A28),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: (activeSector['color'] as Color).withAlpha(120), width: 1.5),
boxShadow: const [
BoxShadow(color: Colors.black45, blurRadius: 16, offset: Offset(0, 6)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: (activeSector['color'] as Color).withAlpha(40),
borderRadius: BorderRadius.circular(12),
),
child: Icon(activeSector['icon'] as IconData, color: activeSector['color'] as Color, size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activeSector['title'] as String,
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w800),
),
const SizedBox(height: 2),
Text(
'التخصص الاستراتيجي: ${activeSector['featured']}',
style: TextStyle(color: activeSector['color'] as Color, fontSize: 12, fontWeight: FontWeight.w700),
),
],
),
),
],
),
const SizedBox(height: 12),
Text(
activeSector['description'] as String,
style: const TextStyle(color: Colors.white70, fontSize: 12.5, height: 1.5),
),
],
),
),
const SizedBox(height: 16),
// Trades List
const Text(
'المهن والحرف المعتمدة في هذا القطاع:',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5),
),
const SizedBox(height: 10),
...((activeSector['trades'] as List<String>).asMap().entries.map((tEntry) {
final idx = tEntry.key + 1;
final tradeTitle = tEntry.value;
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.darkCardBorder),
),
child: Row(
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: Colors.white.withAlpha(10),
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
'$idx',
style: const TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.bold),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
tradeTitle,
style: const TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.w600),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: const Color(0xFF10B981).withAlpha(20),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFF10B981).withAlpha(80)),
),
child: const Text('دبلوم مهني VTC', style: TextStyle(color: Color(0xFF10B981), fontSize: 10, fontWeight: FontWeight.bold)),
),
],
),
);
}).toList()),
],
);
}
// ===========================================================================
// TAB 2: INTERACTIVE EV & HYBRID DIAGNOSTIC VIRTUAL LAB (120 FPS)
// ===========================================================================
Widget _buildEvDiagnosticLabTab() {
return ListView(
padding: const EdgeInsets.all(16),
children: [
// Live Diagnostics Canvas (Battery Pack & Inverter)
Container(
height: 280,
decoration: BoxDecoration(
color: const Color(0xFF070E14),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: _activeDtcCode.startsWith('P0000') ? const Color(0xFF10B981).withAlpha(80) : const Color(0xFFFF2D55),
width: 1.5,
),
boxShadow: const [
BoxShadow(color: Colors.black54, blurRadius: 16, offset: Offset(0, 6)),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(19),
child: CustomPaint(
painter: _EvDiagnosticsPainter(
batterySoc: _batterySoc,
packVoltage: _packVoltage,
isolationResistance: _isolationResistance,
hvilClosed: _hvilInterlockClosed,
isFaulted: !_activeDtcCode.startsWith('P0000'),
),
),
),
),
const SizedBox(height: 14),
// Live Diagnostic Telemetry Grid
Row(
children: [
_buildTelemetryCard(
'حالة الشحن (SoC)',
'${_batterySoc.toInt()}%',
const Color(0xFF10B981),
CupertinoIcons.battery_75_percent,
),
const SizedBox(width: 8),
_buildTelemetryCard(
'جهد البطارية (Pack V)',
'${_packVoltage.toInt()} V',
AppColors.saqelCyan,
CupertinoIcons.bolt_fill,
),
const SizedBox(width: 8),
_buildTelemetryCard(
'مقاومة العزل (R_iso)',
'${_isolationResistance.toStringAsFixed(1)} MΩ',
_isolationResistance >= 0.5 ? const Color(0xFF10B981) : const Color(0xFFFF2D55),
CupertinoIcons.shield_lefthalf_fill,
),
const SizedBox(width: 8),
_buildTelemetryCard(
'حلقة القفل (HVIL)',
_hvilInterlockClosed ? 'مغلقة (آمن)' : 'مفتوحة (خطر)',
_hvilInterlockClosed ? const Color(0xFF10B981) : const Color(0xFFFF2D55),
CupertinoIcons.lock_shield_fill,
),
],
),
const SizedBox(height: 14),
// Active DTC Code Box
Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: _activeDtcCode.startsWith('P0000') ? const Color(0xFF0F261E) : const Color(0xFF330E14),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: _activeDtcCode.startsWith('P0000') ? const Color(0xFF10B981) : const Color(0xFFFF2D55),
),
),
child: Row(
children: [
Icon(
_activeDtcCode.startsWith('P0000') ? CupertinoIcons.check_mark_circled_solid : CupertinoIcons.exclamationmark_triangle_fill,
color: _activeDtcCode.startsWith('P0000') ? const Color(0xFF10B981) : const Color(0xFFFF2D55),
size: 22,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('كود التشخيص والأعطال (OBD-II DTC Readout):', style: TextStyle(color: Colors.white70, fontSize: 11)),
const SizedBox(height: 2),
Text(
_activeDtcCode,
style: TextStyle(
color: _activeDtcCode.startsWith('P0000') ? const Color(0xFF10B981) : const Color(0xFFFF2D55),
fontWeight: FontWeight.w800,
fontSize: 13,
),
),
],
),
),
],
),
),
const SizedBox(height: 14),
// Simulated Fault Injection Controls
LuxuryCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('حقن الأعطال المخبرية (Fault Injection Simulation):', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13)),
const SizedBox(height: 4),
const Text('اختر عطلاً كهربائياً لاختبار استجابة نظام إدارة البطارية (BMS) وفصل قواطع الجهد العالي:', style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11)),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
OutlinedButton.icon(
icon: const Icon(CupertinoIcons.shield_slash, color: Color(0xFFFF2D55), size: 16),
label: const Text('عطل تسريب العزل (P0AA6 - Loss of Isolation)', style: TextStyle(color: Colors.white, fontSize: 11.5)),
style: OutlinedButton.styleFrom(side: const BorderSide(color: Color(0xFFFF2D55))),
onPressed: () => _triggerFault('P0AA6', 'Hybrid Battery Voltage Isolation Fault (Leakage < 0.5 MΩ)', 0.1, true),
),
OutlinedButton.icon(
icon: const Icon(CupertinoIcons.lock_slash_fill, color: Color(0xFFF59E0B), size: 16),
label: const Text('قطع حلقة الأمان (P0A0D - HVIL Open Circuit)', style: TextStyle(color: Colors.white, fontSize: 11.5)),
style: OutlinedButton.styleFrom(side: const BorderSide(color: Color(0xFFF59E0B))),
onPressed: () => _triggerFault('P0A0D', 'High Voltage System Interlock Circuit Open (HVIL Broken)', 5.2, false),
),
ElevatedButton.icon(
icon: const Icon(CupertinoIcons.arrow_counterclockwise, size: 16),
label: const Text('إعادة ضبط وتشغيل النظام السليم (Clear DTC)', style: TextStyle(fontSize: 11.5, fontWeight: FontWeight.bold)),
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF10B981), foregroundColor: Colors.black),
onPressed: _clearFaults,
),
],
),
],
),
),
],
);
}
Widget _buildTelemetryCard(String label, String value, Color color, IconData icon) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.darkCardBorder),
),
child: Column(
children: [
Icon(icon, color: color, size: 18),
const SizedBox(height: 4),
Text(value, style: TextStyle(color: color, fontWeight: FontWeight.w800, fontSize: 13.5)),
const SizedBox(height: 2),
Text(label, style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 9.5), textAlign: TextAlign.center, maxLines: 1, overflow: TextOverflow.ellipsis),
],
),
),
);
}
// ===========================================================================
// TAB 3: CBT COMPETENCIES & CERTIFICATION STANDARDS
// ===========================================================================
Widget _buildCbtCompetenciesTab() {
return ListView(
padding: const EdgeInsets.all(16),
children: [
// Level 3 Certification Banner
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF132A1C), Color(0xFF0F1A28)],
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: const Color(0xFF10B981), width: 1.5),
),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(CupertinoIcons.rosette, color: Color(0xFF10B981), size: 24),
SizedBox(width: 10),
Text(
'نظام التدريب المبني على الكفايات (CBT Level 3)',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 15),
),
],
),
SizedBox(height: 8),
Text(
'برنامج معتمد دولياً لتأهيل فنيي المركبات الكهربائية والهجينة وفق معايير السلامة المهنية العالمية (NFPA 70E و ISO 26262 و ECE-R100). يشترط إتقان 5 كفايات إلزامية لنيل رخصة الفحص الميداني.',
style: TextStyle(color: Colors.white70, fontSize: 12.5, height: 1.5),
),
],
),
),
const SizedBox(height: 16),
_buildCbtModuleCard(
'الكفاية 1: إجراءات السلامة وفصل الجهد العالي (HV De-energization)',
'التحقق من خلو الجهد الكهربائي (Zero Voltage Verification) واستخدام قفازات الفئة Class 0 (1000V).',
'95% إتقان مطلوب',
CupertinoIcons.shield_fill,
const Color(0xFF10B981),
),
_buildCbtModuleCard(
'الكفاية 2: تشخيص خلايا بطارية الجهد العالي (BMS Cell Balancing)',
'قراءة قيم الجهد لكل خلية (3.2V - 4.2V) وفحص مقاومة العزل وتوازن درجات الحرارة.',
'معتمد دولياً',
CupertinoIcons.battery_full,
AppColors.saqelCyan,
),
_buildCbtModuleCard(
'الكفاية 3: فحص محول التيار والمحرك الكهربائي (Inverter & Motor)',
'فحص إشارات التردد العالي (PWM Gate Signals) وقياس مقاومة أطوار المحرك الثلاثية (U-V-W).',
'مستوى متقدم',
CupertinoIcons.bolt_horizontal_fill,
const Color(0xFFF59E0B),
),
_buildCbtModuleCard(
'الكفاية 4: بروتوكول حلقة الأمان وقفل الفحص (HVIL Loop Diagnostics)',
'كشف انقطاع أسلاك التوصيل السريع وإشارات تأكيد إغلاق غطاء مقبس الخدمة (Service Plug).',
'إلزامي للفحص',
CupertinoIcons.lock_shield,
const Color(0xFF8B5CF6),
),
],
);
}
Widget _buildCbtModuleCard(String title, String desc, String badge, IconData icon, Color color) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.darkSurface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.darkCardBorder),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withAlpha(30),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: color.withAlpha(20),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withAlpha(80)),
),
child: Text(badge, style: TextStyle(color: color, fontSize: 10.5, fontWeight: FontWeight.bold)),
),
],
),
const SizedBox(height: 6),
Text(desc, style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12, height: 1.4)),
],
),
),
],
),
);
}
}
// =============================================================================
// EV DIAGNOSTICS CUSTOM PAINTER (120 FPS HIGH-VOLTAGE ARCHITECTURE)
// =============================================================================
class _EvDiagnosticsPainter extends CustomPainter {
final double batterySoc;
final double packVoltage;
final double isolationResistance;
final bool hvilClosed;
final bool isFaulted;
_EvDiagnosticsPainter({
required this.batterySoc,
required this.packVoltage,
required this.isolationResistance,
required this.hvilClosed,
required this.isFaulted,
});
@override
void paint(Canvas canvas, Size size) {
final w = size.width;
final h = size.height;
final cy = h / 2;
// Watermark
const watermarkSpan = TextSpan(
text: 'منصة صَقِل • مختبر فحص المركبات الكهربائية VTC EV Lab',
style: TextStyle(color: Color(0x3300F5D4), fontSize: 11, fontWeight: FontWeight.bold),
);
final watermarkPainter = TextPainter(text: watermarkSpan, textDirection: TextDirection.rtl)..layout();
watermarkPainter.paint(canvas, const Offset(12, 12));
// 1. High Voltage Battery Pack Silhouette
final bRect = RRect.fromRectAndRadius(
Rect.fromLTWH(24, cy - 65, w * 0.40, 130),
const Radius.circular(16),
);
canvas.drawRRect(bRect, Paint()..color = const Color(0xFF0F1E29));
canvas.drawRRect(bRect, Paint()..color = const Color(0xFF10B981)..style = PaintingStyle.stroke..strokeWidth = 2);
// Battery Pack label
const bSpan = TextSpan(
text: 'بطارية الجهد العالي (384V DC)\nLi-Ion NMC Battery Pack',
style: TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold),
);
final btp = TextPainter(text: bSpan, textDirection: TextDirection.rtl)..layout();
btp.paint(canvas, Offset(36, cy - 45));
// SoC Bar inside pack
final socW = (w * 0.40 - 24) * (batterySoc / 100.0);
canvas.drawRRect(
RRect.fromRectAndRadius(Rect.fromLTWH(36, cy + 20, socW, 16), const Radius.circular(6)),
Paint()..color = const Color(0xFF10B981),
);
// 2. Inverter & Motor Unit
final invRect = RRect.fromRectAndRadius(
Rect.fromLTWH(w * 0.60, cy - 65, w * 0.34, 130),
const Radius.circular(16),
);
canvas.drawRRect(invRect, Paint()..color = const Color(0xFF1E1A2E));
canvas.drawRRect(
invRect,
Paint()
..color = isFaulted ? const Color(0xFFFF2D55) : const Color(0xFF8B5CF6)
..style = PaintingStyle.stroke
..strokeWidth = 2,
);
final invSpan = TextSpan(
text: 'محول القدرة والمحرك (Inverter)\nAC 3-Phase Permanent Magnet',
style: TextStyle(color: isFaulted ? const Color(0xFFFF2D55) : Colors.white, fontSize: 11, fontWeight: FontWeight.bold),
);
final itp = TextPainter(text: invSpan, textDirection: TextDirection.rtl)..layout();
itp.paint(canvas, Offset(w * 0.60 + 12, cy - 45));
// 3. High-Voltage Bus Cables (Orange Cables +HV and -HV)
final hvCableColor = isFaulted ? const Color(0xFFFF2D55) : const Color(0xFFFF7A00); // Standard Orange EV Cable
final cablePaint = Paint()..color = hvCableColor..strokeWidth = 4..strokeCap = StrokeCap.round;
// +HV Cable (Top)
canvas.drawLine(Offset(24 + w * 0.40, cy - 20), Offset(w * 0.60, cy - 20), cablePaint);
// -HV Cable (Bottom)
canvas.drawLine(Offset(24 + w * 0.40, cy + 20), Offset(w * 0.60, cy + 20), cablePaint);
// Cable Voltage Badge
final voltSpan = TextSpan(
text: isFaulted ? '⚠️ جهد معزول تلقائياً' : '+384V DC Bus',
style: TextStyle(color: hvCableColor, fontSize: 10, fontWeight: FontWeight.w800),
);
final vtp = TextPainter(text: voltSpan, textDirection: TextDirection.ltr)..layout();
vtp.paint(canvas, Offset(w * 0.46, cy - 36));
// 4. HVIL Interlock Loop Indicator
final hvilColor = hvilClosed ? const Color(0xFF10B981) : const Color(0xFFFF2D55);
final hvilPaint = Paint()..color = hvilColor..strokeWidth = 2..style = PaintingStyle.stroke;
canvas.drawCircle(Offset(w * 0.50, cy + 45), 8, hvilPaint);
canvas.drawCircle(Offset(w * 0.50, cy + 45), 4, Paint()..color = hvilColor);
}
@override
bool shouldRepaint(covariant _EvDiagnosticsPainter oldDelegate) => true;
}