Update Saqel Platform: 2026-09-05 00:11:09

This commit is contained in:
Hamza-Ayed
2026-09-05 00:11:09 +03:00
parent e7bc20bc98
commit b0c2bfc03b
8 changed files with 2312 additions and 68 deletions
@@ -26,6 +26,8 @@ import '../../../logic/cubits/video_playback_cubit.dart';
import '../../widgets/luxury_widgets.dart';
import '../../widgets/socratic_dialog.dart';
import 'package:video_player/video_player.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../curriculum/math_interactive_lab_view.dart';
/// مشغل الفيديو السقراطي الذكي ونقاط الفحص والإرجاع العلاجي
class SocraticVideoPlayerScreen extends StatefulWidget {
@@ -55,6 +57,8 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
int _lastSyncedPosition = -1;
Timer? _controlsHideTimer;
bool _showVideoControls = true;
final FlutterTts _tts = FlutterTts();
bool _isSpeakingChalkboard = false;
void _revealVideoControls() {
if (!mounted) return;
@@ -65,6 +69,21 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
});
}
Future<void> _speakConcept(String text) async {
if (text.isEmpty) return;
setState(() => _isSpeakingChalkboard = true);
try {
await _tts.setLanguage('ar');
await _tts.setSpeechRate(0.48);
_tts.setCompletionHandler(() {
if (mounted) setState(() => _isSpeakingChalkboard = false);
});
await _tts.speak(text);
} catch (_) {
if (mounted) setState(() => _isSpeakingChalkboard = false);
}
}
@override
void initState() {
super.initState();
@@ -97,6 +116,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
@override
void dispose() {
_tts.stop();
_playbackTicker?.cancel();
_controlsHideTimer?.cancel();
_watermarkController.dispose();
@@ -319,30 +339,10 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
),
),
),
// Center Lesson Title & Visuals
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),
),
],
),
),
// Center Lesson Title & Visuals / Interactive Socratic Chalkboard
if (_videoInitError != null || !_isVideoInitialized || state.playbackData.videoUrl.isEmpty)
Positioned.fill(
child: _buildInteractiveChalkboard(state),
)
else if (_showVideoControls) Center(
child: Column(
@@ -772,6 +772,159 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
),
);
}
Widget _buildInteractiveChalkboard(VideoPlaybackReady state) {
final cp = state.playbackData.checkpoints.isNotEmpty ? state.playbackData.checkpoints.first : null;
final explanation = cp?.pedagogicalExplanation ??
'الدرس الوزاري المعتمد: يركز على إتقان المفاهيم الرياضية والعلمية وحل المسائل التحليلية خطوة بخطوة.';
final hint = cp?.hint ?? 'راجع خطوات الحل ونظم المتغيرات بدقة.';
final isMath = (widget.subject?.id ?? '').contains('math') || widget.lesson.title.contains('معادل') || widget.lesson.title.contains('رياضيات');
return Container(
padding: const EdgeInsets.all(20),
decoration: const BoxDecoration(
gradient: RadialGradient(
center: Alignment(0.0, -0.2),
radius: 1.2,
colors: [Color(0xFF0F2537), Color(0xFF070D18)],
),
),
child: Center(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 5),
decoration: BoxDecoration(
color: AppColors.saqelCyan.withAlpha(30),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.saqelCyan.withAlpha(90)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(CupertinoIcons.sparkles, color: AppColors.saqelCyan, size: 14),
SizedBox(width: 6),
Text(
'لوحة الشرح السقراطية التفاعلية 💡',
style: TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.w700),
),
],
),
),
const SizedBox(height: 12),
Text(
widget.lesson.title,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w800),
),
const SizedBox(height: 10),
Container(
constraints: const BoxConstraints(maxWidth: 600),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.black.withAlpha(120),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.white12),
),
child: Column(
children: [
Text(
explanation,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontSize: 13, height: 1.5),
),
const SizedBox(height: 8),
Text(
'💡 إرشاد ذهني: $hint',
textAlign: TextAlign.center,
style: const TextStyle(color: AppColors.guardianAmber, fontSize: 11, fontWeight: FontWeight.w600),
),
],
),
),
const SizedBox(height: 16),
Wrap(
alignment: WrapAlignment.center,
spacing: 10,
runSpacing: 8,
children: [
ElevatedButton.icon(
icon: Icon(_isSpeakingChalkboard ? CupertinoIcons.waveform : CupertinoIcons.volume_up, size: 16),
label: Text(_isSpeakingChalkboard ? 'جاري الاستماع...' : 'استمع للشرح الصوتي 🎧'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.saqelCyan,
foregroundColor: Colors.black,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
textStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () => _speakConcept('$explanation. $hint'),
),
if (cp != null)
ElevatedButton.icon(
icon: const Icon(CupertinoIcons.question_circle_fill, size: 16),
label: const Text('فحص الفهم السقراطي ⚡'),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.appleBlue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
textStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.w700),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (modalContext) => SocraticCheckpointModal(
checkpoint: cp,
onOptionSelected: (opt) {
Navigator.of(modalContext).pop();
context.read<VideoPlaybackCubit>().submitCheckpointAnswer(opt);
},
),
);
},
),
if (isMath)
OutlinedButton.icon(
icon: const Icon(CupertinoIcons.function, size: 16, color: AppColors.saqelCyan),
label: const Text('معمل جيوجبرا للتحقق بيانياً 📐', style: TextStyle(color: AppColors.saqelCyan, fontSize: 12, fontWeight: FontWeight.w700)),
style: OutlinedButton.styleFrom(
side: const BorderSide(color: AppColors.saqelCyan),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: AppColors.darkBackground,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (ctx) => const SizedBox(
height: 600,
child: MathInteractiveLabView(),
),
);
},
),
],
),
const SizedBox(height: 12),
const Text(
'فيديو المعلم قيد المزامنة الرقمية والتجهيز؛ تم تفعيل لوحة الشرح التفاعلية لضمان التعلم الفوري المستمر 🚀',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.textSecondaryDark, fontSize: 11),
),
],
),
),
),
);
}
}
class _GridPatternPainter extends CustomPainter {