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:
@@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
/// Directorate Model (المظلة المركزية: مديرية الثقافة العسكرية)
|
||||
class DirectorateOverview {
|
||||
final String code;
|
||||
|
||||
@@ -105,7 +105,7 @@ class _SchoolPrincipalScreenState extends State<SchoolPrincipalScreen>
|
||||
}
|
||||
|
||||
void _showDualFormsPreview() async {
|
||||
final data = await DirectorateApiService.fetchDualForms();
|
||||
await DirectorateApiService.fetchDualForms();
|
||||
if (!mounted) return;
|
||||
|
||||
showModalBottomSheet(
|
||||
|
||||
@@ -316,10 +316,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -332,10 +332,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.18.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -561,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -84,7 +84,7 @@ class CurriculumBakedData {
|
||||
#### البند 1: حل المعادلات بإخراج العامل المشترك الأكبر
|
||||
- **قاعدة:** نجعل طرف المعادلة الأيمن صفراً، ثم نخرج العامل المشترك، ونحل المعادلة الناتجة.
|
||||
- **مثال 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\\}\$.
|
||||
|
||||
- **أتحقق من فهمي (صفحة 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\$. نعوض في الثانية:
|
||||
$$\$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^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 = 3 \\implies y = 4\$ (النقطة: \$(3, 4)\$)
|
||||
- عندما \$x = -4 \\implies y = -3\$ (النقطة: \$( -4, -3)\$)
|
||||
|
||||
@@ -144,11 +144,11 @@ class CurriculumBakedData {
|
||||
---
|
||||
|
||||
### السؤال الثاني: حل النظام الجبري التالي وتحقق من صحة الحل (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\$ في المعادلة الثانية:
|
||||
$$\$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 = 1 \\implies y = 2(1) + 3 = 5\$ ➔ الحل الأول: \$(1, 5)\$.
|
||||
- إذا \$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):
|
||||
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}\$).
|
||||
- ينعدم الضرب القياسي إذا كان المتجهان متعامدين (\$\\phi = 90^\\circ\$ لأن \$\\cos 90^\\circ = 0\$).
|
||||
|
||||
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\$.
|
||||
3. اتزان الحلقة في المنتصف بوضع قوة موازنة ثالثة في الربع المقابل بمقدار \$250\\text{ g}\$ وزاوية \$233^\\circ\$.
|
||||
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();
|
||||
// Default video availability: true for core Grade 10 subjects (Math, Physics, English) and explicitly tagged lessons
|
||||
final bool isCoreTenthSubjectLesson = filePath != null && (
|
||||
filePath.contains('math_10/semester_1') ||
|
||||
filePath.contains('physics_10/semester_1') ||
|
||||
filePath.contains('english_10/semester_1') ||
|
||||
filePath.contains('lesson_01') ||
|
||||
filePath.contains('lesson_1a')
|
||||
final jsonId = json['id']?.toString();
|
||||
|
||||
// Stable, deterministic lesson ID (vital for persistent per-lesson progress tracking)
|
||||
final stableId = (jsonId != null && jsonId.isNotEmpty)
|
||||
? jsonId
|
||||
: (filePath != null && filePath.isNotEmpty
|
||||
? 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?) ??
|
||||
((json['video_url'] != null && json['video_url'].toString().isNotEmpty) || isCoreTenthSubjectLesson);
|
||||
final bool hasVideoExplicit = !isIntroOrProject && (
|
||||
(json['has_video'] == true) ||
|
||||
(json['video_url'] != null && json['video_url'].toString().isNotEmpty) ||
|
||||
isMath10Lesson1
|
||||
);
|
||||
|
||||
return CurriculumLessonItemModel(
|
||||
id: json['id']?.toString() ?? 'lesson_${DateTime.now().millisecondsSinceEpoch}',
|
||||
id: stableId,
|
||||
title: json['title']?.toString() ?? 'درس بدون عنوان',
|
||||
outcomes: outs,
|
||||
markdownFilePath: filePath,
|
||||
|
||||
@@ -76,19 +76,34 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
: _repo = repo ?? CurriculumRepository(),
|
||||
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 {
|
||||
AppLogger.log('Loading Socratic playback for lesson: ${lesson.title}', tag: 'VIDEO_CUBIT');
|
||||
_lastObservedPosition = -1;
|
||||
emit(VideoPlaybackLoading());
|
||||
final storageKey = _getLessonStorageKey(lesson);
|
||||
|
||||
try {
|
||||
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;
|
||||
Set<int> passedIds = {};
|
||||
try {
|
||||
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;
|
||||
final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? [];
|
||||
passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet();
|
||||
} catch (_) {}
|
||||
|
||||
emit(VideoPlaybackReady(
|
||||
@@ -96,18 +111,22 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
lessonItem: lesson,
|
||||
subject: subject,
|
||||
currentPositionSeconds: resumePos,
|
||||
passedCheckpointIds: passedIds,
|
||||
isPlaying: true,
|
||||
));
|
||||
} catch (e) {
|
||||
AppLogger.log('Playback API unavailable (${lesson.title}): $e — Switching to offline Socratic fallback', tag: 'VIDEO_CUBIT');
|
||||
final fallback = _buildResilientLessonPlayback(lesson, subject: subject);
|
||||
|
||||
// Load saved resume position
|
||||
// Load saved resume position strictly per video
|
||||
int resumePos = fallback.lastPositionSeconds ?? 0;
|
||||
Set<int> passedIds = {};
|
||||
try {
|
||||
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;
|
||||
final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? [];
|
||||
passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet();
|
||||
} catch (_) {}
|
||||
|
||||
emit(VideoPlaybackReady(
|
||||
@@ -115,6 +134,7 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
lessonItem: lesson,
|
||||
subject: subject,
|
||||
currentPositionSeconds: resumePos,
|
||||
passedCheckpointIds: passedIds,
|
||||
isPlaying: true,
|
||||
));
|
||||
}
|
||||
@@ -197,21 +217,12 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
LessonVersionModel(
|
||||
lessonId: 101,
|
||||
isAi: true,
|
||||
teacherName: 'منصة صَقِل الذكية 🤖',
|
||||
teacherName: 'منصة صَقِل التعليمية المعتمدة',
|
||||
schoolName: 'المركز الرقمي المعتمد',
|
||||
label: 'شرح الذكاء الاصطناعي الرسمي',
|
||||
label: 'الشرح الرقمي الرسمي المعتمد',
|
||||
isRecommended: true,
|
||||
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;
|
||||
if (currentState is! VideoPlaybackReady) return;
|
||||
try {
|
||||
final storageKey = _getLessonStorageKey(currentState.lessonItem, fallbackId: '${currentState.playbackData.lessonId}');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (currentState.lessonItem != null) {
|
||||
await prefs.setInt('saved_video_pos_${currentState.lessonItem!.id}', positionSeconds);
|
||||
await prefs.setInt('saved_video_pos_$storageKey', positionSeconds);
|
||||
if (currentState.passedCheckpointIds.isNotEmpty) {
|
||||
await prefs.setStringList(
|
||||
'passed_checkpoints_$storageKey',
|
||||
currentState.passedCheckpointIds.map((id) => id.toString()).toList(),
|
||||
);
|
||||
}
|
||||
if (currentState.playbackData.lessonId > 0) {
|
||||
await prefs.setInt('saved_video_pos_${currentState.playbackData.lessonId}', positionSeconds);
|
||||
await _repo.saveProgress(
|
||||
lessonId: currentState.playbackData.lessonId,
|
||||
positionSeconds: positionSeconds,
|
||||
@@ -307,6 +322,13 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
AppLogger.log('✅ Correct answer! Rewarding +0.5% readiness.', tag: 'SOCRATIC_ENGINE');
|
||||
final updatedPassed = Set<int>.from(currentState.passedCheckpointIds)..add(cp.id);
|
||||
_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(
|
||||
clearActiveCheckpoint: true,
|
||||
isPlaying: true,
|
||||
|
||||
+103
-5
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
|
||||
/// ==============================================================================
|
||||
/// 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
|
||||
int _selectedMeterIndex = 0;
|
||||
bool _isPlayingBeat = false;
|
||||
int _currentBeatStep = 0;
|
||||
|
||||
final List<Map<String, dynamic>> _poetryMeters = [
|
||||
{
|
||||
@@ -186,7 +282,9 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
_tabController = TabController(length: 2, vsync: this)..addListener(() {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -339,8 +437,8 @@ class _ArabicInteractiveLabViewState extends State<ArabicInteractiveLabView>
|
||||
_selectedWordIndex = null;
|
||||
}),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(left: idx == 0 ? 8 : 0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
margin: const EdgeInsets.only(left: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isCur
|
||||
? const Color(0xFF1E293B)
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
|
||||
/// ==============================================================================
|
||||
/// SAQEL ENTERPRISE - JORDAN HISTORY INTERACTIVE TIMELINE (الخط الزمني لتاريخ الأردن)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
|
||||
/// ==============================================================================
|
||||
/// 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) {
|
||||
if (!lesson.hasVideo) {
|
||||
_showNoVideoAvailableSheet(context, lesson);
|
||||
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
|
||||
_buildVideoOptionCard(
|
||||
title: 'شرح الذكاء الاصطناعي لمنصة صَقِل 🤖',
|
||||
subtitle: 'الشرح الرسمي المعتمد وتفكيك المفاهيم خطوة بخطوة',
|
||||
tag: 'المنهاج المعتمد + سقراطي',
|
||||
tagColor: AppColors.saqelCyan,
|
||||
icon: CupertinoIcons.sparkles,
|
||||
iconColor: AppColors.saqelCyan,
|
||||
onTap: () {
|
||||
Navigator.of(ctx).pop();
|
||||
// Direct authentic launch: zero mockups, zero fake teacher names
|
||||
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),
|
||||
],
|
||||
instructorName: 'منصة صَقِل التعليمية المعتمدة',
|
||||
videoSourceLabel: 'الشرح الرقمي الرسمي المعتمد',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -188,45 +188,42 @@ class _SubjectsGridScreenState extends State<SubjectsGridScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// K-12 Grade Level Selector Bar (الصف الأول -> التوجيهي)
|
||||
SizedBox(
|
||||
height: 42,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: (state.availableGrades.isNotEmpty ? state.availableGrades : GradeLevelModel.allK12Grades).length,
|
||||
itemBuilder: (context, gIdx) {
|
||||
final grade = (state.availableGrades.isNotEmpty ? state.availableGrades : GradeLevelModel.allK12Grades)[gIdx];
|
||||
final isSelected = state.selectedGrade == grade.key || state.selectedGrade.replaceAll('grade_', '') == grade.key.replaceAll('grade_', '');
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: InkWell(
|
||||
onTap: () => context.read<CurriculumCubit>().selectGrade(grade.key),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
// Official Academic Grade Level Banner (Strict Grade-Gate — الصف العاشر فقط)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
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,
|
||||
color: AppColors.saqelCyan.withAlpha(20),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: AppColors.saqelCyan.withAlpha(60)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.saqelCyan.withAlpha(35),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
grade.name,
|
||||
style: TextStyle(
|
||||
color: isSelected ? AppColors.saqelCyan : AppColors.textSecondaryDark,
|
||||
fontWeight: isSelected ? FontWeight.w800 : FontWeight.w500,
|
||||
fontSize: 12.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../data/models/error_notebook_model.dart';
|
||||
import '../../../data/repositories/error_notebook_repository.dart';
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
late AnimationController _watermarkController;
|
||||
VideoPlayerController? _videoController;
|
||||
bool _isVideoInitialized = false;
|
||||
String? _videoInitError;
|
||||
int _lastSyncedPosition = -1;
|
||||
Timer? _controlsHideTimer;
|
||||
bool _showVideoControls = true;
|
||||
@@ -162,6 +163,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isVideoInitialized = true;
|
||||
_videoInitError = null;
|
||||
});
|
||||
_revealVideoControls();
|
||||
if (state.currentPositionSeconds > 3) {
|
||||
@@ -178,6 +180,13 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
_videoController!.play();
|
||||
}
|
||||
}
|
||||
}).catchError((err) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isVideoInitialized = false;
|
||||
_videoInitError = 'الفيديو قيد المزامنة والتجهيز على خادم البث المباشر';
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (_isVideoInitialized) {
|
||||
// Sync play/pause state
|
||||
@@ -311,7 +320,31 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
),
|
||||
),
|
||||
// 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(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
@@ -340,10 +340,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -356,10 +356,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.18.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -585,10 +585,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -16,6 +16,10 @@ class SuperAdminTheme {
|
||||
static const Color surfaceCard = Color(0xFF141C2E);
|
||||
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 imperialPurple = Color(0xFF8B5CF6);
|
||||
static const Color emeraldGreen = Color(0xFF10B981);
|
||||
|
||||
@@ -187,8 +187,8 @@ class _SuperAdminShellState extends State<SuperAdminShell> {
|
||||
label: 'الرصد الكلي',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(CupertinoIcons.cpu),
|
||||
activeIcon: Icon(CupertinoIcons.cpu, color: SuperAdminTheme.royalGold),
|
||||
icon: Icon(CupertinoIcons.sparkles),
|
||||
activeIcon: Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.royalGold),
|
||||
label: 'الذكاء المحلي',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
|
||||
@@ -53,7 +53,7 @@ class AiClusterTab extends StatelessWidget {
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.cpu, color: SuperAdminTheme.cyberCyan, size: 22),
|
||||
Icon(CupertinoIcons.sparkles, color: SuperAdminTheme.cyberCyan, size: 22),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'عنقود الذكاء الاصطناعي السيادي (Local Inference)',
|
||||
|
||||
@@ -31,7 +31,6 @@ class TreasuryCliqTab extends StatelessWidget {
|
||||
|
||||
if (state is TreasuryLoaded) {
|
||||
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);
|
||||
|
||||
return SingleChildScrollView(
|
||||
|
||||
@@ -316,10 +316,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -332,10 +332,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.18.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -561,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -28,7 +28,8 @@ void main() {
|
||||
class SaqelTeacherApp extends StatelessWidget {
|
||||
final TeacherRepository repository;
|
||||
|
||||
const SaqelTeacherApp({super.key, required this.repository});
|
||||
SaqelTeacherApp({super.key, TeacherRepository? repository})
|
||||
: repository = repository ?? TeacherRepository();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -316,10 +316,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.18"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -332,10 +332,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.18.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -561,10 +561,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
version: "0.7.11"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:teacher_app/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Teacher App Smoke Test', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const SaqelTeacherApp());
|
||||
await tester.pumpWidget(SaqelTeacherApp());
|
||||
expect(find.text('استوديو المعلم المعتمد — صَقِل'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user