Update Saqel Platform: 2026-09-11 20:37:10
This commit is contained in:
+210
-89
@@ -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,104 +843,222 @@ 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: Padding(
|
||||
padding: const EdgeInsets.all(22),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
child: Column(
|
||||
children: [
|
||||
LuxuryCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(22),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.appleBlue.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: const Icon(CupertinoIcons.book_fill, color: AppColors.saqelCyan, size: 36),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
widget.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w800),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.emeraldGreen.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.emeraldGreen.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: const Text(
|
||||
'كتاب رسمي معتمد — وزارة التربية والتعليم الأردنية',
|
||||
style: TextStyle(color: AppColors.emeraldGreen, fontSize: 12, fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'المقرر الدراسي المعتمد لمادة ${widget.subjectTitle} (الصف العاشر الأساسي). تم فحص وتوثيق سلامة المحتوى وحقوق النشر والتكامل الرقمي مع خطة المنهاج الوزاري.',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 13.5, height: 1.6),
|
||||
),
|
||||
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)),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.list_bullet, size: 18),
|
||||
label: const Text(
|
||||
'العودة لدروس المادة',
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
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 ?? "نسخة رسمية"})',
|
||||
title: 'الكتاب المدرسي المعتمد',
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.checkmark_seal_fill, size: 18),
|
||||
label: const Text(
|
||||
'معتمد رسمياً ✅',
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 64,
|
||||
height: 64,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.appleBlue.withAlpha(30),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: const Icon(CupertinoIcons.book_fill, color: AppColors.saqelCyan, size: 36),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
widget.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w800),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.emeraldGreen.withAlpha(25),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.emeraldGreen.withAlpha(60)),
|
||||
),
|
||||
child: const Text(
|
||||
'كتاب رسمي معتمد — وزارة التربية والتعليم الأردنية',
|
||||
style: TextStyle(color: AppColors.emeraldGreen, fontSize: 12, fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'المقرر الدراسي المعتمد لمادة ${widget.subjectTitle} (الصف العاشر الأساسي). تم فحص وتوثيق سلامة المحتوى وحقوق النشر والتكامل الرقمي مع خطة المنهاج الوزاري.',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 13.5, height: 1.6),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.appleBlue,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.list_bullet, size: 18),
|
||||
label: const Text(
|
||||
'تصفح دروس ووحدات الكتاب',
|
||||
style: TextStyle(fontWeight: FontWeight.w800, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.saqelCyan,
|
||||
side: BorderSide(color: AppColors.saqelCyan.withOpacity(0.5)),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
onPressed: () {
|
||||
SaqelToast.showSuccess(
|
||||
context,
|
||||
'الأصل الوزاري منشور ومطابق للمنهاج (${widget.assetId ?? "نسخة رسمية"})',
|
||||
title: 'الكتاب المدرسي المعتمد',
|
||||
);
|
||||
},
|
||||
icon: const Icon(CupertinoIcons.checkmark_seal_fill, size: 18),
|
||||
label: const Text(
|
||||
'اعتماد وزارة التربية والتعليم ✅',
|
||||
style: TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+1034
-23
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)';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user