diff --git a/mobile-app/lib/app/app.dart b/mobile-app/lib/app/app.dart index 5539ddcb3..4104d9bde 100644 --- a/mobile-app/lib/app/app.dart +++ b/mobile-app/lib/app/app.dart @@ -16,7 +16,7 @@ import 'package:freecodecamp/service/dio_service.dart'; import 'package:freecodecamp/service/news/api_service.dart'; import 'package:freecodecamp/ui/views/code_radio/code_radio_view.dart'; -import 'package:freecodecamp/ui/views/learn/challenge/challenge_view.dart'; +import 'package:freecodecamp/ui/views/learn/challenge/templates/template_view.dart'; import 'package:freecodecamp/ui/views/learn/landing/landing_view.dart'; import 'package:freecodecamp/ui/views/learn/superblock/superblock_view.dart'; import 'package:freecodecamp/ui/views/login/native_login_view.dart'; @@ -48,7 +48,7 @@ import 'package:stacked_services/stacked_services.dart'; MaterialRoute(page: NewsAuthorView), MaterialRoute(page: NewsImageView), MaterialRoute(page: CodeRadioView), - MaterialRoute(page: ChallengeView), + MaterialRoute(page: ChallengeTemplateView), MaterialRoute(page: ProfileView), MaterialRoute(page: LearnLandingView, initial: true), MaterialRoute(page: NativeLoginView), diff --git a/mobile-app/lib/app/app.router.dart b/mobile-app/lib/app/app.router.dart index 709424426..d29845627 100644 --- a/mobile-app/lib/app/app.router.dart +++ b/mobile-app/lib/app/app.router.dart @@ -13,7 +13,7 @@ import 'package:freecodecamp/models/news/bookmarked_tutorial_model.dart' import 'package:freecodecamp/models/podcasts/episodes_model.dart' as _i19; import 'package:freecodecamp/models/podcasts/podcasts_model.dart' as _i20; import 'package:freecodecamp/ui/views/code_radio/code_radio_view.dart' as _i10; -import 'package:freecodecamp/ui/views/learn/challenge/challenge_view.dart' +import 'package:freecodecamp/ui/views/learn/challenge/templates/template_view.dart' as _i11; import 'package:freecodecamp/ui/views/learn/landing/landing_view.dart' as _i13; import 'package:freecodecamp/ui/views/learn/superblock/superblock_view.dart' @@ -60,7 +60,7 @@ class Routes { static const codeRadioView = '/code-radio-view'; - static const challengeView = '/challenge-view'; + static const challengeTemplateView = '/challenge-template-view'; static const profileView = '/profile-view'; @@ -84,7 +84,7 @@ class Routes { newsAuthorView, newsImageView, codeRadioView, - challengeView, + challengeTemplateView, profileView, learnLandingView, nativeLoginView, @@ -133,8 +133,8 @@ class StackedRouter extends _i1.RouterBase { page: _i10.CodeRadioView, ), _i1.RouteDef( - Routes.challengeView, - page: _i11.ChallengeView, + Routes.challengeTemplateView, + page: _i11.ChallengeTemplateView, ), _i1.RouteDef( Routes.profileView, @@ -239,16 +239,14 @@ class StackedRouter extends _i1.RouterBase { settings: data, ); }, - _i11.ChallengeView: (data) { - final args = data.getArgs(nullOk: false); + _i11.ChallengeTemplateView: (data) { + final args = data.getArgs(nullOk: false); return _i18.MaterialPageRoute( - builder: (context) => _i11.ChallengeView( + builder: (context) => _i11.ChallengeTemplateView( key: args.key, - url: args.url, block: args.block, challengeId: args.challengeId, - challengesCompleted: args.challengesCompleted, - isProject: args.isProject), + challengesCompleted: args.challengesCompleted), settings: data, ); }, @@ -513,52 +511,42 @@ class NewsImageViewArguments { } } -class ChallengeViewArguments { - const ChallengeViewArguments({ +class ChallengeTemplateViewArguments { + const ChallengeTemplateViewArguments({ this.key, - required this.url, required this.block, required this.challengeId, required this.challengesCompleted, - required this.isProject, }); final _i18.Key? key; - final String url; - final _i22.Block block; final String challengeId; final int challengesCompleted; - final bool isProject; - @override String toString() { - return '{"key": "$key", "url": "$url", "block": "$block", "challengeId": "$challengeId", "challengesCompleted": "$challengesCompleted", "isProject": "$isProject"}'; + return '{"key": "$key", "block": "$block", "challengeId": "$challengeId", "challengesCompleted": "$challengesCompleted"}'; } @override - bool operator ==(covariant ChallengeViewArguments other) { + bool operator ==(covariant ChallengeTemplateViewArguments other) { if (identical(this, other)) return true; return other.key == key && - other.url == url && other.block == block && other.challengeId == challengeId && - other.challengesCompleted == challengesCompleted && - other.isProject == isProject; + other.challengesCompleted == challengesCompleted; } @override int get hashCode { return key.hashCode ^ - url.hashCode ^ block.hashCode ^ challengeId.hashCode ^ - challengesCompleted.hashCode ^ - isProject.hashCode; + challengesCompleted.hashCode; } } @@ -794,27 +782,23 @@ extension NavigatorStateExtension on _i23.NavigationService { transition: transition); } - Future navigateToChallengeView({ + Future navigateToChallengeTemplateView({ _i18.Key? key, - required String url, required _i22.Block block, required String challengeId, required int challengesCompleted, - required bool isProject, int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, }) async { - return navigateTo(Routes.challengeView, - arguments: ChallengeViewArguments( + return navigateTo(Routes.challengeTemplateView, + arguments: ChallengeTemplateViewArguments( key: key, - url: url, block: block, challengeId: challengeId, - challengesCompleted: challengesCompleted, - isProject: isProject), + challengesCompleted: challengesCompleted), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -1082,27 +1066,23 @@ extension NavigatorStateExtension on _i23.NavigationService { transition: transition); } - Future replaceWithChallengeView({ + Future replaceWithChallengeTemplateView({ _i18.Key? key, - required String url, required _i22.Block block, required String challengeId, required int challengesCompleted, - required bool isProject, int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, }) async { - return replaceWith(Routes.challengeView, - arguments: ChallengeViewArguments( + return replaceWith(Routes.challengeTemplateView, + arguments: ChallengeTemplateViewArguments( key: key, - url: url, block: block, challengeId: challengeId, - challengesCompleted: challengesCompleted, - isProject: isProject), + challengesCompleted: challengesCompleted), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, diff --git a/mobile-app/lib/service/firebase/analytics_observer.dart b/mobile-app/lib/service/firebase/analytics_observer.dart index 110cea87d..fba8d5922 100644 --- a/mobile-app/lib/service/firebase/analytics_observer.dart +++ b/mobile-app/lib/service/firebase/analytics_observer.dart @@ -28,8 +28,9 @@ class AnalyticsObserver extends RouteObserver { route.settings.arguments as NewsBookmarkTutorialViewArguments; screenName += '/${routeArgs.tutorial.tutorialTitle}'; break; - case ChallengeViewArguments: - final routeArgs = route.settings.arguments as ChallengeViewArguments; + case ChallengeTemplateViewArguments: + final routeArgs = + route.settings.arguments as ChallengeTemplateViewArguments; screenName += '/${routeArgs.challengeId}'; break; default: diff --git a/mobile-app/lib/service/learn/learn_service.dart b/mobile-app/lib/service/learn/learn_service.dart index 15db89fa7..b9a59d902 100644 --- a/mobile-app/lib/service/learn/learn_service.dart +++ b/mobile-app/lib/service/learn/learn_service.dart @@ -172,17 +172,13 @@ class LearnService { if (challengeIndex == maxChallenges - 1) { _navigationService.back(); } else { - String challenge = block.challengeTiles[challengeIndex + 1].id; - String url = LearnService.baseUrl; _navigationService.replaceWith( - Routes.challengeView, - arguments: ChallengeViewArguments( - url: - '$url/challenges/${block.superBlock.dashedName}/${block.dashedName}/$challenge.json', - block: block, - challengeId: block.challengeTiles[challengeIndex + 1].id, - challengesCompleted: challengesCompleted + 1, - isProject: block.challenges.length == 1), + Routes.challengeTemplateView, + arguments: ChallengeTemplateViewArguments( + challengeId: block.challengeTiles[challengeIndex + 1].id, + block: block, + challengesCompleted: challengesCompleted + 1, + ), ); } } diff --git a/mobile-app/lib/ui/views/learn/block/block_view.dart b/mobile-app/lib/ui/views/learn/block/block_view.dart index 88ccc3d8b..9fcd8cb21 100644 --- a/mobile-app/lib/ui/views/learn/block/block_view.dart +++ b/mobile-app/lib/ui/views/learn/block/block_view.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:freecodecamp/extensions/i18n_extension.dart'; import 'package:freecodecamp/models/learn/curriculum_model.dart'; -import 'package:freecodecamp/service/learn/learn_service.dart'; import 'package:freecodecamp/ui/views/learn/block/block_viewmodel.dart'; import 'package:freecodecamp/ui/views/learn/utils/learn_globals.dart'; import 'package:freecodecamp/ui/views/learn/widgets/download_button_widget.dart'; @@ -243,10 +242,7 @@ class BlockView extends StatelessWidget { onTap: () async { String challengeId = block.challengeTiles[i].id; - String url = LearnService.baseUrl; - model.routeToChallengeView( - '$url/challenges/${block.superBlock.dashedName}/${block.dashedName}/$challengeId.json', block, challengeId, ); @@ -380,13 +376,7 @@ class ChallengeTile extends StatelessWidget { width: 70, child: InkWell( onTap: () async { - String url = LearnService.baseUrl; - - String fullUrl = - '$url/challenges/${block.superBlock.dashedName}/${block.dashedName}/$challengeId.json'; - model.routeToChallengeView( - fullUrl, block, challengeId, ); diff --git a/mobile-app/lib/ui/views/learn/block/block_viewmodel.dart b/mobile-app/lib/ui/views/learn/block/block_viewmodel.dart index 1488fe846..710b8f8bc 100644 --- a/mobile-app/lib/ui/views/learn/block/block_viewmodel.dart +++ b/mobile-app/lib/ui/views/learn/block/block_viewmodel.dart @@ -82,15 +82,13 @@ class BlockViewModel extends BaseViewModel { notifyListeners(); } - void routeToChallengeView(String url, Block block, String challengeId) { + void routeToChallengeView(Block block, String challengeId) { _navigationService.navigateTo( - Routes.challengeView, - arguments: ChallengeViewArguments( - url: url, - block: block, + Routes.challengeTemplateView, + arguments: ChallengeTemplateViewArguments( challengeId: challengeId, + block: block, challengesCompleted: _challengesCompleted, - isProject: block.challenges.length == 1, ), ); } @@ -98,10 +96,7 @@ class BlockViewModel extends BaseViewModel { Future routeToCertification(Block block) async { String challengeId = block.challengeTiles[0].id; - String url = LearnService.baseUrl; - routeToChallengeView( - '$url/challenges/${block.superBlock.dashedName}/${block.dashedName}/$challengeId.json', block, challengeId, ); diff --git a/mobile-app/lib/ui/views/learn/challenge/challenge_view.dart b/mobile-app/lib/ui/views/learn/challenge/challenge_view.dart index 6c5162725..f8c72b776 100644 --- a/mobile-app/lib/ui/views/learn/challenge/challenge_view.dart +++ b/mobile-app/lib/ui/views/learn/challenge/challenge_view.dart @@ -5,10 +5,6 @@ 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/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'; import 'package:freecodecamp/ui/views/learn/widgets/dynamic_panel/panels/dynamic_panel.dart'; import 'package:phone_ide/phone_ide.dart'; @@ -17,16 +13,14 @@ import 'package:stacked/stacked.dart'; class ChallengeView extends StatelessWidget { const ChallengeView({ Key? key, - required this.url, required this.block, - required this.challengeId, + required this.challenge, required this.challengesCompleted, required this.isProject, }) : super(key: key); - final String url; + final Challenge challenge; final Block block; - final String challengeId; final int challengesCompleted; final bool isProject; @@ -35,237 +29,182 @@ class ChallengeView extends StatelessWidget { return ViewModelBuilder.reactive( viewModelBuilder: () => ChallengeViewModel(), onViewModelReady: (model) { - model.init(url, block, challengeId, challengesCompleted); + model.init(block, challenge, challengesCompleted); }, - builder: (context, model, child) => FutureBuilder( - future: model.challenge, - builder: (context, snapshot) { - if (snapshot.hasData) { - Challenge challenge = snapshot.data!; - int maxChallenges = block.challenges.length; - int currChallengeNum = block.challengeTiles - .indexWhere((element) => element.id == challenge.id) + - 1; - - if (challenge.challengeType == 10) { - return PythonProjectView( - challenge: challenge, - block: block, - challengesCompleted: challengesCompleted, - ); - } else if (challenge.challengeType == 11) { - return PythonView( - challenge: challenge, - block: block, - challengesCompleted: challengesCompleted, - currentChallengeNum: currChallengeNum, - ); - } 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); - - bool keyboard = MediaQuery.of(context).viewInsets.bottom != 0; - - bool onlyJs = - challenge.files.every((file) => file.ext.name == 'js'); - - bool editableRegion = - currFile.editableRegionBoundaries.isNotEmpty; - EditorOptions options = EditorOptions( - hasRegion: editableRegion, - ); + builder: (context, model, child) { + int maxChallenges = block.challenges.length; + ChallengeFile currFile = model.currentFile(challenge); + + bool keyboard = MediaQuery.of(context).viewInsets.bottom != 0; + + bool onlyJs = challenge.files.every((file) => file.ext.name == 'js'); + + bool editableRegion = currFile.editableRegionBoundaries.isNotEmpty; + EditorOptions options = EditorOptions( + hasRegion: editableRegion, + ); + + Editor editor = Editor( + language: currFile.ext.name.toUpperCase(), + options: options, + ); + + model.initiateFile(editor, challenge, currFile, editableRegion); + model.listenToFocusedController(editor); + model.listenToSymbolBarScrollController(); + + if (model.showPanel) { + FocusManager.instance.primaryFocus?.unfocus(); + } + + editor.onTextChange.stream.listen((text) { + model.fileService.saveFileInCache( + challenge, + model.currentSelectedFile != '' + ? model.currentSelectedFile + : challenge.files[0].name, + text, + ); - Editor editor = Editor( - language: currFile.ext.name.toUpperCase(), - options: options, - ); + model.setEditorText = text; + model.setHasTypedInEditor = true; + model.setCompletedChallenge = false; + }); - model.initiateFile(editor, challenge, currFile, editableRegion); - model.listenToFocusedController(editor); - model.listenToSymbolBarScrollController(); - - if (model.showPanel) { - FocusManager.instance.primaryFocus?.unfocus(); - } - - editor.onTextChange.stream.listen((text) { - model.fileService.saveFileInCache( - challenge, - model.currentSelectedFile != '' - ? model.currentSelectedFile - : challenge.files[0].name, - text, - ); - - model.setEditorText = text; - model.setHasTypedInEditor = true; - model.setCompletedChallenge = false; - }); - - BoxDecoration decoration = const BoxDecoration( - border: Border( - bottom: BorderSide( - width: 4, - color: Colors.blue, - ), - ), - ); + BoxDecoration decoration = const BoxDecoration( + border: Border( + bottom: BorderSide( + width: 4, + color: Colors.blue, + ), + ), + ); - return PopScope( - canPop: true, - onPopInvokedWithResult: (bool didPop, dynamic result) { - model.learnService.updateProgressOnPop(context, block); - }, - child: Scaffold( - appBar: PreferredSize( - preferredSize: Size( - MediaQuery.sizeOf(context).width, - model.showPanel ? 0 : 50, - ), - child: AppBar( - automaticallyImplyLeading: !model.showPreview, - title: challenge.files.length == 1 && !model.showPreview - ? Text(context.t.editor) - : Row( - children: [ - if (model.showPreview && !onlyJs) - Expanded( - child: Container( - decoration: model.showProjectPreview - ? decoration - : null, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(0), - ), - elevation: 0, - ), - onPressed: () { - model.setShowConsole = false; - model.setShowProjectPreview = true; - }, - child: Text( - context.t.preview, - ), - ), + return PopScope( + canPop: true, + onPopInvokedWithResult: (bool didPop, dynamic result) { + model.learnService.updateProgressOnPop(context, block); + }, + child: Scaffold( + appBar: PreferredSize( + preferredSize: Size( + MediaQuery.sizeOf(context).width, + model.showPanel ? 0 : 50, + ), + child: AppBar( + automaticallyImplyLeading: !model.showPreview, + title: challenge.files.length == 1 && !model.showPreview + ? Text(context.t.editor) + : Row( + children: [ + if (model.showPreview && !onlyJs) + Expanded( + child: Container( + decoration: model.showProjectPreview + ? decoration + : null, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), ), + elevation: 0, ), - if (model.showPreview) - Expanded( - child: Container( - decoration: - model.showConsole ? decoration : null, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(0), - ), - elevation: 0, - ), - onPressed: () { - model.setShowConsole = true; - model.setShowProjectPreview = false; - }, - child: Text( - context.t.console, - ), - ), - ), + onPressed: () { + model.setShowConsole = false; + model.setShowProjectPreview = true; + }, + child: Text( + context.t.preview, ), - if (!model.showPreview && - challenge.files.length > 1) - for (ChallengeFile file in challenge.files) - customTabBar( - model, - challenge, - file, - editor, - ) - ], - ), - ), - ), - bottomNavigationBar: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: customBottomBar( - model, - keyboard, - challenge, - editor, - context, - ), - ), - body: !model.showPreview - ? Column( - children: [ - if (model.showPanel && !keyboard) - DynamicPanel( - challenge: challenge, - model: model, - panel: model.panelType, - maxChallenges: maxChallenges, - challengesCompleted: challengesCompleted, - editor: editor, + ), ), - Expanded(child: editor) - ], - ) - : Column( - children: [ - if (model.showPanel && !keyboard) - DynamicPanel( - challenge: challenge, - model: model, - panel: model.panelType, - maxChallenges: maxChallenges, - challengesCompleted: challengesCompleted, - editor: editor, + ), + if (model.showPreview) + Expanded( + child: Container( + decoration: + model.showConsole ? decoration : null, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + elevation: 0, + ), + onPressed: () { + model.setShowConsole = true; + model.setShowProjectPreview = false; + }, + child: Text( + context.t.console, + ), + ), ), - model.showProjectPreview && !onlyJs - ? ProjectPreview( - challenge: challenge, - model: model, - ) - : JavaScriptConsole( - messages: model.consoleMessages, - ) - ], - ), - ), - ); - } - } - - return Scaffold( - appBar: AppBar( - title: Text(context.t.loading), - automaticallyImplyLeading: false, + ), + if (!model.showPreview && challenge.files.length > 1) + for (ChallengeFile file in challenge.files) + customTabBar( + model, + challenge, + file, + editor, + ) + ], + ), + ), ), - body: const Center( - child: CircularProgressIndicator(), + bottomNavigationBar: Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: customBottomBar( + model, + keyboard, + challenge, + editor, + context, + ), ), - ); - }, - ), + body: !model.showPreview + ? Column( + children: [ + if (model.showPanel && !keyboard) + DynamicPanel( + challenge: challenge, + model: model, + panel: model.panelType, + maxChallenges: maxChallenges, + challengesCompleted: challengesCompleted, + editor: editor, + ), + Expanded(child: editor) + ], + ) + : Column( + children: [ + if (model.showPanel && !keyboard) + DynamicPanel( + challenge: challenge, + model: model, + panel: model.panelType, + maxChallenges: maxChallenges, + challengesCompleted: challengesCompleted, + editor: editor, + ), + model.showProjectPreview && !onlyJs + ? ProjectPreview( + challenge: challenge, + model: model, + ) + : JavaScriptConsole( + messages: model.consoleMessages, + ) + ], + ), + ), + ); + }, ); } diff --git a/mobile-app/lib/ui/views/learn/challenge/challenge_viewmodel.dart b/mobile-app/lib/ui/views/learn/challenge/challenge_viewmodel.dart index 6910fa285..e4fe4257f 100644 --- a/mobile-app/lib/ui/views/learn/challenge/challenge_viewmodel.dart +++ b/mobile-app/lib/ui/views/learn/challenge/challenge_viewmodel.dart @@ -87,8 +87,9 @@ class ChallengeViewModel extends BaseViewModel { TestRunner runner = TestRunner(); SnackbarService snackbar = locator(); - Future? _challenge; - Future? get challenge => _challenge; + + Challenge? _challenge; + Challenge? get challenge => _challenge; Block? _block; Block? get block => _block; @@ -179,7 +180,7 @@ class ChallengeViewModel extends BaseViewModel { notifyListeners(); } - set setChallenge(Future challenge) { + set setChallenge(Challenge challenge) { _challenge = challenge; notifyListeners(); } @@ -225,40 +226,33 @@ class ChallengeViewModel extends BaseViewModel { } void init( - String url, Block block, - String challengeId, + Challenge challenge, int challengesCompleted, ) async { setupDialogUi(); - List nonEditorTypes = [10, 11, 15, 19, 21, 22]; - - setChallenge = learnOfflineService.getChallenge(url, challengeId); - Challenge challenge = await _challenge!; + setChallenge = challenge; - learnService.setLastVisitedChallenge(url, block); - if (!nonEditorTypes.contains(challenge.challengeType)) { - List currentEditedChallenge = challenge.files - .where((element) => element.editableRegionBoundaries.isNotEmpty) - .toList(); + List currentEditedChallenge = challenge.files + .where((element) => element.editableRegionBoundaries.isNotEmpty) + .toList(); - if (editorText == null) { - String text = await fileService.getExactFileFromCache( - challenge, - currentEditedChallenge.isEmpty - ? challenge.files.first - : currentEditedChallenge.first, - ); + if (editorText == null) { + String text = await fileService.getExactFileFromCache( + challenge, + currentEditedChallenge.isEmpty + ? challenge.files.first + : currentEditedChallenge.first, + ); - if (text != '') { - setEditorText = text; - } + if (text != '') { + setEditorText = text; } - setCurrentSelectedFile = currentEditedChallenge.isEmpty - ? challenge.files[0].name - : currentEditedChallenge[0].name; } + setCurrentSelectedFile = currentEditedChallenge.isEmpty + ? challenge.files[0].name + : currentEditedChallenge[0].name; setBlock = block; setChallengesCompleted = challengesCompleted; @@ -360,7 +354,7 @@ class ChallengeViewModel extends BaseViewModel { Future parsePreviewDocument(String doc) async { SharedPreferences prefs = await SharedPreferences.getInstance(); - Challenge? currChallenge = await challenge; + Challenge? currChallenge = challenge; if (currChallenge == null) return parse(doc).outerHtml; @@ -448,7 +442,7 @@ class ChallengeViewModel extends BaseViewModel { ); if (res?.confirmed == true) { - Challenge? currChallenge = await challenge; + Challenge? currChallenge = challenge; for (ChallengeFile file in currChallenge!.files) { prefs.remove('${currChallenge.id}.${file.name}'); @@ -467,13 +461,11 @@ class ChallengeViewModel extends BaseViewModel { await prefs.remove(challengeUrl); _navigationService.replaceWith( - Routes.challengeView, - arguments: ChallengeViewArguments( - url: challengeUrl, + Routes.challengeTemplateView, + arguments: ChallengeTemplateViewArguments( block: block!, challengeId: currChallenge.id, challengesCompleted: challengesCompleted, - isProject: block!.challenges.length == 1, ), ); } diff --git a/mobile-app/lib/ui/views/learn/challenge/templates/template_view.dart b/mobile-app/lib/ui/views/learn/challenge/templates/template_view.dart new file mode 100644 index 000000000..2cb1ac595 --- /dev/null +++ b/mobile-app/lib/ui/views/learn/challenge/templates/template_view.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.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_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/challenge/templates/template_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class ChallengeTemplateView extends StatelessWidget { + const ChallengeTemplateView({ + Key? key, + required this.block, + required this.challengeId, + required this.challengesCompleted, + }) : super(key: key); + + final Block block; + final String challengeId; + final int challengesCompleted; + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + onViewModelReady: (model) => model.initiate(block, challengeId), + viewModelBuilder: () => ChallengeTemplateViewModel(), + builder: (context, model, child) => Scaffold( + body: FutureBuilder( + future: model.challenge, + builder: (context, snapshot) { + if (snapshot.hasData) { + Challenge challenge = snapshot.data!; + + int challengeType = challenge.challengeType; + List tiles = block.challengeTiles; + int challNum = + tiles.indexWhere((el) => el.id == challenge.id) + 1; + switch (challengeType) { + case 0: + return ChallengeView( + challenge: challenge, + block: block, + challengesCompleted: challengesCompleted, + isProject: tiles.length > 1, + ); + case 10: + return PythonProjectView( + challenge: challenge, + block: block, + challengesCompleted: challengesCompleted, + ); + case 11: + return PythonView( + challenge: challenge, + block: block, + challengesCompleted: challengesCompleted, + currentChallengeNum: challNum, + ); + case 15: + case 19: + return MultipleChoiceView( + challenge: challenge, + block: block, + challengesCompleted: challengesCompleted, + currentChallengeNum: challNum, + ); + case 21: + case 22: + return EnglishView( + challenge: challenge, + block: block, + currentChallengeNum: challNum, + ); + default: + return Text( + 'Unknown Challenge, info : ${challenge.challengeType}', + ); + } + } + + return const Center( + child: CircularProgressIndicator(), + ); + }, + ), + ), + ); + } +} diff --git a/mobile-app/lib/ui/views/learn/challenge/templates/template_viewmodel.dart b/mobile-app/lib/ui/views/learn/challenge/templates/template_viewmodel.dart new file mode 100644 index 000000000..343453d31 --- /dev/null +++ b/mobile-app/lib/ui/views/learn/challenge/templates/template_viewmodel.dart @@ -0,0 +1,30 @@ +import 'package:freecodecamp/app/app.locator.dart'; +import 'package:freecodecamp/models/learn/challenge_model.dart'; +import 'package:freecodecamp/models/learn/curriculum_model.dart'; +import 'package:freecodecamp/service/learn/learn_offline_service.dart'; +import 'package:freecodecamp/service/learn/learn_service.dart'; +import 'package:stacked/stacked.dart'; + +class ChallengeTemplateViewModel extends BaseViewModel { + Future? _challenge; + Future? get challenge => _challenge; + + final LearnOfflineService _learnOfflineService = + locator(); + final LearnService _learnService = locator(); + set setChallenge(Future challenge) { + _challenge = challenge; + notifyListeners(); + } + + void initiate(Block block, String challengeId) { + final String base = LearnService.baseUrl; + + String url = + '$base/challenges/${block.superBlock.dashedName}/${block.dashedName}/$challengeId.json'; + + setChallenge = _learnOfflineService.getChallenge(url); + + _learnService.setLastVisitedChallenge(url, block); + } +} diff --git a/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart b/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart index c84fdd7f9..d424c1422 100644 --- a/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart +++ b/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart @@ -129,12 +129,10 @@ class LearnLandingViewModel extends BaseViewModel { hasInternet: true, ); - _navigationService.navigateToChallengeView( - url: lastVisitedChallenge[0], + _navigationService.navigateToChallengeTemplateView( block: block, - challengeId: challenge.id, challengesCompleted: completedChallenges, - isProject: block.challenges.length == 1, + challengeId: challenge.id, ); } } @@ -163,7 +161,7 @@ class LearnLandingViewModel extends BaseViewModel { void disabledButtonSnack() { snack.showSnackbar(title: 'Not available use the web version', message: ''); - Future.delayed( const Duration(milliseconds: 2500), () { + Future.delayed(const Duration(milliseconds: 2500), () { snack.closeSnackbar(); }); } diff --git a/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/hint/hint_widget_view.dart b/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/hint/hint_widget_view.dart index d7e17e986..34be5e2f0 100644 --- a/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/hint/hint_widget_view.dart +++ b/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/hint/hint_widget_view.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:developer'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; @@ -163,11 +162,10 @@ class HintWidgetView extends StatelessWidget { IconButton( onPressed: () async { final forumLink = await genForumLink( - await challengeModel.challenge as Challenge, + challengeModel.challenge as Challenge, challengeModel.block as Block, context, editorText: challengeModel.editorText ?? ''); - log(forumLink); challengeModel.learnService.forumHelpDialog(forumLink); }, icon: const Icon(Icons.question_mark), diff --git a/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_model.dart b/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_model.dart index f38125384..5cbfc1091 100644 --- a/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_model.dart +++ b/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_model.dart @@ -29,7 +29,7 @@ class PassWidgetModel extends BaseViewModel { if (_user != null) { List? completedChallenges = _user?.completedChallenges; - Challenge? currChallenge = await challengeModel.challenge; + Challenge? currChallenge = challengeModel.challenge; if (currChallenge != null && completedChallenges != null) { if (completedChallenges .indexWhere((element) => element.id == currChallenge.id) != diff --git a/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_view.dart b/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_view.dart index d82f7b822..74d2b099d 100644 --- a/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_view.dart +++ b/mobile-app/lib/ui/views/learn/widgets/dynamic_panel/panels/pass/pass_widget_view.dart @@ -206,7 +206,7 @@ class PassButton extends StatelessWidget { model.learnService.goToNextChallenge( maxChallenges, completed, - await model.challenge as Challenge, + model.challenge as Challenge, model.block as Block, ); },