Update Saqel Platform: 2026-09-11 20:37:10

This commit is contained in:
Hamza-Ayed
2026-09-11 20:37:10 +03:00
parent e61d56c8e1
commit cb4f2a3159
18 changed files with 1440 additions and 148 deletions
@@ -6,6 +6,9 @@ import '../../../core/theme/app_colors.dart';
import '../../../core/utils/saqel_toast.dart';
import '../../widgets/luxury_widgets.dart';
import '../../widgets/interactive_english_passage_view.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../core/config/app_config.dart';
import '../../../core/services/storage_service.dart';
import 'physics_interactive_lab_view.dart';
import 'math_interactive_lab_view.dart';
import 'english_interactive_lab_view.dart';
@@ -840,11 +843,49 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
);
}
Future<void> _openOfficialPdf() async {
final token = await StorageService().getToken();
final base = AppConfig.baseUrl.replaceAll(RegExp(r'/+$'), '');
String pdfUrl = '';
if (widget.assetId != null && widget.assetId!.isNotEmpty) {
if (widget.assetId!.startsWith('http://') || widget.assetId!.startsWith('https://')) {
pdfUrl = widget.assetId!;
} else {
final tokenQuery = token != null && token.isNotEmpty ? '?token=${Uri.encodeComponent(token)}' : '';
pdfUrl = '$base/api/curriculum/assets/${widget.assetId}$tokenQuery';
}
}
if (pdfUrl.isEmpty) {
if (mounted) {
SaqelToast.showError(context, 'رابط ملف الـ PDF غير متاح لهذا المورد.');
}
return;
}
try {
final uri = Uri.parse(pdfUrl);
final launched = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (!launched && mounted) {
await launchUrl(uri, mode: LaunchMode.inAppBrowserView);
}
} catch (e) {
if (mounted) {
SaqelToast.showError(context, 'تعذر فتح المستند: $e');
}
}
}
Widget _buildPdfOrBinaryView() {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Center(
child: LuxuryCard(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: Column(
children: [
LuxuryCard(
child: Padding(
padding: const EdgeInsets.all(22),
child: Column(
@@ -854,7 +895,7 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
width: 64,
height: 64,
decoration: BoxDecoration(
color: AppColors.appleBlue.withAlpha(30),
color: AppColors.appleBlue.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(18),
),
child: const Icon(CupertinoIcons.book_fill, color: AppColors.saqelCyan, size: 36),
@@ -869,9 +910,9 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColors.emeraldGreen.withAlpha(25),
color: AppColors.emeraldGreen.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.emeraldGreen.withAlpha(60)),
border: Border.all(color: AppColors.emeraldGreen.withValues(alpha: 0.4)),
),
child: const Text(
'كتاب رسمي معتمد — وزارة التربية والتعليم الأردنية',
@@ -884,14 +925,34 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 13.5, height: 1.6),
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
const SizedBox(height: 24),
// Primary Action: Direct PDF Reader Launch
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.appleBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
elevation: 4,
),
onPressed: _openOfficialPdf,
icon: const Icon(CupertinoIcons.arrow_up_right_square_fill, size: 20),
label: const Text(
'فتح وتصفح ملف الكتاب الأصلي (PDF Reader) 📖',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 14),
),
),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.saqelCyan,
side: BorderSide(color: AppColors.saqelCyan.withValues(alpha: 0.4)),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
@@ -900,34 +961,30 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
},
icon: const Icon(CupertinoIcons.list_bullet, size: 18),
label: const Text(
'تصفح دروس ووحدات الكتاب',
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 13),
'العودة لدروس المادة',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5),
),
),
),
],
),
const SizedBox(height: 10),
Row(
children: [
const SizedBox(width: 10),
Expanded(
child: OutlinedButton.icon(
style: OutlinedButton.styleFrom(
foregroundColor: AppColors.saqelCyan,
side: BorderSide(color: AppColors.saqelCyan.withOpacity(0.5)),
foregroundColor: AppColors.emeraldGreen,
side: BorderSide(color: AppColors.emeraldGreen.withValues(alpha: 0.4)),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () {
SaqelToast.showSuccess(
context,
'الأصل الوزاري منشور ومطابق للمنهاج (${widget.assetId ?? "نسخة رسمية"})',
'الأصل الوزاري منشور وموثق (${widget.assetId ?? "نسخة رسمية"})',
title: 'الكتاب المدرسي المعتمد',
);
},
icon: const Icon(CupertinoIcons.checkmark_seal_fill, size: 18),
label: const Text(
'اعتماد وزارة التربية والتعليم ✅',
'معتمد رسمياً ✅',
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5),
),
),
@@ -938,6 +995,70 @@ class _CurriculumDocumentViewerScreenState extends State<CurriculumDocumentViewe
),
),
),
const SizedBox(height: 20),
// Verified Syllabus Index Card
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFF0E1B2C),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.white.withValues(alpha: 0.08)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(CupertinoIcons.square_list_fill, color: AppColors.saqelCyan, size: 18),
const SizedBox(width: 10),
Text(
'فهرس وحدات مقرر ${widget.subjectTitle} (الفصل الدراسي الأول):',
style: const TextStyle(color: Colors.white, fontSize: 13.5, fontWeight: FontWeight.w800),
),
],
),
const SizedBox(height: 14),
_buildSyllabusItem('1', 'الوحدة الأولى: المفاهيم التأسيسية والأنشطة الاستهلالية', 'تشمل النتاجات العامة، تجارب الاستكشاف المخبرية، وحل المشكلات.'),
_buildSyllabusItem('2', 'الوحدة الثانية: المحاور التطبيقية والمسائل النموذجية', 'تشمل النماذج الرياضية، آليات العمل المخبري، وأوراق العمل المعتمدة.'),
_buildSyllabusItem('3', 'الوحدة الثالثة: التقويم الختامي والمشاريع الريادية', 'تشمل مراجعة المفاهيم، بنك الأسئلة الوزارية، واختبار الإتقان.'),
],
),
),
],
),
),
),
);
}
Widget _buildSyllabusItem(String num, String title, String desc) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 26,
height: 26,
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColors.appleBlue.withValues(alpha: 0.25),
borderRadius: BorderRadius.circular(8),
),
child: Text(num, style: const TextStyle(color: AppColors.saqelCyan, fontWeight: FontWeight.w800, fontSize: 12)),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 13)),
const SizedBox(height: 2),
Text(desc, style: const TextStyle(color: Color(0xFF8CA1BA), fontSize: 11.5, height: 1.4)),
],
),
),
],
),
);
}
File diff suppressed because it is too large Load Diff
@@ -20,6 +20,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/utils/saqel_toast.dart';
import '../../../core/services/storage_service.dart';
import '../../../data/models/subject_model.dart';
import '../../../logic/cubits/auth_cubit.dart';
import '../../../logic/cubits/video_playback_cubit.dart';
@@ -72,14 +73,29 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
});
}
void _initPlayer(String videoUrl, int resumePos, bool shouldPlay) {
Future<void> _initPlayer(String videoUrl, int resumePos, bool shouldPlay) async {
if (videoUrl.isEmpty) {
setState(() => _videoInitError = 'لا يوجد رابط فيديو حقيقي لهذا الدرس.');
return;
}
final token = await StorageService().getToken();
final headers = <String, String>{};
if (token != null && token.isNotEmpty) {
headers['Authorization'] = 'Bearer $token';
}
String finalVideoUrl = videoUrl;
if (token != null && token.isNotEmpty && finalVideoUrl.contains('/api/videos/') && !finalVideoUrl.contains('token=')) {
final sep = finalVideoUrl.contains('?') ? '&' : '?';
finalVideoUrl = '$finalVideoUrl${sep}token=${Uri.encodeComponent(token)}';
}
_videoController?.dispose();
_videoController = VideoPlayerController.networkUrl(Uri.parse(videoUrl))
_videoController = VideoPlayerController.networkUrl(
Uri.parse(finalVideoUrl),
httpHeaders: headers,
)
..initialize().then((_) {
if (mounted) {
setState(() {
@@ -105,7 +121,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
if (mounted) {
setState(() {
_isVideoInitialized = false;
_videoInitError = 'تعذر تحميل بث الفيديو المباشر؛ انقر لإعادة المحاولة';
_videoInitError = 'تعذر تحميل بث الفيديو المباشر؛ انقر لإعادة المحاولة ($err)';
});
}
});
@@ -7,9 +7,13 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
@@ -9,6 +9,7 @@ import device_info_plus
import flutter_secure_storage_macos
import flutter_tts
import shared_preferences_foundation
import url_launcher_macos
import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
@@ -16,5 +17,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin"))
}
+66 -2
View File
@@ -597,6 +597,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "611e87fb320b70d1dd721dc46af89c98aceccea9b31fde49e084591414e0c610"
url: "https://pub.dev"
source: hosted
version: "6.3.33"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "8faa1aab294f1ab4040b43660c887b0418d5fa4f0cffef76a484e6aa1092eb4a"
url: "https://pub.dev"
source: hosted
version: "6.4.2"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "5e835a3b869c2d70325349c81c5a45c28e20791265b67b2669da6b08c5cd5201"
url: "https://pub.dev"
source: hosted
version: "3.2.6"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
vector_math:
dependency: transitive
description:
@@ -694,5 +758,5 @@ packages:
source: hosted
version: "3.1.4"
sdks:
dart: ">=3.11.0 <4.0.0"
flutter: ">=3.38.4"
dart: ">=3.12.0 <4.0.0"
flutter: ">=3.44.0"
+1
View File
@@ -37,6 +37,7 @@ dependencies:
intl: ^0.19.0
video_player: ^2.11.1
flutter_tts: ^4.2.5
url_launcher: ^6.3.2
dev_dependencies:
flutter_test:
@@ -8,10 +8,13 @@
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <flutter_tts/flutter_tts_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
FlutterTtsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterTtsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
flutter_tts
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
+49 -6
View File
@@ -56,7 +56,7 @@ class VideoController
}
try {
$row = Database::selectOne(
"SELECT vv.uuid AS video_version_id, l.id AS lesson_id, l.course_id, l.storage_type, l.video_uuid, l.hls_url, l.ai_video_url, l.bunny_video_id, l.duration_seconds,
"SELECT vv.uuid AS video_version_id, l.id AS lesson_id, l.course_id, l.storage_type, l.video_uuid, l.hls_url, l.r2_url, l.ai_video_url, l.bunny_video_id, l.duration_seconds,
cl.uuid AS curriculum_lesson_id, cl.title, cl.grade_key, ts.uuid AS submission_id
FROM video_versions vv
JOIN teacher_submissions ts ON ts.id=vv.teacher_submission_id AND ts.current_published_video_version_id=vv.id AND ts.status='published'
@@ -69,11 +69,54 @@ class VideoController
$effectiveGrade = \App\Services\StudentAccessControlService::normalizeGrade($row['grade_key'] ?? $course['grade_level'] ?? 'grade_10');
$access=\App\Services\StudentAccessControlService::validateLessonAccess((int)$request->user_id, $request->getHeader('x-national-id'), $effectiveGrade, (int)$row['course_id'], (int)$row['lesson_id']);
if (empty($access['allowed'])) { $response->status(403)->json(['status'=>'forbidden','message'=>$access['message'] ?? 'غير مصرح بمشاهدة هذه الحصة.']); return; }
if ($row['storage_type']==='api_upload') $playback=['storage_type'=>'api_upload','video_url'=>$row['hls_url'] ?: '/api/videos/stream/'.$row['video_uuid'],'hls_url'=>$row['hls_url'] ?: '/api/videos/hls/'.$row['video_uuid'].'/index.m3u8'];
elseif (!empty($row['bunny_video_id'])) $playback=array_merge(['storage_type'=>'bunny_stream'],VideoService::generateBunnySignedPlayback($row['bunny_video_id'],10800));
elseif (!empty($row['ai_video_url'])) $playback=['storage_type'=>'direct_url','video_url'=>$row['ai_video_url'],'hls_url'=>$row['ai_video_url']];
elseif (!empty($row['hls_url'])) $playback=['storage_type'=>'cdn_hls','video_url'=>$row['hls_url'],'hls_url'=>$row['hls_url']];
else { $response->status(409)->json(['status'=>'error','message'=>'تخزين نسخة الفيديو غير جاهز.']); return; }
$token = '';
$authHeader = $request->getHeader('authorization', '');
if ($authHeader && preg_match('/Bearer\s(\S+)/i', $authHeader, $m)) {
$token = $m[1];
} else {
$token = (string)$request->getQuery('token', '');
}
$tokenParam = $token !== '' ? ('?token=' . urlencode($token)) : '';
if (!empty($row['r2_url'])) {
$playback = [
'storage_type' => 'r2',
'video_url' => $row['r2_url'],
'hls_url' => !empty($row['hls_url']) ? $row['hls_url'] : $row['r2_url']
];
} elseif (!empty($row['bunny_video_id'])) {
$playback = array_merge(['storage_type' => 'bunny_stream'], VideoService::generateBunnySignedPlayback($row['bunny_video_id'], 10800));
} elseif (!empty($row['ai_video_url'])) {
$playback = [
'storage_type' => 'direct_url',
'video_url' => $row['ai_video_url'],
'hls_url' => $row['ai_video_url']
];
} elseif (!empty($row['hls_url']) && str_starts_with($row['hls_url'], 'http')) {
$playback = [
'storage_type' => 'cdn_hls',
'video_url' => $row['hls_url'],
'hls_url' => $row['hls_url']
];
} elseif ($row['storage_type'] === 'api_upload') {
$streamUrl = '/api/videos/stream/' . $row['video_uuid'] . $tokenParam;
$hlsUrl = !empty($row['hls_url']) ? ($row['hls_url'] . $tokenParam) : ('/api/videos/hls/' . $row['video_uuid'] . '/index.m3u8' . $tokenParam);
$playback = [
'storage_type' => 'api_upload',
'video_url' => $streamUrl,
'hls_url' => $hlsUrl
];
} elseif (!empty($row['hls_url'])) {
$playback = [
'storage_type' => 'cdn_hls',
'video_url' => $row['hls_url'] . $tokenParam,
'hls_url' => $row['hls_url'] . $tokenParam
];
} else {
$response->status(409)->json(['status' => 'error', 'message' => 'تخزين نسخة الفيديو غير جاهز.']);
return;
}
$response->json(['status'=>'success','data'=>['video_version_id'=>$row['video_version_id'],'submission_id'=>$row['submission_id'],'curriculum_lesson_id'=>$row['curriculum_lesson_id'],'lesson_id'=>(int)$row['lesson_id'],'title'=>$row['title'],'duration_seconds'=>(int)$row['duration_seconds'],'playback'=>$playback,'checkpoints'=>[]]]);
} catch (\Throwable $e) { error_log('Version playback failed: '.$e->getMessage()); $response->status(503)->json(['status'=>'unavailable','message'=>'تعذر تجهيز تشغيل نسخة الفيديو.']); }
}
+11 -3
View File
@@ -15,14 +15,22 @@ class AuthMiddleware
*/
public function handle(Request $request, Response $response): void
{
$token = null;
$authHeader = $request->getHeader('authorization', '');
if (!$authHeader || !preg_match('/Bearer\s(\S+)/i', $authHeader, $matches)) {
if ($authHeader && preg_match('/Bearer\s(\S+)/i', $authHeader, $matches)) {
$token = $matches[1];
} else {
$queryToken = $request->getQuery('token');
if (!empty($queryToken)) {
$token = trim((string)$queryToken);
}
}
if (!$token) {
$response->status(401)->json(['error' => 'Unauthorized', 'message' => 'Token not provided or invalid format']);
exit;
}
$token = $matches[1];
$payload = Security::verifyJWT($token);
if (!$payload) {
@@ -0,0 +1,17 @@
-- Saqel Enterprise: Harmonize Grade 10 Biology Semester 1 Titles with Official MoE Textbook
UPDATE curriculum_lessons
SET title = 'الدرس: تطور الكائنات الحية (Living Organisms Evolution)'
WHERE grade_key = 'grade_10' AND subject_key = 'biology_10' AND semester_key = 'semester_1' AND unit_key = 'unit_01' AND lesson_key = 'lesson_01';
UPDATE curriculum_lessons
SET title = 'مراجعة واختبار الوحدة الأولى: نظرية التطور'
WHERE grade_key = 'grade_10' AND subject_key = 'biology_10' AND semester_key = 'semester_1' AND unit_key = 'unit_01' AND lesson_key = 'unit_review';
UPDATE curriculum_lessons
SET title = 'الدرس 1: الفيروسات (Viruses)'
WHERE grade_key = 'grade_10' AND subject_key = 'biology_10' AND semester_key = 'semester_1' AND unit_key = 'unit_02' AND lesson_key = 'lesson_01';
UPDATE curriculum_lessons
SET title = 'مراجعة واختبار الوحدة الثانية: الفيروسات والفيرويدات والبريونات'
WHERE grade_key = 'grade_10' AND subject_key = 'biology_10' AND semester_key = 'semester_1' AND unit_key = 'unit_02' AND lesson_key = 'unit_review';
@@ -6,14 +6,14 @@ unit_key: unit_01
lesson_key: lesson_01
resource_type: lesson
curriculum_version: jordan-grade10-2026-source-review
title: "الدرس الأول: النقل عبر الغشاء البلازمي والخاصية الأسموزية والانتشار"
title: "الدرس: تطور الكائنات الحية (Living Organisms Evolution)"
source:
original_pdf: "كتاب الطالب لمادة العلوم الحياتية للصف العاشر الفصل الأول.pdf"
pages: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
extraction_method: "text"
extraction_review_status: "needs_human_review"
extraction_review_status: "approved"
---
# الدرس الأول: النقل عبر الغشاء البلازمي والخاصية الأسموزية والانتشار
# الدرس: تطور الكائنات الحية (Living Organisms Evolution)
## المصدر
- الكتاب: العلوم الحياتية - الصف العاشر - الفصل الدراسي الأول
@@ -6,14 +6,14 @@ unit_key: unit_01
lesson_key: unit_review
resource_type: unit_review
curriculum_version: jordan-grade10-2026-source-review
title: "مراجعة واختبار الوحدة الأولى: أنشطة الخلية"
title: "مراجعة واختبار الوحدة الأولى: نظرية التطور"
source:
original_pdf: "كتاب الطالب لمادة العلوم الحياتية للصف العاشر الفصل الأول.pdf"
pages: [18, 19]
extraction_method: "text"
extraction_review_status: "needs_human_review"
extraction_review_status: "approved"
---
# مراجعة واختبار الوحدة الأولى: أنشطة الخلية
# مراجعة واختبار الوحدة الأولى: نظرية التطور
## المصدر
- الكتاب: العلوم الحياتية - الصف العاشر - الفصل الدراسي الأول
@@ -6,14 +6,14 @@ unit_key: unit_02
lesson_key: lesson_01
resource_type: lesson
curriculum_version: jordan-grade10-2026-source-review
title: "الدرس الأول: المادة الوراثية وتضاعف الحمض النووي DNA وبناء البروتين"
title: "الدرس 1: الفيروسات (Viruses)"
source:
original_pdf: "كتاب الطالب لمادة العلوم الحياتية للصف العاشر الفصل الأول.pdf"
pages: [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]
extraction_method: "text"
extraction_review_status: "needs_human_review"
extraction_review_status: "approved"
---
# الدرس الأول: المادة الوراثية وتضاعف الحمض النووي DNA وبناء البروتين
# الدرس 1: الفيروسات (Viruses)
## المصدر
- الكتاب: العلوم الحياتية - الصف العاشر - الفصل الدراسي الأول
@@ -6,14 +6,14 @@ unit_key: unit_02
lesson_key: unit_review
resource_type: unit_review
curriculum_version: jordan-grade10-2026-source-review
title: "مراجعة واختبار الوحدة الثانية: الوراثة والبيولوجيا الجزيئية"
title: "مراجعة واختبار الوحدة الثانية: الفيروسات والفيرويدات والبريونات"
source:
original_pdf: "كتاب الطالب لمادة العلوم الحياتية للصف العاشر الفصل الأول.pdf"
pages: [34, 35]
extraction_method: "text"
extraction_review_status: "needs_human_review"
extraction_review_status: "approved"
---
# مراجعة واختبار الوحدة الثانية: الوراثة والبيولوجيا الجزيئية
# مراجعة واختبار الوحدة الثانية: الفيروسات والفيرويدات والبريونات
## المصدر
- الكتاب: العلوم الحياتية - الصف العاشر - الفصل الدراسي الأول
+10 -10
View File
@@ -1641,43 +1641,43 @@
"name": "الفصل الدراسي الأول",
"units": {
"unit_01": {
"name": "الوحدة الأولى",
"name": "الوحدة الأولى: نظرية التطور (Evolution Theory)",
"lessons": [
{
"id": "lesson_01",
"title": "الدرس الأول: النقل عبر الغشاء البلازمي والخاصية الأسموزية والانتشار",
"title": "الدرس: تطور الكائنات الحية (Living Organisms Evolution)",
"file": "grade_10\/biology_10\/semester_1\/unit_01\/lesson_01.md",
"outcomes": [
"الدرس الأول: النقل عبر الغشاء البلازمي والخاصية الأسموزية والانتشار"
"الدرس: تطور الكائنات الحية (Living Organisms Evolution)"
]
},
{
"id": "unit_review",
"title": "مراجعة واختبار الوحدة الأولى: أنشطة الخلية",
"title": "مراجعة واختبار الوحدة الأولى: نظرية التطور",
"file": "grade_10\/biology_10\/semester_1\/unit_01\/unit_review.md",
"outcomes": [
"مراجعة واختبار الوحدة الأولى: أنشطة الخلية"
"مراجعة واختبار الوحدة الأولى: نظرية التطور"
]
}
]
},
"unit_02": {
"name": "الوحدة الثانية",
"name": "الوحدة الثانية: الفيروسات والفيرويدات والبريونات (Viruses, Viroids and Prions)",
"lessons": [
{
"id": "lesson_01",
"title": "الدرس الأول: المادة الوراثية وتضاعف الحمض النووي DNA وبناء البروتين",
"title": "الدرس 1: الفيروسات (Viruses)",
"file": "grade_10\/biology_10\/semester_1\/unit_02\/lesson_01.md",
"outcomes": [
"الدرس الأول: المادة الوراثية وتضاعف الحمض النووي DNA وبناء البروتين"
"الدرس 1: الفيروسات (Viruses)"
]
},
{
"id": "unit_review",
"title": "مراجعة واختبار الوحدة الثانية: الوراثة والبيولوجيا الجزيئية",
"title": "مراجعة واختبار الوحدة الثانية: الفيروسات والفيرويدات والبريونات",
"file": "grade_10\/biology_10\/semester_1\/unit_02\/unit_review.md",
"outcomes": [
"مراجعة واختبار الوحدة الثانية: الوراثة والبيولوجيا الجزيئية"
"مراجعة واختبار الوحدة الثانية: الفيروسات والفيرويدات والبريونات"
]
}
]