Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c2f17cc
feat: english
Sembauke Dec 5, 2024
3e680ed
feat: a bunch stuff
Sembauke Dec 6, 2024
b93c7e1
fix: rename odin to multiple choice
Sembauke Dec 6, 2024
c20b678
fix: challenge types for English
Sembauke Dec 6, 2024
42902a0
fix: give intructions a proper background
Sembauke Dec 6, 2024
34efd59
feat: controllable inputs
Sembauke Dec 9, 2024
90d5761
fix: show words directly before and after BLANK
Sembauke Dec 9, 2024
485804f
feat: check input answers with dynamic input borders
Sembauke Dec 9, 2024
19aee92
feat: go to next challenge when completed
Sembauke Dec 9, 2024
f66f258
fix: get step numbering correct for English tasks
Sembauke Dec 10, 2024
d00e588
fix: simplify titles for English and MCQ view
Sembauke Dec 10, 2024
b2cf617
feat: audio widget
Sembauke Dec 12, 2024
68b60cb
feat: add audio element to MCQ and style it
Sembauke Dec 12, 2024
73197a1
fix: select grid widget for the rest of the superblocks
Sembauke Dec 12, 2024
9356707
fix: mutliple small issues
Sembauke Dec 12, 2024
6cd5dba
fix: do not init editor files on non editor challenges
Sembauke Dec 12, 2024
2618445
feat: add feedback widget
Sembauke Dec 12, 2024
e5065d4
fix: give dialogue header some margin
Sembauke Dec 12, 2024
dc26c46
feat: add Niraj's suggestions
Sembauke Dec 16, 2024
e620d58
fix: save initial value
Sembauke Dec 16, 2024
e6338a3
fix: put button at bottom and use audio service
Sembauke Dec 16, 2024
b1dbaa7
fix: title and replace paragraph on the last array index
Sembauke Dec 16, 2024
6e0bd38
fix: stop audio if english challenge is closed
Nirajn2311 Dec 18, 2024
30f8613
fix: temp fix to prevent UI bug in MCQ challenges
Nirajn2311 Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mobile-app/devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
101 changes: 85 additions & 16 deletions mobile-app/lib/models/learn/challenge_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,33 @@ class Challenge {
final List<ChallengeTest> tests;
final List<ChallengeFile> files;

// English Challenges
final FillInTheBlank? fillInTheBlank;
final EnglishAudio? audio;

// Challenge Type 11 - Video
// TODO: Renamed to questions and its an array of questions
Question? question;

// Challenge Type 15 - Odin
final List<String>? assignments;

Challenge({
required this.id,
required this.block,
required this.title,
required this.description,
required this.instructions,
required this.dashedName,
required this.superBlock,
this.videoId,
required this.challengeType,
required this.tests,
required this.files,
this.question,
this.assignments,
});
Challenge(
{required this.id,
required this.block,
required this.title,
required this.description,
required this.instructions,
required this.dashedName,
required this.superBlock,
this.videoId,
required this.challengeType,
required this.tests,
required this.files,
this.question,
this.assignments,
this.fillInTheBlank,
this.audio});

factory Challenge.fromJson(Map<String, dynamic> data) {
return Challenge(
Expand All @@ -71,6 +76,12 @@ class Challenge {
superBlock: data['superBlock'],
videoId: data['videoId'],
challengeType: data['challengeType'],
fillInTheBlank: data['fillInTheBlank'] != null
? FillInTheBlank.fromJson(data['fillInTheBlank'])
: null,
audio: data['scene'] != null
? EnglishAudio.fromJson(data['scene']['setup']['audio'])
: null,
tests: (data['tests'] ?? [])
.map<ChallengeTest>((file) => ChallengeTest.fromJson(file))
.toList(),
Expand Down Expand Up @@ -145,7 +156,9 @@ class Question {
return Question(
text: data['text'],
answers: (data['answers'] ?? [])
.map<Answer>((answer) => Answer.fromJson(answer))
.map<Answer>(
(answer) => Answer.fromJson(answer),
)
.toList(),
solution: data['solution'],
);
Expand Down Expand Up @@ -220,3 +233,59 @@ class ChallengeFile {
);
}
}

class FillInTheBlank {
const FillInTheBlank({required this.sentence, required this.blanks});
final String sentence;
final List<Blank> blanks;

factory FillInTheBlank.fromJson(Map<String, dynamic> data) {
return FillInTheBlank(
sentence: data['sentence'],
blanks: data['blanks']
.map<Blank>(
(blank) => Blank.fromJson(blank),
)
.toList(),
);
}
}

class Blank {
const Blank({
required this.answer,
required this.feedback,
});

final String answer;
final String feedback;

factory Blank.fromJson(Map<String, dynamic> data) {
return Blank(
answer: data['answer'],
feedback: data['feedback'] ?? '',
);
}
}

class EnglishAudio {
const EnglishAudio(
{required this.fileName,
required this.startTime,
required this.startTimeStamp,
required this.finishTimeStamp});

final String fileName;
final String startTime;
final String startTimeStamp;
final String finishTimeStamp;

factory EnglishAudio.fromJson(Map<String, dynamic> data) {
return EnglishAudio(
fileName: data['filename'],
startTime: data['startTime'].toString(),
startTimeStamp: data['startTimestamp'].toString(),
finishTimeStamp: data['finishTimestamp'].toString(),
);
}
}
7 changes: 6 additions & 1 deletion mobile-app/lib/models/learn/curriculum_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ class Block {
});

static bool checkIfStepBased(String superblock) {
return superblock == '2022/responsive-web-design';
List<String> stepbased = [
'2022/responsive-web-design',
'a2-english-for-developers'
];

return stepbased.contains(superblock);
}

factory Block.fromJson(
Expand Down
99 changes: 91 additions & 8 deletions mobile-app/lib/ui/views/learn/block/block_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class BlockView extends StatelessWidget {
bool isCertification = block.challenges.length == 1 &&
block.superBlock.dashedName != 'the-odin-project';

bool isDialogue =
block.superBlock.dashedName == 'a2-english-for-developers';

int calculateProgress =
(model.challengesCompleted / block.challenges.length * 100).round();

Expand Down Expand Up @@ -91,7 +94,15 @@ class BlockView extends StatelessWidget {
model: model,
block: block,
),
if (!isCertification && isStepBased) ...[
if (isDialogue) ...[
buildDivider(),
dialogueWidget(
block.challenges,
context,
model,
)
],
if (!isCertification && isStepBased && !isDialogue) ...[
buildDivider(),
gridWidget(context, model)
],
Expand All @@ -112,6 +123,79 @@ class BlockView extends StatelessWidget {
);
}

Widget dialogueWidget(
List<ChallengeOrder> challenges,
BuildContext context,
BlockViewModel model,
) {
List<List<ChallengeOrder>> structure = [];

List<ChallengeOrder> dialogueHeaders = [];

for (int i = 0; i < challenges.length; i++) {
if (challenges[i].title.contains('Dialogue')) {
structure.add([]);
}
}

int dialogueIndex = 0;

dialogueHeaders.add(challenges[0]);

for (int i = 1; i < challenges.length; i++) {
if (challenges[i].title.contains('Dialogue')) {
dialogueHeaders.add(challenges[i]);
dialogueIndex++;
} else {
structure[dialogueIndex].add(challenges[i]);
}
}
return Column(
children: [
...List.generate(structure.length, (step) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
dialogueHeaders[step].title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
GridView.count(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
padding: const EdgeInsets.all(16),
crossAxisCount: (MediaQuery.of(context).size.width / 70 -
MediaQuery.of(context).viewPadding.horizontal)
.round(),
children: List.generate(
structure[step].length,
(index) {
return Center(
child: ChallengeTile(
block: block,
model: model,
challengeId: structure[step][index].id,
step: int.parse(
structure[step][index].title.split('Task')[1],
),
isDowloaded: false,
),
);
},
),
),
],
);
})
],
);
}

Widget gridWidget(BuildContext context, BlockViewModel model) {
return SizedBox(
height: 300,
Expand All @@ -133,7 +217,8 @@ class BlockView extends StatelessWidget {
child: ChallengeTile(
block: block,
model: model,
step: step,
step: step + 1,
challengeId: block.challengeTiles[step].id,
isDowloaded: (snapshot.data is bool
? snapshot.data as bool
: false),
Expand Down Expand Up @@ -271,18 +356,18 @@ class ChallengeTile extends StatelessWidget {
required this.model,
required this.step,
required this.isDowloaded,
required this.challengeId,
}) : super(key: key);

final Block block;
final BlockViewModel model;
final int step;
final bool isDowloaded;
final String challengeId;

@override
Widget build(BuildContext context) {
bool isCompleted = model.completedChallenge(
block.challengeTiles[step].id,
);
bool isCompleted = model.completedChallenge(challengeId);

return GridTile(
child: Container(
Expand All @@ -304,8 +389,6 @@ class ChallengeTile extends StatelessWidget {
width: 70,
child: InkWell(
onTap: () async {
String challengeId = block.challengeTiles[step].id;

String url = LearnService.baseUrl;

String fullUrl =
Expand All @@ -319,7 +402,7 @@ class ChallengeTile extends StatelessWidget {
},
child: Center(
child: Text(
(step + 1).toString(),
(step).toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
Expand Down
19 changes: 15 additions & 4 deletions mobile-app/lib/ui/views/learn/challenge/challenge_view.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Expand All @@ -6,7 +8,8 @@ import 'package:freecodecamp/extensions/i18n_extension.dart';
import 'package:freecodecamp/models/learn/challenge_model.dart';
import 'package:freecodecamp/models/learn/curriculum_model.dart';
import 'package:freecodecamp/ui/views/learn/challenge/challenge_viewmodel.dart';
import 'package:freecodecamp/ui/views/learn/challenge/templates/odin/odin_view.dart';
import 'package:freecodecamp/ui/views/learn/challenge/templates/english/english_view.dart';
import 'package:freecodecamp/ui/views/learn/challenge/templates/multiple_choice/multiple_choice_view.dart';
import 'package:freecodecamp/ui/views/learn/challenge/templates/python-project/python_project_view.dart';
import 'package:freecodecamp/ui/views/learn/challenge/templates/python/python_view.dart';
import 'package:freecodecamp/ui/views/learn/widgets/console/console_view.dart';
Expand Down Expand Up @@ -46,7 +49,7 @@ class ChallengeView extends StatelessWidget {
int currChallengeNum = block.challengeTiles
.indexWhere((element) => element.id == challenge.id) +
1;

log(challenge.challengeType.toString());
if (challenge.challengeType == 10) {
return PythonProjectView(
challenge: challenge,
Expand All @@ -60,13 +63,21 @@ class ChallengeView extends StatelessWidget {
challengesCompleted: challengesCompleted,
currentChallengeNum: currChallengeNum,
);
} else if (challenge.challengeType == 15) {
return OdinView(
} else if (challenge.challengeType == 15 ||
challenge.challengeType == 19) {
return MultipleChoiceView(
challenge: challenge,
block: block,
challengesCompleted: challengesCompleted,
currentChallengeNum: currChallengeNum,
);
} else if (challenge.challengeType == 22 ||
challenge.challengeType == 21) {
return EnglishView(
challenge: challenge,
currentChallengeNum: currChallengeNum,
block: block,
);
} else {
ChallengeFile currFile = model.currentFile(challenge);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,14 @@ class ChallengeViewModel extends BaseViewModel {
) async {
setupDialogUi();

// these are non editor types
List<int> challengeTypes = [10, 11, 15, 19, 21, 22];

setChallenge = learnOfflineService.getChallenge(url, challengeId);
Challenge challenge = await _challenge!;

learnService.setLastVisitedChallenge(url, block);

if (challenge.challengeType == 11 ||
challenge.challengeType == 10 ||
challenge.challengeType == 15) {
} else {
if (!challengeTypes.contains(challenge.challengeType)) {
List<ChallengeFile> currentEditedChallenge = challenge.files
.where((element) => element.editableRegionBoundaries.isNotEmpty)
.toList();
Expand Down
Loading
Loading