Update Saqel Platform: 2026-09-05 00:11:09
This commit is contained in:
+177
-24
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user