fix(platform): grade-gate student app, isolate per-video resume & checkpoints, remove fake teachers, enhance arabic lab, clean flutter analyze

This commit is contained in:
Hamza-Ayed
2026-09-04 23:41:32 +03:00
parent ae00aa943e
commit e7bc20bc98
22 changed files with 291 additions and 316 deletions
@@ -1,5 +1,3 @@
import 'dart:convert';
/// Directorate Model (المظلة المركزية: مديرية الثقافة العسكرية) /// Directorate Model (المظلة المركزية: مديرية الثقافة العسكرية)
class DirectorateOverview { class DirectorateOverview {
final String code; final String code;
@@ -105,7 +105,7 @@ class _SchoolPrincipalScreenState extends State<SchoolPrincipalScreen>
} }
void _showDualFormsPreview() async { void _showDualFormsPreview() async {
final data = await DirectorateApiService.fetchDualForms(); await DirectorateApiService.fetchDualForms();
if (!mounted) return; if (!mounted) return;
showModalBottomSheet( showModalBottomSheet(
+6 -6
View File
@@ -316,10 +316,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.18" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -332,10 +332,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@@ -561,10 +561,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.9" version: "0.7.11"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -84,7 +84,7 @@ class CurriculumBakedData {
#### البند 1: حل المعادلات بإخراج العامل المشترك الأكبر #### البند 1: حل المعادلات بإخراج العامل المشترك الأكبر
- **قاعدة:** نجعل طرف المعادلة الأيمن صفراً، ثم نخرج العامل المشترك، ونحل المعادلة الناتجة. - **قاعدة:** نجعل طرف المعادلة الأيمن صفراً، ثم نخرج العامل المشترك، ونحل المعادلة الناتجة.
- **مثال 1:** حل المعادلة: \$x^3 + 4x^2 = 5x\$ - **مثال 1:** حل المعادلة: \$x^3 + 4x^2 = 5x\$
$$\$x^3 + 4x^2 - 5x = 0 \\implies x(x^2 + 4x - 5) = 0 \\implies x(x + 5)(x - 1) = 0\$$ \$\$x^3 + 4x^2 - 5x = 0 \\implies x(x^2 + 4x - 5) = 0 \\implies x(x + 5)(x - 1) = 0\$\$
إذن جذور المعادلة هي: \$\\{-5, 0, 1\\}\$. إذن جذور المعادلة هي: \$\\{-5, 0, 1\\}\$.
- **أتحقق من فهمي (صفحة 9):** - **أتحقق من فهمي (صفحة 9):**
@@ -97,10 +97,10 @@ class CurriculumBakedData {
- **المفهوم:** نقطة تقاطع المستقيم والمنحنى تمثل حل النظام المشترك. - **المفهوم:** نقطة تقاطع المستقيم والمنحنى تمثل حل النظام المشترك.
- **طريقة التعويض:** نجعل أحد المتغيرين موضوعاً للقانون في المعادلة الخطية ونعوضه في المعادلة التربيعية. - **طريقة التعويض:** نجعل أحد المتغيرين موضوعاً للقانون في المعادلة الخطية ونعوضه في المعادلة التربيعية.
- **مثال محلول:** - **مثال محلول:**
$$\\begin{cases} y - x = 1 \\\\ x^2 + y^2 = 25 \\end{cases}$$ \$\$\\begin{cases} y - x = 1 \\\\ x^2 + y^2 = 25 \\end{cases}\$\$
من المعادلة الأولى: \$y = x + 1\$. نعوض في الثانية: من المعادلة الأولى: \$y = x + 1\$. نعوض في الثانية:
$$\$x^2 + (x + 1)^2 = 25 \\implies 2x^2 + 2x - 24 = 0 \\implies x^2 + x - 12 = 0\$$ \$\$x^2 + (x + 1)^2 = 25 \\implies 2x^2 + 2x - 24 = 0 \\implies x^2 + x - 12 = 0\$\$
$$\$(x + 4)(x - 3) = 0 \\implies x = 3 \\text{ أو } x = -4\$$ \$\$(x + 4)(x - 3) = 0 \\implies x = 3 \\text{ أو } x = -4\$\$
- عندما \$x = 3 \\implies y = 4\$ (النقطة: \$(3, 4)\$) - عندما \$x = 3 \\implies y = 4\$ (النقطة: \$(3, 4)\$)
- عندما \$x = -4 \\implies y = -3\$ (النقطة: \$( -4, -3)\$) - عندما \$x = -4 \\implies y = -3\$ (النقطة: \$( -4, -3)\$)
@@ -144,11 +144,11 @@ class CurriculumBakedData {
--- ---
### السؤال الثاني: حل النظام الجبري التالي وتحقق من صحة الحل (6 علامات) ### السؤال الثاني: حل النظام الجبري التالي وتحقق من صحة الحل (6 علامات)
$$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}$$ \$\$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}\$\$
**خطوات الإجابة:** **خطوات الإجابة:**
- نعوض \$y = 2x + 3\$ في المعادلة الثانية: - نعوض \$y = 2x + 3\$ في المعادلة الثانية:
$$\$x^2 + 2x + 3 = 6 \\implies x^2 + 2x - 3 = 0\$$ \$\$x^2 + 2x + 3 = 6 \\implies x^2 + 2x - 3 = 0\$\$
- التحليل: \$(x + 3)(x - 1) = 0\$. - التحليل: \$(x + 3)(x - 1) = 0\$.
- إذا \$x = 1 \\implies y = 2(1) + 3 = 5\$ ➔ الحل الأول: \$(1, 5)\$. - إذا \$x = 1 \\implies y = 2(1) + 3 = 5\$ ➔ الحل الأول: \$(1, 5)\$.
- إذا \$x = -3 \\implies y = 2(-3) + 3 = -3\$ ➔ الحل الثاني: \$( -3, -3)\$. - إذا \$x = -3 \\implies y = 2(-3) + 3 = -3\$ ➔ الحل الثاني: \$( -3, -3)\$.
@@ -193,12 +193,12 @@ $$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}$$
### ضرب المتجهات (Vector Multiplication): ### ضرب المتجهات (Vector Multiplication):
1. **الضرب القياسي / النقطي (Dot Product):** 1. **الضرب القياسي / النقطي (Dot Product):**
$$\\vec{A} \\cdot \\vec{B} = |A| |B| \\cos\\phi$$ \$\$\\vec{A} \\cdot \\vec{B} = |A| |B| \\cos\\phi\$\$
- الناتج كمية قياسية (مثل حساب الشغل: \$W = \\vec{F} \\cdot \\vec{d}\$). - الناتج كمية قياسية (مثل حساب الشغل: \$W = \\vec{F} \\cdot \\vec{d}\$).
- ينعدم الضرب القياسي إذا كان المتجهان متعامدين (\$\\phi = 90^\\circ\$ لأن \$\\cos 90^\\circ = 0\$). - ينعدم الضرب القياسي إذا كان المتجهان متعامدين (\$\\phi = 90^\\circ\$ لأن \$\\cos 90^\\circ = 0\$).
2. **الضرب المتجهي / التقاطعي (Cross Product):** 2. **الضرب المتجهي / التقاطعي (Cross Product):**
$$|\\vec{A} \\times \\vec{B}| = |A| |B| \\sin\\phi$$ \$\$|\\vec{A} \\times \\vec{B}| = |A| |B| \\sin\\phi\$\$
- الناتج متجه عمودي على المستوى الذي يحوي المتجهين، ويُحدد اتجاهه باستخدام قاعدة اليد اليمنى. - الناتج متجه عمودي على المستوى الذي يحوي المتجهين، ويُحدد اتجاهه باستخدام قاعدة اليد اليمنى.
"""; """;
@@ -243,7 +243,7 @@ $$\\begin{cases} y = 2x + 3 \\\\ x^2 + y = 6 \\end{cases}$$
2. تعليق كتلة \$m_2 = 200\\text{ g}\$ عند زاوية \$90^\\circ\$. 2. تعليق كتلة \$m_2 = 200\\text{ g}\$ عند زاوية \$90^\\circ\$.
3. اتزان الحلقة في المنتصف بوضع قوة موازنة ثالثة في الربع المقابل بمقدار \$250\\text{ g}\$ وزاوية \$233^\\circ\$. 3. اتزان الحلقة في المنتصف بوضع قوة موازنة ثالثة في الربع المقابل بمقدار \$250\\text{ g}\$ وزاوية \$233^\\circ\$.
4. **الاستنتاج:** محصلة قوتين متعامدتين تُحسب بفيثاغورس: 4. **الاستنتاج:** محصلة قوتين متعامدتين تُحسب بفيثاغورس:
$$R = \\sqrt{F_1^2 + F_2^2} = \\sqrt{150^2 + 200^2} = 250\\text{ gf}$$ \$\$R = \\sqrt{F_1^2 + F_2^2} = \\sqrt{150^2 + 200^2} = 250\\text{ gf}\$\$
"""; """;
// ============================================================================ // ============================================================================
@@ -224,20 +224,36 @@ class CurriculumLessonItemModel {
} }
final filePath = json['file']?.toString(); final filePath = json['file']?.toString();
// Default video availability: true for core Grade 10 subjects (Math, Physics, English) and explicitly tagged lessons final jsonId = json['id']?.toString();
final bool isCoreTenthSubjectLesson = filePath != null && (
filePath.contains('math_10/semester_1') || // Stable, deterministic lesson ID (vital for persistent per-lesson progress tracking)
filePath.contains('physics_10/semester_1') || final stableId = (jsonId != null && jsonId.isNotEmpty)
filePath.contains('english_10/semester_1') || ? jsonId
filePath.contains('lesson_01') || : (filePath != null && filePath.isNotEmpty
filePath.contains('lesson_1a') ? filePath.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_')
: 'lesson_${(json['title']?.toString() ?? 'default').hashCode.abs()}');
// Video availability:
// 1. Explicitly false for introductory/project overviews
// 2. Physics & other subjects currently have no video -> false
// 3. Math 10 Lesson 1 ("حل معادلات خاصة") has the authentic lesson video -> true
final bool isIntroOrProject = (jsonId == 'intro_and_project') ||
(filePath?.contains('intro_and_project') ?? false) ||
((json['title']?.toString() ?? '').contains('مقدمة ومشروع'));
final bool isMath10Lesson1 = !isIntroOrProject && (
(filePath != null && filePath.contains('math_10/semester_1/unit_01/lesson_01')) ||
((json['title']?.toString() ?? '').contains('معادلاتٍ خاصّةٍ'))
); );
final bool hasVideoExplicit = (json['has_video'] as bool?) ?? final bool hasVideoExplicit = !isIntroOrProject && (
((json['video_url'] != null && json['video_url'].toString().isNotEmpty) || isCoreTenthSubjectLesson); (json['has_video'] == true) ||
(json['video_url'] != null && json['video_url'].toString().isNotEmpty) ||
isMath10Lesson1
);
return CurriculumLessonItemModel( return CurriculumLessonItemModel(
id: json['id']?.toString() ?? 'lesson_${DateTime.now().millisecondsSinceEpoch}', id: stableId,
title: json['title']?.toString() ?? 'درس بدون عنوان', title: json['title']?.toString() ?? 'درس بدون عنوان',
outcomes: outs, outcomes: outs,
markdownFilePath: filePath, markdownFilePath: filePath,
@@ -76,19 +76,34 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
: _repo = repo ?? CurriculumRepository(), : _repo = repo ?? CurriculumRepository(),
super(VideoPlaybackInitial()); super(VideoPlaybackInitial());
String _getLessonStorageKey(CurriculumLessonItemModel? lesson, {String? fallbackId}) {
if (lesson?.markdownFilePath != null && lesson!.markdownFilePath!.isNotEmpty) {
return lesson.markdownFilePath!.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
}
if (lesson?.id != null && lesson!.id.isNotEmpty) {
return lesson.id.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
}
return fallbackId ?? 'lesson_default';
}
Future<void> loadLesson(CurriculumLessonItemModel lesson, {SubjectModel? subject}) async { Future<void> loadLesson(CurriculumLessonItemModel lesson, {SubjectModel? subject}) async {
AppLogger.log('Loading Socratic playback for lesson: ${lesson.title}', tag: 'VIDEO_CUBIT'); AppLogger.log('Loading Socratic playback for lesson: ${lesson.title}', tag: 'VIDEO_CUBIT');
_lastObservedPosition = -1; _lastObservedPosition = -1;
emit(VideoPlaybackLoading()); emit(VideoPlaybackLoading());
final storageKey = _getLessonStorageKey(lesson);
try { try {
final playback = await _repo.getLessonPlayback(lesson.markdownFilePath ?? lesson.id, subjectId: subject?.id); final playback = await _repo.getLessonPlayback(lesson.markdownFilePath ?? lesson.id, subjectId: subject?.id);
// Load saved resume position // Load saved resume position strictly per video
int resumePos = playback.lastPositionSeconds ?? 0; int resumePos = playback.lastPositionSeconds ?? 0;
Set<int> passedIds = {};
try { try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final localPos = prefs.getInt('saved_video_pos_${lesson.id}') ?? 0; final localPos = prefs.getInt('saved_video_pos_$storageKey') ?? 0;
if (localPos > resumePos) resumePos = localPos; if (localPos > resumePos) resumePos = localPos;
final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? [];
passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet();
} catch (_) {} } catch (_) {}
emit(VideoPlaybackReady( emit(VideoPlaybackReady(
@@ -96,18 +111,22 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
lessonItem: lesson, lessonItem: lesson,
subject: subject, subject: subject,
currentPositionSeconds: resumePos, currentPositionSeconds: resumePos,
passedCheckpointIds: passedIds,
isPlaying: true, isPlaying: true,
)); ));
} catch (e) { } catch (e) {
AppLogger.log('Playback API unavailable (${lesson.title}): $e — Switching to offline Socratic fallback', tag: 'VIDEO_CUBIT'); AppLogger.log('Playback API unavailable (${lesson.title}): $e — Switching to offline Socratic fallback', tag: 'VIDEO_CUBIT');
final fallback = _buildResilientLessonPlayback(lesson, subject: subject); final fallback = _buildResilientLessonPlayback(lesson, subject: subject);
// Load saved resume position // Load saved resume position strictly per video
int resumePos = fallback.lastPositionSeconds ?? 0; int resumePos = fallback.lastPositionSeconds ?? 0;
Set<int> passedIds = {};
try { try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final localPos = prefs.getInt('saved_video_pos_${lesson.id}') ?? 0; final localPos = prefs.getInt('saved_video_pos_$storageKey') ?? 0;
if (localPos > resumePos) resumePos = localPos; if (localPos > resumePos) resumePos = localPos;
final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? [];
passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet();
} catch (_) {} } catch (_) {}
emit(VideoPlaybackReady( emit(VideoPlaybackReady(
@@ -115,6 +134,7 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
lessonItem: lesson, lessonItem: lesson,
subject: subject, subject: subject,
currentPositionSeconds: resumePos, currentPositionSeconds: resumePos,
passedCheckpointIds: passedIds,
isPlaying: true, isPlaying: true,
)); ));
} }
@@ -197,21 +217,12 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
LessonVersionModel( LessonVersionModel(
lessonId: 101, lessonId: 101,
isAi: true, isAi: true,
teacherName: 'منصة صَقِل الذكية 🤖', teacherName: 'منصة صَقِل التعليمية المعتمدة',
schoolName: 'المركز الرقمي المعتمد', schoolName: 'المركز الرقمي المعتمد',
label: 'شرح الذكاء الاصطناعي الرسمي', label: 'الشرح الرقمي الرسمي المعتمد',
isRecommended: true, isRecommended: true,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
), ),
LessonVersionModel(
lessonId: 102,
isAi: false,
teacherName: 'أ. أحمد المجالي 👨‍🏫',
schoolName: 'مدارس الثقافة العسكرية',
label: 'شرح معلم معتمد 🌟 4.9',
isRecommended: false,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
),
], ],
); );
} }
@@ -279,12 +290,16 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
final currentState = state; final currentState = state;
if (currentState is! VideoPlaybackReady) return; if (currentState is! VideoPlaybackReady) return;
try { try {
final storageKey = _getLessonStorageKey(currentState.lessonItem, fallbackId: '${currentState.playbackData.lessonId}');
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if (currentState.lessonItem != null) { await prefs.setInt('saved_video_pos_$storageKey', positionSeconds);
await prefs.setInt('saved_video_pos_${currentState.lessonItem!.id}', positionSeconds); if (currentState.passedCheckpointIds.isNotEmpty) {
await prefs.setStringList(
'passed_checkpoints_$storageKey',
currentState.passedCheckpointIds.map((id) => id.toString()).toList(),
);
} }
if (currentState.playbackData.lessonId > 0) { if (currentState.playbackData.lessonId > 0) {
await prefs.setInt('saved_video_pos_${currentState.playbackData.lessonId}', positionSeconds);
await _repo.saveProgress( await _repo.saveProgress(
lessonId: currentState.playbackData.lessonId, lessonId: currentState.playbackData.lessonId,
positionSeconds: positionSeconds, positionSeconds: positionSeconds,
@@ -307,6 +322,13 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
AppLogger.log('✅ Correct answer! Rewarding +0.5% readiness.', tag: 'SOCRATIC_ENGINE'); AppLogger.log('✅ Correct answer! Rewarding +0.5% readiness.', tag: 'SOCRATIC_ENGINE');
final updatedPassed = Set<int>.from(currentState.passedCheckpointIds)..add(cp.id); final updatedPassed = Set<int>.from(currentState.passedCheckpointIds)..add(cp.id);
_lastObservedPosition = cp.timestampSeconds; _lastObservedPosition = cp.timestampSeconds;
// Persist passed checkpoint for this specific video
final storageKey = _getLessonStorageKey(currentState.lessonItem, fallbackId: '${currentState.playbackData.lessonId}');
SharedPreferences.getInstance().then((prefs) {
prefs.setStringList('passed_checkpoints_$storageKey', updatedPassed.map((id) => id.toString()).toList());
}).catchError((_) {});
emit(currentState.copyWith( emit(currentState.copyWith(
clearActiveCheckpoint: true, clearActiveCheckpoint: true,
isPlaying: true, isPlaying: true,
@@ -1,6 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
/// ============================================================================== /// ==============================================================================
/// SAQEL ENTERPRISE - ARABIC INTERACTIVE LAB (شجرة الإعراب ومختبر العروض) /// SAQEL ENTERPRISE - ARABIC INTERACTIVE LAB (شجرة الإعراب ومختبر العروض)
@@ -140,12 +139,109 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
}, },
], ],
}, },
{
'fullText': 'المُعَلِّمُ المُخْلِصُ مُحْتَرَمٌ قَدْرُهُ بَيْنَ النَّاسِ',
'type': 'جملة اسمية تشتمل على اسم فاعل واسم مفعول وعملهما',
'rootNode': 'الجملة الاسمية والمشتقات العاملة (الصف العاشر)',
'words': [
{
'word': 'المُعَلِّمُ',
'role': 'مبتدأ مرفوع (اسم فاعل من غير الثلاثي)',
'case': 'مرفوع وعلامة رفعه الضمة الظاهرة على آخره',
'explanation': 'اسم معرف بأل وقع في أول الجملة، وهو اسم فاعل من الفعل علّم يُعلّم.',
'tag': 'مبتدأ / اسم فاعل',
},
{
'word': 'المُخْلِصُ',
'role': 'نعت (صفة) للمبتدأ',
'case': 'مرفوع وعلامة رفعه الضمة الظاهرة',
'explanation': 'نعت حقيقي طابق المنعوت في التذكير والتعريف والإعراب.',
'tag': 'نعت مرفوع',
},
{
'word': 'مُحْتَرَمٌ',
'role': 'خبر المبتدأ (اسم مفعول عامل)',
'case': 'مرفوع وعلامة رفعه الضمة الظاهرة',
'explanation': 'خبر أتم معنى الجملة، وهو مشتق (اسم مفعول) يعمل عمل فعله المبني للمجهول.',
'tag': 'خبر / اسم مفعول',
},
{
'word': 'قَدْرُهُ',
'role': 'نائب فاعل لاسم المفعول (مُحترَم)',
'case': 'مرفوع بالضمة، والهاء ضمير متصل مبني في محل جر مضاف إليه',
'explanation': 'اسم المفعول يعمل عمل فعله المبني للمجهول فيرفع نائب فاعل بعده.',
'tag': 'نائب فاعل',
},
{
'word': 'بَيْنَ',
'role': 'ظرف مكان منصوب (مفعول فيه)',
'case': 'منصوب بالفتحة وهو مضاف',
'explanation': 'ظرف مكان يحدد موقع الاتصاف بالاحترام.',
'tag': 'ظرف مكان',
},
{
'word': 'النَّاسِ',
'role': 'مضاف إليه مجرور',
'case': 'مجرور وعلامة جره الكسرة الظاهرة على آخره',
'explanation': 'اسم مجرور بالإضافة جاء بعد ظرف المكان.',
'tag': 'مضاف إليه',
},
],
},
{
'fullText': 'مَنْ يَزْرَعْ خَيْراً يَحْصُدْ ثِمَاراً طَيِّبَةً',
'type': 'أسلوب شرط جازم (منهاج العاشر الأساسي)',
'rootNode': 'أسلوب الشرط (اسم شرط + فعل الشرط + جواب الشرط)',
'words': [
{
'word': 'مَنْ',
'role': 'اسم شرط جازم مبني',
'case': 'مبني على السكون في محل رفع مبتدأ',
'explanation': 'اسم شرط جازم يدل على العاقل ويربط بين جملتين.',
'tag': 'اسم شرط',
},
{
'word': 'يَزْرَعْ',
'role': 'فعل الشرط مضارع مجزوم',
'case': 'مجزوم بالسكون، والفاعل ضمير مستتر تقديره (هو)',
'explanation': 'فعل مضارع وقع بعد أداة الشرط الجازمة، فجُزم بالسكون.',
'tag': 'فعل شرط مجزوم',
},
{
'word': 'خَيْراً',
'role': 'مفعول به لفعل الشرط',
'case': 'منصوب وعلامة نصبه تنوين الفتح',
'explanation': 'وقع عليه فعل الزراعة المعنوي.',
'tag': 'مفعول به',
},
{
'word': 'يَحْصُدْ',
'role': 'جواب الشرط وجزاؤه مضارع مجزوم',
'case': 'مجزوم بالسكون، والفاعل مستتر تقديره (هو)',
'explanation': 'فعل مضارع جُزم لوقوعه جواباً للشرط.',
'tag': 'جواب شرط مجزوم',
},
{
'word': 'ثِمَاراً',
'role': 'مفعول به لجواب الشرط',
'case': 'منصوب وعلامة نصبه تنوين الفتح الظاهر',
'explanation': 'الشيء المحصود نتيجة العمل.',
'tag': 'مفعول به',
},
{
'word': 'طَيِّبَةً',
'role': 'نعت لثماراً',
'case': 'منصوب وعلامة نصبه تنوين الفتح',
'explanation': 'صفة طابقت موصوفها في التنكير والجمع والتأنيث والنصب.',
'tag': 'نعت منصوب',
},
],
},
]; ];
// Poetry Meter State // Poetry Meter State
int _selectedMeterIndex = 0; int _selectedMeterIndex = 0;
bool _isPlayingBeat = false; bool _isPlayingBeat = false;
int _currentBeatStep = 0;
final List<Map<String, dynamic>> _poetryMeters = [ final List<Map<String, dynamic>> _poetryMeters = [
{ {
@@ -186,7 +282,9 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_tabController = TabController(length: 2, vsync: this); _tabController = TabController(length: 2, vsync: this)..addListener(() {
if (mounted) setState(() {});
});
} }
@override @override
@@ -339,8 +437,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
_selectedWordIndex = null; _selectedWordIndex = null;
}), }),
child: Container( child: Container(
margin: EdgeInsets.only(left: idx == 0 ? 8 : 0), margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isCur color: isCur
? const Color(0xFF1E293B) ? const Color(0xFF1E293B)
@@ -1,6 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
/// ============================================================================== /// ==============================================================================
/// SAQEL ENTERPRISE - JORDAN HISTORY INTERACTIVE TIMELINE (الخط الزمني لتاريخ الأردن) /// SAQEL ENTERPRISE - JORDAN HISTORY INTERACTIVE TIMELINE (الخط الزمني لتاريخ الأردن)
@@ -1,6 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
/// ============================================================================== /// ==============================================================================
/// SAQEL ENTERPRISE - ISLAMIC INHERITANCE & TAJWEED LAB (حاسبة المواريث والتجويد) /// SAQEL ENTERPRISE - ISLAMIC INHERITANCE & TAJWEED LAB (حاسبة المواريث والتجويد)
@@ -787,211 +787,21 @@ class _SubjectHubScreenState extends State<SubjectHubScreen> with SingleTickerPr
); );
} }
/// Multi-Teacher and AI Video Selector Bottom Sheet /// Direct Launch of Socratic Video Player or Transparent No-Video Sheet
void _showLessonVideoSelector(BuildContext context, CurriculumLessonItemModel lesson) { void _showLessonVideoSelector(BuildContext context, CurriculumLessonItemModel lesson) {
if (!lesson.hasVideo) { if (!lesson.hasVideo) {
_showNoVideoAvailableSheet(context, lesson); _showNoVideoAvailableSheet(context, lesson);
return; return;
} }
showModalBottomSheet(
context: context,
backgroundColor: AppColors.darkSurface,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (ctx) => SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.white24,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 16),
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(25),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(CupertinoIcons.play_circle_fill, color: AppColors.saqelCyan, size: 24),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'اختر أسلوب الشرح المناسب لك 🎥',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 16),
),
const SizedBox(height: 3),
Text(
lesson.title,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
),
],
),
),
],
),
const SizedBox(height: 18),
// Option 1: Official Saqel AI Master Lesson // Direct authentic launch: zero mockups, zero fake teacher names
_buildVideoOptionCard( Navigator.of(context).push(
title: 'شرح الذكاء الاصطناعي لمنصة صَقِل 🤖', CupertinoPageRoute(
subtitle: 'الشرح الرسمي المعتمد وتفكيك المفاهيم خطوة بخطوة', builder: (_) => SocraticVideoPlayerScreen(
tag: 'المنهاج المعتمد + سقراطي', lesson: lesson,
tagColor: AppColors.saqelCyan, subject: widget.subject,
icon: CupertinoIcons.sparkles, instructorName: 'منصة صَقِل التعليمية المعتمدة',
iconColor: AppColors.saqelCyan, videoSourceLabel: 'الشرح الرقمي الرسمي المعتمد',
onTap: () {
Navigator.of(ctx).pop();
Navigator.of(context).push(
CupertinoPageRoute(
builder: (_) => SocraticVideoPlayerScreen(
lesson: lesson,
subject: widget.subject,
instructorName: 'منصة صَقِل الذكية 🤖',
videoSourceLabel: 'شرح الذكاء الاصطناعي الرسمي',
),
),
);
},
),
const SizedBox(height: 12),
// Option 2: Certified Teacher 1
_buildVideoOptionCard(
title: 'شرح الأستاذ رعد الخمايسة 👨‍🏫',
subtitle: 'خبير التوجيهي — حل نماذج وأسئلة وزارية إضافية',
tag: 'معلم معتمد 🌟 4.9',
tagColor: AppColors.guardianAmber,
icon: CupertinoIcons.person_crop_circle_badge_checkmark,
iconColor: AppColors.guardianAmber,
onTap: () {
Navigator.of(ctx).pop();
Navigator.of(context).push(
CupertinoPageRoute(
builder: (_) => SocraticVideoPlayerScreen(
lesson: lesson,
subject: widget.subject,
instructorName: 'أ. رعد الخمايسة (معلم معتمد)',
videoSourceLabel: 'شرح الأستاذ رعد الخمايسة',
),
),
);
},
),
const SizedBox(height: 12),
// Option 3: Certified Teacher 2
_buildVideoOptionCard(
title: 'شرح الأستاذ إياد النجار 👨‍🏫',
subtitle: 'كبير المعلمين — تبسيط المفاهيم المعقدة والتحليل البياني',
tag: 'معلم معتمد 🌟 4.8',
tagColor: AppColors.emeraldGreen,
icon: CupertinoIcons.person_crop_circle_fill,
iconColor: AppColors.emeraldGreen,
onTap: () {
Navigator.of(ctx).pop();
Navigator.of(context).push(
CupertinoPageRoute(
builder: (_) => SocraticVideoPlayerScreen(
lesson: lesson,
subject: widget.subject,
instructorName: 'أ. إياد النجار (كبير المعلمين)',
videoSourceLabel: 'شرح الأستاذ إياد النجار',
),
),
);
},
),
const SizedBox(height: 16),
],
),
),
),
);
}
Widget _buildVideoOptionCard({
required String title,
required String subtitle,
required String tag,
required Color tagColor,
required IconData icon,
required Color iconColor,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(14),
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: AppColors.darkCard,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: AppColors.darkCardBorder),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: iconColor.withAlpha(25),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: iconColor, size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13.5),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
decoration: BoxDecoration(
color: tagColor.withAlpha(25),
borderRadius: BorderRadius.circular(6),
),
child: Text(
tag,
style: TextStyle(color: tagColor, fontSize: 10, fontWeight: FontWeight.w700),
),
),
],
),
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 11.5),
),
],
),
),
const SizedBox(width: 8),
const Icon(CupertinoIcons.chevron_back, color: AppColors.textSecondaryDark, size: 14),
],
), ),
), ),
); );
@@ -188,45 +188,42 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// K-12 Grade Level Selector Bar (الصف الأول -> التوجيهي) // Official Academic Grade Level Banner (Strict Grade-Gate — الصف العاشر فقط)
SizedBox( Container(
height: 42, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: ListView.builder( decoration: BoxDecoration(
scrollDirection: Axis.horizontal, color: AppColors.saqelCyan.withAlpha(20),
itemCount: (state.availableGrades.isNotEmpty ? state.availableGrades : GradeLevelModel.allK12Grades).length, borderRadius: BorderRadius.circular(16),
itemBuilder: (context, gIdx) { border: Border.all(color: AppColors.saqelCyan.withAlpha(60)),
final grade = (state.availableGrades.isNotEmpty ? state.availableGrades : GradeLevelModel.allK12Grades)[gIdx]; ),
final isSelected = state.selectedGrade == grade.key || state.selectedGrade.replaceAll('grade_', '') == grade.key.replaceAll('grade_', ''); child: Row(
children: [
return Padding( Container(
padding: const EdgeInsets.only(left: 8), padding: const EdgeInsets.all(8),
child: InkWell( decoration: BoxDecoration(
onTap: () => context.read<CurriculumCubit>().selectGrade(grade.key), color: AppColors.saqelCyan.withAlpha(35),
borderRadius: BorderRadius.circular(14), borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? AppColors.saqelCyan.withAlpha(30) : AppColors.darkSurface,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: isSelected ? AppColors.saqelCyan : AppColors.darkCardBorder,
width: isSelected ? 1.5 : 1.0,
),
),
child: Center(
child: Text(
grade.name,
style: TextStyle(
color: isSelected ? AppColors.saqelCyan : AppColors.textSecondaryDark,
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500,
fontSize: 12.5,
),
),
),
),
), ),
); child: const Icon(CupertinoIcons.lock_shield_fill, color: AppColors.saqelCyan, size: 18),
}, ),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'الصف العاشر الأساسي — المسار الأكاديمي المعتمد',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.w800, fontSize: 13.5),
),
SizedBox(height: 2),
Text(
'خطة دراسية موحدة ومطابقة للمنهاج الوزاري الأردني المطور',
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
],
),
),
],
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@@ -1,6 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../data/models/error_notebook_model.dart'; import '../../../data/models/error_notebook_model.dart';
import '../../../data/repositories/error_notebook_repository.dart'; import '../../../data/repositories/error_notebook_repository.dart';
@@ -51,6 +51,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
late AnimationController _watermarkController; late AnimationController _watermarkController;
VideoPlayerController? _videoController; VideoPlayerController? _videoController;
bool _isVideoInitialized = false; bool _isVideoInitialized = false;
String? _videoInitError;
int _lastSyncedPosition = -1; int _lastSyncedPosition = -1;
Timer? _controlsHideTimer; Timer? _controlsHideTimer;
bool _showVideoControls = true; bool _showVideoControls = true;
@@ -162,6 +163,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
if (mounted) { if (mounted) {
setState(() { setState(() {
_isVideoInitialized = true; _isVideoInitialized = true;
_videoInitError = null;
}); });
_revealVideoControls(); _revealVideoControls();
if (state.currentPositionSeconds > 3) { if (state.currentPositionSeconds > 3) {
@@ -178,6 +180,13 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
_videoController!.play(); _videoController!.play();
} }
} }
}).catchError((err) {
if (mounted) {
setState(() {
_isVideoInitialized = false;
_videoInitError = 'الفيديو قيد المزامنة والتجهيز على خادم البث المباشر';
});
}
}); });
} else if (_isVideoInitialized) { } else if (_isVideoInitialized) {
// Sync play/pause state // Sync play/pause state
@@ -311,7 +320,31 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
), ),
), ),
// Center Lesson Title & Visuals // Center Lesson Title & Visuals
if (_showVideoControls) Center( if (_videoInitError != null)
Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(CupertinoIcons.videocam_fill, color: AppColors.guardianAmber, size: 44),
const SizedBox(height: 12),
Text(
_videoInitError!,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
const Text(
'يمكنك دراسة نتاجات وملخص الدرس عبر تبويب الكتب والمذكرات',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 12),
),
],
),
),
)
else if (_showVideoControls) Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
+6 -6
View File
@@ -340,10 +340,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.18" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -356,10 +356,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@@ -585,10 +585,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.9" version: "0.7.11"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -15,6 +15,10 @@ class SuperAdminTheme {
static const Color surface = Color(0xFF0E1422); static const Color surface = Color(0xFF0E1422);
static const Color surfaceCard = Color(0xFF141C2E); static const Color surfaceCard = Color(0xFF141C2E);
static const Color border = Color(0xFF1E293B); static const Color border = Color(0xFF1E293B);
static const Color backgroundDark = Color(0xFF06090F);
static const Color surfaceDark = Color(0xFF0E1422);
static const Color crimsonRed = Color(0xFFEF4444);
static const Color royalGold = Color(0xFFFFD166); static const Color royalGold = Color(0xFFFFD166);
static const Color imperialPurple = Color(0xFF8B5CF6); static const Color imperialPurple = Color(0xFF8B5CF6);
@@ -187,8 +187,8 @@ class _SuperAdminShellState extends State<SuperAdminShell> {
label: 'الرصد الكلي', label: 'الرصد الكلي',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(CupertinoIcons.cpu), icon: Icon(CupertinoIcons.sparkles),
activeIcon: Icon(CupertinoIcons.cpu, color: SuperAdminTheme.royalGold), activeIcon: Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.royalGold),
label: 'الذكاء المحلي', label: 'الذكاء المحلي',
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
@@ -53,7 +53,7 @@ class AiClusterTab extends StatelessWidget {
children: [ children: [
const Row( const Row(
children: [ children: [
Icon(CupertinoIcons.cpu, color: SuperAdminTheme.cyberCyan, size: 22), Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.cyberCyan, size: 22),
SizedBox(width: 8), SizedBox(width: 8),
Text( Text(
'عنقود الذكاء الاصطناعي السيادي (Local Inference)', 'عنقود الذكاء الاصطناعي السيادي (Local Inference)',
@@ -31,7 +31,6 @@ class TreasuryCliqTab extends StatelessWidget {
if (state is TreasuryLoaded) { if (state is TreasuryLoaded) {
final pendingItems = state.queue.where((item) => item.status == 'queued').toList(); final pendingItems = state.queue.where((item) => item.status == 'queued').toList();
final completedItems = state.queue.where((item) => item.status == 'completed').toList();
final double pendingSum = pendingItems.fold(0.0, (acc, item) => acc + item.amountJod); final double pendingSum = pendingItems.fold(0.0, (acc, item) => acc + item.amountJod);
return SingleChildScrollView( return SingleChildScrollView(
+6 -6
View File
@@ -316,10 +316,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.18" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -332,10 +332,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@@ -561,10 +561,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.9" version: "0.7.11"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
+2 -1
View File
@@ -28,7 +28,8 @@ void main() {
class SaqelTeacherApp extends StatelessWidget { class SaqelTeacherApp extends StatelessWidget {
final TeacherRepository repository; final TeacherRepository repository;
const SaqelTeacherApp({super.key, required this.repository}); SaqelTeacherApp({super.key, TeacherRepository? repository})
: repository = repository ?? TeacherRepository();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
+6 -6
View File
@@ -316,10 +316,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.18" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -332,10 +332,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.18.0"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@@ -561,10 +561,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.9" version: "0.7.11"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
+1 -1
View File
@@ -3,7 +3,7 @@ import 'package:teacher_app/main.dart';
void main() { void main() {
testWidgets('Teacher App Smoke Test', (WidgetTester tester) async { testWidgets('Teacher App Smoke Test', (WidgetTester tester) async {
await tester.pumpWidget(const SaqelTeacherApp()); await tester.pumpWidget(SaqelTeacherApp());
expect(find.text('استوديو المعلم المعتمد — صَقِل'), findsOneWidget); expect(find.text('استوديو المعلم المعتمد — صَقِل'), findsOneWidget);
}); });
} }