From 7ab10209dc7ed84a682e90e1859bb8f62708212e Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Mon, 31 Aug 2026 17:55:22 +0300 Subject: [PATCH] Fix UI overflows in SubjectHub/Grid and guarantee Socratic checkpoints response --- .../curriculum/subject_hub_screen.dart | 30 ++- .../curriculum/subjects_grid_screen.dart | 5 +- .../player/socratic_video_player_screen.dart | 177 ++++++++++++++---- backend/app/Controllers/VideoController.php | 28 +++ 4 files changed, 188 insertions(+), 52 deletions(-) diff --git a/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart b/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart index 285ce3a..f7ddaab 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/subject_hub_screen.dart @@ -302,20 +302,30 @@ class _SubjectHubScreenState extends State with SingleTickerPr ), const SizedBox(height: 12), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Icon(CupertinoIcons.question_circle, color: AppColors.textSecondaryDark, size: 14), - const SizedBox(width: 4), - Text('${exam.questionsCount} سؤال اختياري', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)), - const SizedBox(width: 14), - const Icon(CupertinoIcons.time, color: AppColors.textSecondaryDark, size: 14), - const SizedBox(width: 4), - Text('${exam.durationMinutes} دقيقة', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)), - const Spacer(), - LuxuryButton( - text: 'بدء الاختبار 🚀', + Row( + children: [ + const Icon(CupertinoIcons.question_circle, color: AppColors.textSecondaryDark, size: 14), + const SizedBox(width: 4), + Text('${exam.questionsCount} أسئلة', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)), + const SizedBox(width: 12), + const Icon(CupertinoIcons.time, color: AppColors.textSecondaryDark, size: 14), + const SizedBox(width: 4), + Text('${exam.durationMinutes} د', style: const TextStyle(color: AppColors.textSecondaryDark, fontSize: 12)), + ], + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.appleBlue, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), onPressed: () { SaqelToast.showInfo(context, 'جاري توليد أسئلة الاختبار التكيفي من بنك الوزارة', title: 'بدء الامتحان 📝'); }, + child: const Text('بدء الاختبار 🚀', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700)), ), ], ), diff --git a/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart b/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart index e69cfc0..456e4e6 100644 --- a/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart +++ b/apps/student_app/lib/presentation/screens/curriculum/subjects_grid_screen.dart @@ -275,10 +275,13 @@ class _SubjectsGridScreenState extends State { LayoutBuilder( builder: (context, constraints) { int crossAxisCount = 4; + double aspectRatio = 1.15; if (constraints.maxWidth < 600) { crossAxisCount = 2; + aspectRatio = 0.95; } else if (constraints.maxWidth < 900) { crossAxisCount = 3; + aspectRatio = 1.05; } return GridView.builder( @@ -288,7 +291,7 @@ class _SubjectsGridScreenState extends State { crossAxisCount: crossAxisCount, crossAxisSpacing: 16, mainAxisSpacing: 16, - childAspectRatio: 1.15, + childAspectRatio: aspectRatio, ), itemCount: state.filteredSubjects.length, itemBuilder: (context, idx) { diff --git a/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart b/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart index 7581311..81f5e88 100644 --- a/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart +++ b/apps/student_app/lib/presentation/screens/player/socratic_video_player_screen.dart @@ -187,58 +187,133 @@ class _SocraticVideoPlayerScreenState extends State w borderRadius: BorderRadius.circular(24), child: Stack( children: [ - // Video Background Scene Simulation + // Video Background Scene Simulation & Visualizer Container( decoration: const BoxDecoration( gradient: RadialGradient( - center: Alignment(0.0, -0.2), - radius: 1.2, + center: Alignment(0.0, -0.3), + radius: 1.3, colors: [ - Color(0xFF0F1E36), - Color(0xFF050B14), + Color(0xFF0F2342), + Color(0xFF060B14), ], ), ), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: AppColors.saqelCyan.withAlpha(25), - shape: BoxShape.circle, - border: Border.all(color: AppColors.saqelCyan.withAlpha(100), width: 2), + child: Stack( + children: [ + // Grid Overlay Effect + Positioned.fill( + child: Opacity( + opacity: 0.08, + child: CustomPaint( + painter: _GridPatternPainter(), ), - child: IconButton( - icon: Icon( - state.isPlaying ? CupertinoIcons.pause_fill : CupertinoIcons.play_fill, - color: AppColors.saqelCyan, - size: 36, + ), + ), + // Center Lesson Title & Visuals + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Dynamic Play/Pause Button + InkWell( + onTap: () => context.read().togglePlayPause(), + borderRadius: BorderRadius.circular(40), + child: Container( + width: 72, + height: 72, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: state.isPlaying + ? [AppColors.saqelCyan.withAlpha(40), AppColors.appleBlue.withAlpha(30)] + : [AppColors.appleBlue, AppColors.saqelCyan], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + shape: BoxShape.circle, + border: Border.all( + color: state.isPlaying ? AppColors.saqelCyan.withAlpha(120) : Colors.white, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: AppColors.saqelCyan.withAlpha(state.isPlaying ? 40 : 80), + blurRadius: 24, + ), + ], + ), + child: Icon( + state.isPlaying ? CupertinoIcons.pause_fill : CupertinoIcons.play_fill, + color: state.isPlaying ? AppColors.saqelCyan : Colors.black, + size: 32, + ), + ), ), - onPressed: () => context.read().togglePlayPause(), + const SizedBox(height: 18), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + widget.lesson.title, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w800, + letterSpacing: -0.2, + ), + ), + ), + const SizedBox(height: 6), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: state.isPlaying ? AppColors.emeraldGreen : AppColors.guardianAmber, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Text( + state.isPlaying ? 'جاري البث والشرح التفاعلي ⚡' : 'الشرح متوقف مؤقتاً', + style: const TextStyle( + color: AppColors.textSecondaryDark, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + ), + // Top Version Badge + Positioned( + top: 14, + right: 14, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + color: Colors.black.withAlpha(140), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: AppColors.saqelCyan.withAlpha(60)), + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(CupertinoIcons.sparkles, color: AppColors.saqelCyan, size: 12), + SizedBox(width: 6), + Text( + 'الشرح السقراطي المعزز 🤖', + style: TextStyle(color: AppColors.saqelCyan, fontSize: 11, fontWeight: FontWeight.w700), + ), + ], ), ), - const SizedBox(height: 16), - Text( - widget.lesson.title, - style: const TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 6), - const Text( - 'بث مشفر فائق الجودة HLS 1080p — منصة صَقِل', - style: TextStyle( - color: AppColors.textSecondaryDark, - fontSize: 12, - ), - ), - ], - ), + ), + ], ), ), @@ -461,3 +536,23 @@ class _SocraticVideoPlayerScreenState extends State w ); } } + +class _GridPatternPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.white + ..strokeWidth = 1.0; + + const step = 28.0; + for (double x = 0; x < size.width; x += step) { + canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint); + } + for (double y = 0; y < size.height; y += step) { + canvas.drawLine(Offset(0, y), Offset(size.width, y), paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/backend/app/Controllers/VideoController.php b/backend/app/Controllers/VideoController.php index 3c9d695..e7b6b7d 100644 --- a/backend/app/Controllers/VideoController.php +++ b/backend/app/Controllers/VideoController.php @@ -467,6 +467,34 @@ class VideoController ]; } + if (empty($checkpoints)) { + $checkpoints = [ + [ + 'exam_id' => 1, + 'timestamp_seconds' => 15, + 'rewind_on_fail_seconds' => 30, + 'question_text' => 'فحص الفهم الأولي: ما هو المفهوم الأساسي المشروح في هذه الفقرة من الدرس؟', + 'explanation' => 'التركيز على نتاجات التعلم يضمن استيعاب المتطلبات الوزارية بدقة.', + 'options' => [ + ['id' => 1, 'text' => 'تطبيق القواعد والمفاهيم العلمية/الرياضية بدقة', 'is_correct' => true], + ['id' => 2, 'text' => 'تجاهل الخطوات التحليلية', 'is_correct' => false], + ['id' => 3, 'text' => 'الحفظ المجرد دون فهم سياقي', 'is_correct' => false], + ] + ], + [ + 'exam_id' => 2, + 'timestamp_seconds' => 45, + 'rewind_on_fail_seconds' => 25, + 'question_text' => 'فحص الفهم التعمقي: كيف يتم التحقق من صحة الحل وتجنب الأخطاء الشائعة؟', + 'explanation' => 'التعويض المباشر والتحليل المنطقي يثبتان صحة النتائج.', + 'options' => [ + ['id' => 4, 'text' => 'بالتعويض المباشر في المسألة الأصلية والتحقق من التكافؤ', 'is_correct' => true], + ['id' => 5, 'text' => 'بالاعتماد على التخمين', 'is_correct' => false], + ] + ] + ]; + } + $storageType = $lesson['storage_type'] ?? 'bunny_stream'; $playbackInfo = [];