176 lines
5.8 KiB
Dart
176 lines
5.8 KiB
Dart
import '../../core/config/app_config.dart';
|
|
|
|
String _absolutePlaybackUrl(String value) {
|
|
if (value.isEmpty || value.startsWith('http://') || value.startsWith('https://')) return value;
|
|
final base = AppConfig.baseUrl.replaceAll(RegExp(r'/+$'), '');
|
|
return '$base/${value.replaceFirst(RegExp(r'^/+'), '')}';
|
|
}
|
|
|
|
/// Model representing an In-Video Socratic Gatekeeping Checkpoint (فحص الفهم السقراطي)
|
|
class SocraticCheckpointModel {
|
|
final int id;
|
|
final int questionId;
|
|
final String questionText;
|
|
final int timestampSeconds;
|
|
final int rewindSecondsOnFail;
|
|
final String hint;
|
|
final String pedagogicalExplanation;
|
|
final List<SocraticOptionModel> options;
|
|
|
|
const SocraticCheckpointModel({
|
|
required this.id,
|
|
this.questionId = 0,
|
|
required this.questionText,
|
|
required this.timestampSeconds,
|
|
this.rewindSecondsOnFail = 45,
|
|
this.hint = '',
|
|
this.pedagogicalExplanation = '',
|
|
this.options = const [],
|
|
});
|
|
|
|
factory SocraticCheckpointModel.fromJson(Map<String, dynamic> json) {
|
|
final List<SocraticOptionModel> parsedOptions = [];
|
|
if (json['options'] != null && json['options'] is List) {
|
|
for (var opt in json['options']) {
|
|
if (opt is Map) {
|
|
parsedOptions.add(SocraticOptionModel.fromJson(Map<String, dynamic>.from(opt)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return SocraticCheckpointModel(
|
|
id: json['id'] is num
|
|
? (json['id'] as num).toInt()
|
|
: int.tryParse((json['id'] ?? json['exam_id'] ?? '0').toString()) ?? 0,
|
|
questionId: json['question_id'] is num
|
|
? (json['question_id'] as num).toInt()
|
|
: int.tryParse(json['question_id']?.toString() ?? '0') ?? 0,
|
|
questionText: json['question']?.toString() ?? json['question_text']?.toString() ?? 'سؤال فحص الفهم السقراطي',
|
|
timestampSeconds: (json['timestamp_seconds'] as num?)?.toInt() ?? 120,
|
|
rewindSecondsOnFail: (json['rewind_seconds'] as num?)?.toInt() ?? 45,
|
|
hint: json['hint']?.toString() ?? '',
|
|
pedagogicalExplanation: json['explanation']?.toString() ?? '',
|
|
options: parsedOptions,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Model representing a single choice option in a checkpoint
|
|
class SocraticOptionModel {
|
|
final int id;
|
|
final String text;
|
|
final bool isCorrect;
|
|
|
|
const SocraticOptionModel({
|
|
required this.id,
|
|
required this.text,
|
|
required this.isCorrect,
|
|
});
|
|
|
|
factory SocraticOptionModel.fromJson(Map<String, dynamic> json) {
|
|
return SocraticOptionModel(
|
|
id: (json['id'] as num?)?.toInt() ?? 0,
|
|
text: json['text']?.toString() ?? json['option_text']?.toString() ?? '',
|
|
isCorrect: (json['is_correct'] as bool?) ?? false,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Model for Lesson Playback Data (Streams, Versions, Chapters, Checkpoints)
|
|
class LessonPlaybackData {
|
|
final int lessonId;
|
|
final String title;
|
|
final int durationSeconds;
|
|
final String videoUrl;
|
|
final String storageType;
|
|
final List<LessonVersionModel> availableVersions;
|
|
final List<SocraticCheckpointModel> checkpoints;
|
|
|
|
const LessonPlaybackData({
|
|
required this.lessonId,
|
|
required this.title,
|
|
required this.durationSeconds,
|
|
required this.videoUrl,
|
|
this.storageType = 'api_upload',
|
|
this.availableVersions = const [],
|
|
this.checkpoints = const [],
|
|
});
|
|
|
|
factory LessonPlaybackData.fromJson(Map<String, dynamic> json) {
|
|
final lesson = json['lesson'] is Map ? json['lesson'] as Map : {};
|
|
final playback = json['playback'] is Map ? json['playback'] as Map : {};
|
|
|
|
// Parse versions
|
|
final List<LessonVersionModel> versions = [];
|
|
if (json['available_versions'] is List) {
|
|
for (var v in json['available_versions']) {
|
|
if (v is Map) {
|
|
versions.add(LessonVersionModel.fromJson(Map<String, dynamic>.from(v)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse checkpoints
|
|
final List<SocraticCheckpointModel> points = [];
|
|
if (json['checkpoints'] is List) {
|
|
for (var c in json['checkpoints']) {
|
|
if (c is Map) {
|
|
points.add(SocraticCheckpointModel.fromJson(Map<String, dynamic>.from(c)));
|
|
}
|
|
}
|
|
}
|
|
|
|
final vidUrl = _absolutePlaybackUrl(
|
|
playback['hls_url']?.toString() ?? playback['stream_url']?.toString() ?? playback['video_url']?.toString() ?? '',
|
|
);
|
|
|
|
return LessonPlaybackData(
|
|
lessonId: (lesson['id'] as num?)?.toInt() ?? 0,
|
|
title: lesson['title']?.toString() ?? 'الدرس التفاعلي',
|
|
durationSeconds: (lesson['duration_seconds'] as num?)?.toInt() ?? 0,
|
|
videoUrl: vidUrl,
|
|
storageType: playback['storage_type']?.toString() ?? 'api_upload',
|
|
availableVersions: versions,
|
|
checkpoints: points,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Model for Teacher vs AI Lesson versions
|
|
class LessonVersionModel {
|
|
final int lessonId;
|
|
final bool isAi;
|
|
final String teacherName;
|
|
final String schoolName;
|
|
final String label;
|
|
final bool isRecommended;
|
|
final String videoUrl;
|
|
|
|
const LessonVersionModel({
|
|
required this.lessonId,
|
|
required this.isAi,
|
|
required this.teacherName,
|
|
required this.schoolName,
|
|
required this.label,
|
|
required this.isRecommended,
|
|
required this.videoUrl,
|
|
});
|
|
|
|
factory LessonVersionModel.fromJson(Map<String, dynamic> json) {
|
|
final playback = json['playback'] is Map ? json['playback'] as Map : {};
|
|
final vidUrl = _absolutePlaybackUrl(
|
|
playback['hls_url']?.toString() ?? playback['stream_url']?.toString() ?? playback['video_url']?.toString() ?? '',
|
|
);
|
|
|
|
return LessonVersionModel(
|
|
lessonId: (json['lesson_id'] as num?)?.toInt() ?? 0,
|
|
isAi: (json['is_ai'] as bool?) ?? false,
|
|
teacherName: json['teacher_name']?.toString() ?? '',
|
|
schoolName: json['school_name']?.toString() ?? '',
|
|
label: json['label']?.toString() ?? 'شرح معتمد',
|
|
isRecommended: (json['is_recommended'] as bool?) ?? false,
|
|
videoUrl: vidUrl,
|
|
);
|
|
}
|
|
}
|