Update Saqel Platform: 2026-09-12 16:23:39
This commit is contained in:
+237
-20
@@ -223,7 +223,7 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
|
||||
}
|
||||
currentTitle = trimmed.replaceAll(RegExp(r'^#{2,3}\s*'), '');
|
||||
} else if (trimmed.isNotEmpty) {
|
||||
currentLines.add(trimmed);
|
||||
currentLines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,218 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
|
||||
return list;
|
||||
}
|
||||
|
||||
/// Converts LaTeX / Chemical notation into crisp Unicode and clean labels
|
||||
String _cleanFormulas(String text) {
|
||||
var s = text;
|
||||
s = s.replaceAll(r'$$\text{CO}_2$$', 'CO₂');
|
||||
s = s.replaceAll(r'$$\text{H}_2\text{O}$$', 'H₂O');
|
||||
s = s.replaceAll(r'$$\text{O}_3$$', 'O₃');
|
||||
s = s.replaceAll(r'$$\text{O}_2$$', 'O₂');
|
||||
s = s.replaceAll(r'$$\text{N}_2$$', 'N₂');
|
||||
s = s.replaceAll(r'$$\text{CH}_4$$', 'CH₄');
|
||||
s = s.replaceAll(r'$$\text{SO}_2$$', 'SO₂');
|
||||
s = s.replaceAll(r'$$\text{NO}_2$$', 'NO₂');
|
||||
s = s.replaceAll(r'$$\text{CO}$$', 'CO');
|
||||
|
||||
// Generic pattern for $$\text{X}_n$$
|
||||
s = s.replaceAllMapped(RegExp(r'\$\$\\text\{([A-Za-z0-9]+)\}_([0-9]+)\$\$'), (m) {
|
||||
final element = m.group(1) ?? '';
|
||||
final sub = (m.group(2) ?? '').split('').map((d) {
|
||||
const subs = {'0': '₀', '1': '₁', '2': '₂', '3': '₃', '4': '₄', '5': '₅', '6': '₆', '7': '₇', '8': '₈', '9': '₉'};
|
||||
return subs[d] ?? d;
|
||||
}).join('');
|
||||
return '$element$sub';
|
||||
});
|
||||
|
||||
// Strip generic $$ ... $$ or $ ... $ markers cleanly if text
|
||||
s = s.replaceAllMapped(RegExp(r'\$\$([^\$]+)\$\$'), (m) {
|
||||
var formula = m.group(1) ?? '';
|
||||
formula = formula.replaceAll(r'\text{', '').replaceAll('}', '');
|
||||
return formula.trim();
|
||||
});
|
||||
s = s.replaceAllMapped(RegExp(r'\$([^\$]+)\$'), (m) {
|
||||
var formula = m.group(1) ?? '';
|
||||
formula = formula.replaceAll(r'\text{', '').replaceAll('}', '');
|
||||
return formula.trim();
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
/// RichText builder parsing **bold** syntax into styled spans
|
||||
Widget _buildFormattedText(String rawText, {required double fontSize, Color? baseColor}) {
|
||||
final cleaned = _cleanFormulas(rawText);
|
||||
final spans = <TextSpan>[];
|
||||
final regex = RegExp(r'\*\*(.*?)\*\*');
|
||||
int lastMatchEnd = 0;
|
||||
|
||||
for (final match in regex.allMatches(cleaned)) {
|
||||
if (match.start > lastMatchEnd) {
|
||||
spans.add(TextSpan(
|
||||
text: cleaned.substring(lastMatchEnd, match.start),
|
||||
style: TextStyle(
|
||||
color: baseColor ?? Colors.white.withAlpha(225),
|
||||
fontSize: fontSize,
|
||||
height: 1.6,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
final boldText = match.group(1) ?? '';
|
||||
spans.add(TextSpan(
|
||||
text: boldText,
|
||||
style: TextStyle(
|
||||
color: baseColor ?? AppColors.saqelCyan,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: fontSize,
|
||||
height: 1.6,
|
||||
),
|
||||
));
|
||||
|
||||
lastMatchEnd = match.end;
|
||||
}
|
||||
|
||||
if (lastMatchEnd < cleaned.length) {
|
||||
spans.add(TextSpan(
|
||||
text: cleaned.substring(lastMatchEnd),
|
||||
style: TextStyle(
|
||||
color: baseColor ?? Colors.white.withAlpha(225),
|
||||
fontSize: fontSize,
|
||||
height: 1.6,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return RichText(
|
||||
text: TextSpan(children: spans),
|
||||
);
|
||||
}
|
||||
|
||||
/// Renders individual lines with rich Cupertino styling: subheadings, bullets, callouts
|
||||
Widget _buildSectionLine(String line, double fontSize) {
|
||||
final trimmed = line.trim();
|
||||
if (trimmed.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
// 1. Subheading (#### or ###)
|
||||
if (trimmed.startsWith('#### ') || trimmed.startsWith('### ')) {
|
||||
final title = trimmed.replaceAll(RegExp(r'^#{3,4}\s*'), '');
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 14, bottom: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.appleBlue.withAlpha(25),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: AppColors.saqelCyan.withAlpha(50)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.saqelCyan,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_cleanFormulas(title),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: fontSize + 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Blockquote / Callout (> Note or check of understanding)
|
||||
if (trimmed.startsWith('>')) {
|
||||
final quoteText = trimmed.replaceAll(RegExp(r'^>\s*'), '');
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF0F1E36),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.guardianAmber.withAlpha(80)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(CupertinoIcons.lightbulb_fill, color: AppColors.guardianAmber, size: 18),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: _buildFormattedText(quoteText, fontSize: fontSize, baseColor: Colors.white.withAlpha(235)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Sub-bullet (Indented with spaces + bullet)
|
||||
final isIndented = line.startsWith(' ') || line.startsWith('\t');
|
||||
if (isIndented && (trimmed.startsWith('-') || trimmed.startsWith('•') || trimmed.startsWith('*'))) {
|
||||
final itemText = trimmed.replaceFirst(RegExp(r'^[-•\*]\s*'), '');
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 18, top: 4, bottom: 4),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 7),
|
||||
width: 5,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.saqelCyan.withAlpha(160),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _buildFormattedText(itemText, fontSize: fontSize - 0.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Primary Bullet (- or • or *)
|
||||
if (trimmed.startsWith('-') || trimmed.startsWith('•') || trimmed.startsWith('*')) {
|
||||
final itemText = trimmed.replaceFirst(RegExp(r'^[-•\*]\s*'), '');
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 6, top: 6, bottom: 6),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 7),
|
||||
width: 7,
|
||||
height: 7,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.appleBlue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: _buildFormattedText(itemText, fontSize: fontSize),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Standard paragraph line
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: _buildFormattedText(trimmed, fontSize: fontSize),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -618,31 +830,36 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
|
||||
else
|
||||
..._sections.map((section) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 14),
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: LuxuryCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
section.title,
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 14.5),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
...section.lines.map((line) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 6),
|
||||
child: Text(
|
||||
line,
|
||||
style: TextStyle(
|
||||
color: line.startsWith('-') || line.startsWith('•')
|
||||
? AppColors.textSecondaryDark
|
||||
: Colors.white.withAlpha(230),
|
||||
fontSize: _fontSize,
|
||||
height: 1.55,
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 4,
|
||||
height: 18,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.saqelCyan,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_cleanFormulas(section.title),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 15.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...section.lines.map((line) => _buildSectionLine(line, _fontSize)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -135,10 +135,10 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
icon: Icon(CupertinoIcons.money_dollar_circle_fill, size: 18),
|
||||
text: 'حاسبة المواريث والتجويد ⚖️',
|
||||
);
|
||||
} else if (_isHistorySubject || norm == 'تاريخ الأردن') {
|
||||
} else if (_isHistorySubject || norm == 'تاريخ الأردن' || norm == 'التاريخ') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.time_solid, size: 18),
|
||||
text: 'الخط الزمني لتاريخ الأردن 🏛️',
|
||||
text: 'الخط الزمني الشامل والتاريخ 🏛️',
|
||||
);
|
||||
} else if (_isChemistrySubject || norm == 'الكيمياء') {
|
||||
return const Tab(
|
||||
@@ -165,43 +165,59 @@ class _SubjectHubScreenState extends State<SubjectHubScreen>
|
||||
icon: Icon(CupertinoIcons.desktopcomputer, size: 18),
|
||||
text: 'مختبر الخوارزميات والبرمجة 💻',
|
||||
);
|
||||
} else {
|
||||
} else if (norm == 'الجغرافيا') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.map, size: 18),
|
||||
text: 'مختبر الجغرافيا والخرائط 🗺️',
|
||||
);
|
||||
} else if (norm == 'التربية الوطنية والمدنية') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.person_3_fill, size: 18),
|
||||
text: 'مختبر المواطنة والمجتمع 🏛️',
|
||||
);
|
||||
} else if (_isPhysicsSubject || norm == 'الفيزياء') {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.bolt_fill, size: 18),
|
||||
text: 'مختبر الفيزياء والميكانيكا ⚡',
|
||||
);
|
||||
} else {
|
||||
return const Tab(
|
||||
icon: Icon(CupertinoIcons.lab_flask_solid, size: 18),
|
||||
text: 'المختبر الافتراضي التفاعلي 🧪',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget get _dynamicLabWidget {
|
||||
Widget? specializedTool;
|
||||
String? specializedTitle;
|
||||
final norm = Grade10LabsRegistry.normalizeSubject(widget.subject.title);
|
||||
|
||||
if (_isMathSubject) {
|
||||
if (_isMathSubject || norm == 'الرياضيات') {
|
||||
specializedTool = const MathInteractiveLabView();
|
||||
specializedTitle = 'معمل جيوجبرا 📐';
|
||||
} else if (_isEnglishSubject) {
|
||||
} else if (_isEnglishSubject || norm == 'اللغة الإنجليزية') {
|
||||
specializedTool = const EnglishInteractiveLabView();
|
||||
specializedTitle = 'مدرب الصوتيات 🎙️';
|
||||
} else if (_isArabicSubject) {
|
||||
} else if (_isArabicSubject || norm == 'العربية لغتي') {
|
||||
specializedTool = const ArabicInteractiveLabView();
|
||||
specializedTitle = 'شجرة الإعراب 📜';
|
||||
} else if (_isIslamicSubject) {
|
||||
} else if (_isIslamicSubject || norm == 'التربية الإسلامية') {
|
||||
specializedTool = const IslamicInteractiveLabView();
|
||||
specializedTitle = 'حاسبة المواريث ⚖️';
|
||||
} else if (_isHistorySubject) {
|
||||
} else if (_isHistorySubject || norm == 'تاريخ الأردن' || norm == 'التاريخ') {
|
||||
specializedTool = const HistoryInteractiveTimelineView();
|
||||
specializedTitle = 'الخط الزمني الشامل 🏛️';
|
||||
} else if (_isChemistrySubject) {
|
||||
} else if (_isChemistrySubject || norm == 'الكيمياء') {
|
||||
specializedTool = const ChemistryInteractiveLabView();
|
||||
specializedTitle = 'طيف ذرة بور ⚗️';
|
||||
} else if (_isBiologySubject) {
|
||||
} else if (_isBiologySubject || norm == 'العلوم الحياتية') {
|
||||
specializedTool = const BiologyInteractiveLabView();
|
||||
specializedTitle = 'المجهر الرقمي 🔬';
|
||||
} else if (_isEarthSciencesSubject) {
|
||||
} else if (_isEarthSciencesSubject || norm == 'علوم الأرض والبيئة') {
|
||||
specializedTool = const EarthSciencesInteractiveLabView();
|
||||
specializedTitle = 'دورة الصخور والطبقات 🌍';
|
||||
} else if (_isPhysicsSubject) {
|
||||
} else if (_isPhysicsSubject || norm == 'الفيزياء') {
|
||||
specializedTool = const PhysicsInteractiveLabView();
|
||||
specializedTitle = 'محاكاة المتجهات ⚡';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user