Update Saqel Platform: 2026-09-02 09:06:45
This commit is contained in:
@@ -69,6 +69,7 @@ class VideoPlaybackError extends VideoPlaybackState {
|
||||
|
||||
class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
final CurriculumRepository _repo;
|
||||
int _lastObservedPosition = -1;
|
||||
|
||||
VideoPlaybackCubit({CurriculumRepository? repo})
|
||||
: _repo = repo ?? CurriculumRepository(),
|
||||
@@ -76,6 +77,7 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
|
||||
Future<void> loadLesson(CurriculumLessonItemModel lesson, {SubjectModel? subject}) async {
|
||||
AppLogger.log('Loading Socratic playback for lesson: ${lesson.title}', tag: 'VIDEO_CUBIT');
|
||||
_lastObservedPosition = -1;
|
||||
emit(VideoPlaybackLoading());
|
||||
try {
|
||||
final playback = await _repo.getLessonPlayback(lesson.markdownFilePath ?? lesson.id, subjectId: subject?.id);
|
||||
@@ -97,9 +99,9 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
if (currentState is VideoPlaybackReady && currentState.activeCheckpoint == null) {
|
||||
// Check if this second triggers a Socratic checkpoint
|
||||
for (var cp in currentState.playbackData.checkpoints) {
|
||||
if (!currentState.passedCheckpointIds.contains(cp.id) &&
|
||||
seconds >= cp.timestampSeconds &&
|
||||
seconds <= cp.timestampSeconds + 2) {
|
||||
final crossedCheckpoint = seconds >= cp.timestampSeconds &&
|
||||
(_lastObservedPosition < cp.timestampSeconds || _lastObservedPosition > seconds);
|
||||
if (!currentState.passedCheckpointIds.contains(cp.id) && crossedCheckpoint) {
|
||||
AppLogger.log('🚨 Socratic Checkpoint Triggered! (${cp.questionText})', tag: 'SOCRATIC_ENGINE');
|
||||
emit(currentState.copyWith(
|
||||
currentPositionSeconds: seconds,
|
||||
@@ -110,6 +112,7 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
}
|
||||
}
|
||||
|
||||
_lastObservedPosition = seconds;
|
||||
emit(currentState.copyWith(currentPositionSeconds: seconds));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,17 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
VideoPlayerController? _videoController;
|
||||
bool _isVideoInitialized = false;
|
||||
int _lastSyncedPosition = -1;
|
||||
Timer? _controlsHideTimer;
|
||||
bool _showVideoControls = true;
|
||||
|
||||
void _revealVideoControls() {
|
||||
if (!mounted) return;
|
||||
setState(() => _showVideoControls = true);
|
||||
_controlsHideTimer?.cancel();
|
||||
_controlsHideTimer = Timer(const Duration(seconds: 2), () {
|
||||
if (mounted) setState(() => _showVideoControls = false);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -66,6 +77,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
@override
|
||||
void dispose() {
|
||||
_playbackTicker?.cancel();
|
||||
_controlsHideTimer?.cancel();
|
||||
_watermarkController.dispose();
|
||||
_videoController?.dispose();
|
||||
super.dispose();
|
||||
@@ -131,6 +143,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
setState(() {
|
||||
_isVideoInitialized = true;
|
||||
});
|
||||
_revealVideoControls();
|
||||
if (state.isPlaying && state.activeCheckpoint == null) {
|
||||
_videoController!.play();
|
||||
}
|
||||
@@ -220,7 +233,12 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
child: MouseRegion(
|
||||
onHover: (_) => _revealVideoControls(),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: _revealVideoControls,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
child: Stack(
|
||||
children: [
|
||||
@@ -263,7 +281,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
),
|
||||
),
|
||||
// Center Lesson Title & Visuals
|
||||
Center(
|
||||
if (_showVideoControls) Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -342,7 +360,7 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
),
|
||||
),
|
||||
// Top Version Badge
|
||||
Positioned(
|
||||
if (_showVideoControls) Positioned(
|
||||
top: 14,
|
||||
right: 14,
|
||||
child: Container(
|
||||
@@ -397,8 +415,13 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
},
|
||||
),
|
||||
|
||||
// Video Controls Overlay (Bottom)
|
||||
Align(
|
||||
// Video Controls Overlay (Bottom), hidden after inactivity.
|
||||
IgnorePointer(
|
||||
ignoring: !_showVideoControls,
|
||||
child: AnimatedOpacity(
|
||||
opacity: _showVideoControls ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 220),
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
@@ -498,11 +521,15 @@ class _SocraticVideoPlayerScreenState extends State<SocraticVideoPlayerScreen> w
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Remediation Feedback Alert (if triggered)
|
||||
|
||||
@@ -53,7 +53,7 @@ class _SocraticCheckpointModalState extends State<SocraticCheckpointModal> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Top Badge: Socratic Gatekeeping Indicator
|
||||
// Top Badge: understanding checkpoint indicator
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -70,7 +70,7 @@ class _SocraticCheckpointModalState extends State<SocraticCheckpointModal> {
|
||||
Icon(CupertinoIcons.sparkles, color: AppColors.saqelCyan, size: 14),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'فحص الفهم السقراطي الذكي 🧠',
|
||||
'نقطة فهم 🧠',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
|
||||
Reference in New Issue
Block a user