From c2c9a81caf21f2b2d5afcbc71ab231134c21eeaa Mon Sep 17 00:00:00 2001 From: sembauke Date: Thu, 12 Feb 2026 14:54:56 +0100 Subject: [PATCH] fix: handle nodules in reviews --- .../multiple_choice/multiple_choice_view.dart | 68 +-------------- .../templates/review/review_view.dart | 9 ++ .../views/learn/widgets/example_editor.dart | 87 +++++++++++++++++++ 3 files changed, 97 insertions(+), 67 deletions(-) create mode 100644 mobile-app/lib/ui/views/learn/widgets/example_editor.dart diff --git a/mobile-app/lib/ui/views/learn/challenge/templates/multiple_choice/multiple_choice_view.dart b/mobile-app/lib/ui/views/learn/challenge/templates/multiple_choice/multiple_choice_view.dart index 284abf305..540d58c5b 100644 --- a/mobile-app/lib/ui/views/learn/challenge/templates/multiple_choice/multiple_choice_view.dart +++ b/mobile-app/lib/ui/views/learn/challenge/templates/multiple_choice/multiple_choice_view.dart @@ -7,13 +7,13 @@ import 'package:freecodecamp/ui/views/learn/utils/challenge_utils.dart'; import 'package:freecodecamp/ui/views/learn/widgets/assignment_widget.dart'; import 'package:freecodecamp/ui/views/learn/widgets/audio/audio_player_view.dart'; import 'package:freecodecamp/ui/views/learn/widgets/challenge_card.dart'; +import 'package:freecodecamp/ui/views/learn/widgets/example_editor.dart'; import 'package:freecodecamp/ui/views/learn/widgets/explanation_widget.dart'; import 'package:freecodecamp/ui/views/learn/widgets/quiz_widget.dart'; import 'package:freecodecamp/ui/views/learn/widgets/scene/scene_view.dart'; import 'package:freecodecamp/ui/views/learn/widgets/transcript_widget.dart'; import 'package:freecodecamp/ui/views/learn/widgets/youtube_player_widget.dart'; import 'package:freecodecamp/ui/views/news/html_handler/html_handler.dart'; -import 'package:phone_ide/phone_ide.dart'; import 'package:stacked/stacked.dart'; class MultipleChoiceView extends StatelessWidget { @@ -241,70 +241,4 @@ class MultipleChoiceView extends StatelessWidget { } } -class ExampleEditor extends StatelessWidget { - const ExampleEditor({ - super.key, - required this.nodules, - required this.parser, - }); - - final List nodules; - final HTMLParser parser; - @override - Widget build(BuildContext context) { - return ChallengeCard( - title: 'Lesson', - child: Column( - children: [ - ...nodules.map( - (nodule) { - if (nodule.type == NoduleType.paragraph) { - return Column( - children: parser.parse(nodule.asString()), - ); - } else if (nodule.type == NoduleType.interactiveEditor) { - return Column( - children: nodule - .asList() - .map( - (file) => Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - file.ext.toUpperCase(), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - const SizedBox(height: 8), - Editor( - options: EditorOptions( - fontFamily: 'Hack', - takeFullHeight: false, - showLinebar: false, - isEditable: false, - ), - defaultLanguage: file.ext, - defaultValue: file.contents, - path: '/', - ), - ], - ), - ), - ) - .toList(), - ); - } else { - return const SizedBox.shrink(); - } - }, - ) - ], - ), - ); - } -} diff --git a/mobile-app/lib/ui/views/learn/challenge/templates/review/review_view.dart b/mobile-app/lib/ui/views/learn/challenge/templates/review/review_view.dart index 158fcaca2..70d0fc8e3 100644 --- a/mobile-app/lib/ui/views/learn/challenge/templates/review/review_view.dart +++ b/mobile-app/lib/ui/views/learn/challenge/templates/review/review_view.dart @@ -8,9 +8,11 @@ import 'package:freecodecamp/ui/views/learn/utils/challenge_utils.dart'; import 'package:freecodecamp/ui/views/learn/widgets/assignment_widget.dart'; import 'package:freecodecamp/ui/views/learn/widgets/audio/audio_player_view.dart'; import 'package:freecodecamp/ui/views/learn/widgets/challenge_card.dart'; +import 'package:freecodecamp/ui/views/learn/widgets/example_editor.dart'; import 'package:freecodecamp/ui/views/learn/widgets/scene/scene_view.dart'; import 'package:freecodecamp/ui/views/learn/widgets/transcript_widget.dart'; import 'package:freecodecamp/ui/views/learn/widgets/youtube_player_widget.dart'; +import 'package:freecodecamp/ui/views/news/html_handler/html_handler.dart'; import 'package:stacked/stacked.dart'; class ReviewView extends StatelessWidget { @@ -25,6 +27,8 @@ class ReviewView extends StatelessWidget { @override Widget build(BuildContext context) { + HTMLParser parser = HTMLParser(context: context); + return ViewModelBuilder.reactive( viewModelBuilder: () => ReviewViewmodel(), onViewModelReady: (model) => model.initChallenge(challenge, context), @@ -47,6 +51,11 @@ class ReviewView extends StatelessWidget { ], ), ), + if (challenge.nodules?.isNotEmpty ?? false) + ExampleEditor( + nodules: challenge.nodules!, + parser: parser, + ), if (challenge.videoId != null) ...[ const SizedBox(height: 12), ChallengeCard( diff --git a/mobile-app/lib/ui/views/learn/widgets/example_editor.dart b/mobile-app/lib/ui/views/learn/widgets/example_editor.dart new file mode 100644 index 000000000..21569ea7c --- /dev/null +++ b/mobile-app/lib/ui/views/learn/widgets/example_editor.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:freecodecamp/models/learn/challenge_model.dart'; +import 'package:freecodecamp/ui/views/learn/widgets/challenge_card.dart'; +import 'package:freecodecamp/ui/views/news/html_handler/html_handler.dart'; +import 'package:phone_ide/editor/editor.dart'; +import 'package:phone_ide/editor/editor_options.dart'; + +class ExampleEditor extends StatelessWidget { + const ExampleEditor({ + super.key, + required this.nodules, + required this.parser, + }); + + final List nodules; + final HTMLParser parser; + + @override + Widget build(BuildContext context) { + return ChallengeCard( + title: 'Lesson', + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...nodules.map( + (nodule) { + if (nodule.type == NoduleType.paragraph) { + return Column( + children: parser.parse( + nodule.asString(), + customStyles: { + '*': Style( + fontSize: FontSize(18), + ), + 'ul': Style( + fontSize: FontSize(18), + padding: HtmlPaddings.only(left: 10)) + }, + ), + ); + } else if (nodule.type == NoduleType.interactiveEditor) { + return Column( + children: nodule + .asList() + .map( + (file) => Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.ext.toUpperCase(), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 8), + Editor( + options: EditorOptions( + fontFamily: 'Hack', + takeFullHeight: false, + showLinebar: false, + isEditable: false, + ), + defaultLanguage: file.ext, + defaultValue: file.contents, + path: '/', + ), + ], + ), + ), + ) + .toList(), + ); + } else { + return const SizedBox.shrink(); + } + }, + ) + ], + ), + ); + } +}