Skip to content

Commit e5af6c8

Browse files
feat: support audio in quizzes (#1733)
* feat: support audio in quizzes * test code * Revert "test code" This reverts commit c3a9d33.
1 parent 942770f commit e5af6c8

9 files changed

Lines changed: 391 additions & 52 deletions

File tree

mobile-app/lib/models/learn/challenge_model.dart

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class Challenge {
6767

6868
// English Challenges
6969
final FillInTheBlank? fillInTheBlank;
70-
final EnglishAudio? audio;
70+
final EnglishScene? audio;
7171
final Scene? scene;
7272

7373
// Nodules for interactive challenges
@@ -125,7 +125,7 @@ class Challenge {
125125
? FillInTheBlank.fromJson(data['fillInTheBlank'])
126126
: null,
127127
audio: data['scene'] != null
128-
? EnglishAudio.fromJson(data['scene']['setup']['audio'])
128+
? EnglishScene.fromJson(data['scene']['setup']['audio'])
129129
: null,
130130
tests: (data['tests'] ?? [])
131131
.map<ChallengeTest>((file) => ChallengeTest.fromJson(file))
@@ -440,11 +440,13 @@ class QuizQuestion {
440440
final String text;
441441
final List<Answer> answers;
442442
final int solution;
443+
final QuizAudioData? audioData;
443444

444445
const QuizQuestion({
445446
required this.text,
446447
required this.answers,
447448
required this.solution,
449+
this.audioData,
448450
});
449451

450452
factory QuizQuestion.fromJson(Map<String, dynamic> data) {
@@ -466,7 +468,13 @@ class QuizQuestion {
466468
allAnswers.indexWhere((a) => a.answer == data['answer']) + 1;
467469

468470
return QuizQuestion(
469-
text: data['text'], answers: allAnswers, solution: solutionIndex);
471+
text: data['text'],
472+
answers: allAnswers,
473+
solution: solutionIndex,
474+
audioData: data['audioData'] != null
475+
? QuizAudioData.fromJson(data['audioData'])
476+
: null,
477+
);
470478
}
471479
}
472480

@@ -510,7 +518,7 @@ class Scene {
510518
class SceneSetup {
511519
final String background;
512520
final bool? alwaysShowDialogue;
513-
final EnglishAudio audio;
521+
final EnglishScene audio;
514522
final List<SceneCharacter> characters;
515523

516524
const SceneSetup({
@@ -523,7 +531,7 @@ class SceneSetup {
523531
factory SceneSetup.fromJson(Map<String, dynamic> data) {
524532
return SceneSetup(
525533
background: data['background'],
526-
audio: EnglishAudio.fromJson(data['audio']),
534+
audio: EnglishScene.fromJson(data['audio']),
527535
characters: data['characters']
528536
.map<SceneCharacter>(
529537
(character) => SceneCharacter.fromJson(character),
@@ -626,25 +634,96 @@ class SceneDialogue {
626634
}
627635
}
628636

629-
class EnglishAudio {
637+
abstract class AudioClip {
638+
String get fileName;
639+
String? get startTimeStamp;
640+
String? get finishTimeStamp;
641+
}
642+
643+
class EnglishScene implements AudioClip {
644+
@override
630645
final String fileName;
631646
final String startTime;
647+
@override
632648
final String? startTimeStamp;
649+
@override
633650
final String? finishTimeStamp;
634651

635-
const EnglishAudio({
652+
const EnglishScene({
636653
required this.fileName,
637654
required this.startTime,
638655
required this.startTimeStamp,
639656
required this.finishTimeStamp,
640657
});
641658

642-
factory EnglishAudio.fromJson(Map<String, dynamic> data) {
643-
return EnglishAudio(
659+
factory EnglishScene.fromJson(Map<String, dynamic> data) {
660+
return EnglishScene(
644661
fileName: data['filename'],
645662
startTime: data['startTime'].toString(),
646663
startTimeStamp: data['startTimestamp']?.toString(),
647664
finishTimeStamp: data['finishTimestamp']?.toString(),
648665
);
649666
}
650667
}
668+
669+
class QuizTranscriptLine {
670+
final String character;
671+
final String text;
672+
673+
const QuizTranscriptLine({
674+
required this.character,
675+
required this.text,
676+
});
677+
678+
factory QuizTranscriptLine.fromJson(Map<String, dynamic> data) {
679+
return QuizTranscriptLine(
680+
character: data['character'],
681+
text: data['text'],
682+
);
683+
}
684+
}
685+
686+
class QuizAudio implements AudioClip {
687+
@override
688+
final String fileName;
689+
@override
690+
final String? startTimeStamp;
691+
@override
692+
final String? finishTimeStamp;
693+
694+
const QuizAudio({
695+
required this.fileName,
696+
this.startTimeStamp,
697+
this.finishTimeStamp,
698+
});
699+
700+
factory QuizAudio.fromJson(Map<String, dynamic> data) {
701+
return QuizAudio(
702+
fileName: data['filename'],
703+
startTimeStamp: data['startTimestamp']?.toString(),
704+
finishTimeStamp: data['finishTimestamp']?.toString(),
705+
);
706+
}
707+
}
708+
709+
class QuizAudioData {
710+
final QuizAudio audio;
711+
final List<QuizTranscriptLine> transcript;
712+
713+
const QuizAudioData({
714+
required this.audio,
715+
required this.transcript,
716+
});
717+
718+
factory QuizAudioData.fromJson(Map<String, dynamic> data) {
719+
final audioData = data['audio'];
720+
return QuizAudioData(
721+
audio: QuizAudio.fromJson(audioData),
722+
transcript: (data['transcript'] as List)
723+
.map<QuizTranscriptLine>(
724+
(item) => QuizTranscriptLine.fromJson(item),
725+
)
726+
.toList(),
727+
);
728+
}
729+
}

mobile-app/lib/service/audio/audio_service.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ class AudioPlayerHandler extends BaseAudioHandler {
239239
return 'https://cdn.freecodecamp.org/curriculum/english/animation-assets/sounds/$fileName';
240240
}
241241

242-
bool canSeek(bool forward, int currentDuration, EnglishAudio audio) {
242+
bool canSeek(bool forward, int currentDuration, AudioClip audio) {
243243
currentDuration =
244244
currentDuration + parseTimeStamp(audio.startTimeStamp).inSeconds;
245245

@@ -252,23 +252,21 @@ class AudioPlayerHandler extends BaseAudioHandler {
252252
}
253253
}
254254

255-
void loadEnglishAudio(EnglishAudio audio) async {
255+
Future<void> loadCurriculumAudio(AudioClip audio) async {
256256
await _audioPlayer.setAudioSource(
257257
ClippingAudioSource(
258258
start: parseTimeStamp(audio.startTimeStamp),
259259
end: audio.finishTimeStamp == null
260260
? null
261261
: parseTimeStamp(audio.finishTimeStamp),
262262
child: AudioSource.uri(
263-
Uri.parse(
264-
returnUrl(audio.fileName),
265-
),
263+
Uri.parse(returnUrl(audio.fileName)),
266264
),
267265
),
268266
);
269267
await _audioPlayer.load();
270268
setEpisodeId = '';
271-
_audioType = 'english';
269+
_audioType = 'curriculum';
272270
}
273271

274272
void _notifyAudioHandlerAboutPlaybackEvents() {

mobile-app/lib/ui/views/learn/challenge/templates/quiz/quiz_viewmodel.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class QuizViewModel extends BaseViewModel {
5959
text: q.text,
6060
answers: q.answers,
6161
solution: q.solution,
62+
audioData: q.audioData,
6263
))
6364
.toList();
6465
}
@@ -114,6 +115,7 @@ class QuizViewModel extends BaseViewModel {
114115
text: q.text,
115116
answers: q.answers,
116117
solution: q.solution,
118+
audioData: q.audioData,
117119
))
118120
.toList();
119121

mobile-app/lib/ui/views/learn/widgets/audio/audio_player_view.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import 'package:stacked/stacked.dart';
88
class AudioPlayerView extends StatelessWidget {
99
const AudioPlayerView({super.key, required this.audio});
1010

11-
final EnglishAudio audio;
11+
final EnglishScene audio;
1212

1313
@override
1414
Widget build(BuildContext context) {
1515
return ViewModelBuilder<AudioPlayerViewmodel>.reactive(
1616
viewModelBuilder: () => AudioPlayerViewmodel(),
1717
onViewModelReady: (model) => {
1818
model.initPositionListener(),
19-
model.audioService.loadEnglishAudio(audio)
19+
model.audioService.loadCurriculumAudio(audio)
2020
},
2121
onDispose: (model) => model.onDispose(),
2222
builder: (context, model, child) => Padding(
@@ -48,7 +48,7 @@ class InnerAudioWidget extends StatelessWidget {
4848
});
4949

5050
final AudioPlayerViewmodel model;
51-
final EnglishAudio audio;
51+
final EnglishScene audio;
5252
final PlaybackState playerState;
5353

5454
@override

mobile-app/lib/ui/views/learn/widgets/audio/audio_player_viewmodel.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class AudioPlayerViewmodel extends BaseViewModel {
1717
Duration searchTimeStamp(
1818
bool forwards,
1919
int currentPosition,
20-
EnglishAudio audio,
20+
EnglishScene audio,
2121
) {
2222
if (forwards) {
2323
return Duration(

0 commit comments

Comments
 (0)