fix(platform): grade-gate student app, isolate per-video resume & checkpoints, remove fake teachers, enhance arabic lab, clean flutter analyze
This commit is contained in:
@@ -76,19 +76,34 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
: _repo = repo ?? CurriculumRepository(),
|
||||
super(VideoPlaybackInitial());
|
||||
|
||||
String _getLessonStorageKey(CurriculumLessonItemModel? lesson, {String? fallbackId}) {
|
||||
if (lesson?.markdownFilePath != null && lesson!.markdownFilePath!.isNotEmpty) {
|
||||
return lesson.markdownFilePath!.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
||||
}
|
||||
if (lesson?.id != null && lesson!.id.isNotEmpty) {
|
||||
return lesson.id.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_');
|
||||
}
|
||||
return fallbackId ?? 'lesson_default';
|
||||
}
|
||||
|
||||
Future<void> loadLesson(CurriculumLessonItemModel lesson, {SubjectModel? subject}) async {
|
||||
AppLogger.log('Loading Socratic playback for lesson: ${lesson.title}', tag: 'VIDEO_CUBIT');
|
||||
_lastObservedPosition = -1;
|
||||
emit(VideoPlaybackLoading());
|
||||
final storageKey = _getLessonStorageKey(lesson);
|
||||
|
||||
try {
|
||||
final playback = await _repo.getLessonPlayback(lesson.markdownFilePath ?? lesson.id, subjectId: subject?.id);
|
||||
|
||||
// Load saved resume position
|
||||
// Load saved resume position strictly per video
|
||||
int resumePos = playback.lastPositionSeconds ?? 0;
|
||||
Set<int> passedIds = {};
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final localPos = prefs.getInt('saved_video_pos_${lesson.id}') ?? 0;
|
||||
final localPos = prefs.getInt('saved_video_pos_$storageKey') ?? 0;
|
||||
if (localPos > resumePos) resumePos = localPos;
|
||||
final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? [];
|
||||
passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet();
|
||||
} catch (_) {}
|
||||
|
||||
emit(VideoPlaybackReady(
|
||||
@@ -96,18 +111,22 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
lessonItem: lesson,
|
||||
subject: subject,
|
||||
currentPositionSeconds: resumePos,
|
||||
passedCheckpointIds: passedIds,
|
||||
isPlaying: true,
|
||||
));
|
||||
} catch (e) {
|
||||
AppLogger.log('Playback API unavailable (${lesson.title}): $e — Switching to offline Socratic fallback', tag: 'VIDEO_CUBIT');
|
||||
final fallback = _buildResilientLessonPlayback(lesson, subject: subject);
|
||||
|
||||
// Load saved resume position
|
||||
// Load saved resume position strictly per video
|
||||
int resumePos = fallback.lastPositionSeconds ?? 0;
|
||||
Set<int> passedIds = {};
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final localPos = prefs.getInt('saved_video_pos_${lesson.id}') ?? 0;
|
||||
final localPos = prefs.getInt('saved_video_pos_$storageKey') ?? 0;
|
||||
if (localPos > resumePos) resumePos = localPos;
|
||||
final savedPassed = prefs.getStringList('passed_checkpoints_$storageKey') ?? [];
|
||||
passedIds = savedPassed.map((s) => int.tryParse(s) ?? 0).where((id) => id > 0).toSet();
|
||||
} catch (_) {}
|
||||
|
||||
emit(VideoPlaybackReady(
|
||||
@@ -115,6 +134,7 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
lessonItem: lesson,
|
||||
subject: subject,
|
||||
currentPositionSeconds: resumePos,
|
||||
passedCheckpointIds: passedIds,
|
||||
isPlaying: true,
|
||||
));
|
||||
}
|
||||
@@ -197,21 +217,12 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
LessonVersionModel(
|
||||
lessonId: 101,
|
||||
isAi: true,
|
||||
teacherName: 'منصة صَقِل الذكية 🤖',
|
||||
teacherName: 'منصة صَقِل التعليمية المعتمدة',
|
||||
schoolName: 'المركز الرقمي المعتمد',
|
||||
label: 'شرح الذكاء الاصطناعي الرسمي',
|
||||
label: 'الشرح الرقمي الرسمي المعتمد',
|
||||
isRecommended: true,
|
||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
||||
),
|
||||
LessonVersionModel(
|
||||
lessonId: 102,
|
||||
isAi: false,
|
||||
teacherName: 'أ. أحمد المجالي 👨🏫',
|
||||
schoolName: 'مدارس الثقافة العسكرية',
|
||||
label: 'شرح معلم معتمد 🌟 4.9',
|
||||
isRecommended: false,
|
||||
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -279,12 +290,16 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
final currentState = state;
|
||||
if (currentState is! VideoPlaybackReady) return;
|
||||
try {
|
||||
final storageKey = _getLessonStorageKey(currentState.lessonItem, fallbackId: '${currentState.playbackData.lessonId}');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (currentState.lessonItem != null) {
|
||||
await prefs.setInt('saved_video_pos_${currentState.lessonItem!.id}', positionSeconds);
|
||||
await prefs.setInt('saved_video_pos_$storageKey', positionSeconds);
|
||||
if (currentState.passedCheckpointIds.isNotEmpty) {
|
||||
await prefs.setStringList(
|
||||
'passed_checkpoints_$storageKey',
|
||||
currentState.passedCheckpointIds.map((id) => id.toString()).toList(),
|
||||
);
|
||||
}
|
||||
if (currentState.playbackData.lessonId > 0) {
|
||||
await prefs.setInt('saved_video_pos_${currentState.playbackData.lessonId}', positionSeconds);
|
||||
await _repo.saveProgress(
|
||||
lessonId: currentState.playbackData.lessonId,
|
||||
positionSeconds: positionSeconds,
|
||||
@@ -307,6 +322,13 @@ class VideoPlaybackCubit extends Cubit<VideoPlaybackState> {
|
||||
AppLogger.log('✅ Correct answer! Rewarding +0.5% readiness.', tag: 'SOCRATIC_ENGINE');
|
||||
final updatedPassed = Set<int>.from(currentState.passedCheckpointIds)..add(cp.id);
|
||||
_lastObservedPosition = cp.timestampSeconds;
|
||||
|
||||
// Persist passed checkpoint for this specific video
|
||||
final storageKey = _getLessonStorageKey(currentState.lessonItem, fallbackId: '${currentState.playbackData.lessonId}');
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
prefs.setStringList('passed_checkpoints_$storageKey', updatedPassed.map((id) => id.toString()).toList());
|
||||
}).catchError((_) {});
|
||||
|
||||
emit(currentState.copyWith(
|
||||
clearActiveCheckpoint: true,
|
||||
isPlaying: true,
|
||||
|
||||
Reference in New Issue
Block a user