From 4403b5866ab10555310b812e1488e878b1060fda Mon Sep 17 00:00:00 2001 From: SemBauke Date: Mon, 5 Sep 2022 14:59:27 +0200 Subject: [PATCH 01/19] feat: settings learn --- mobile-app/lib/app/app.dart | 4 ++- mobile-app/lib/app/app.router.dart | 26 +++++++++++++++++ .../views/learn/settings/settings_model.dart | 3 ++ .../views/learn/settings/settings_view.dart | 28 +++++++++++++++++++ .../ui/views/profile/profile_viemodel.dart | 7 +++++ .../lib/ui/views/profile/profile_view.dart | 5 ++++ 6 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 mobile-app/lib/ui/views/learn/settings/settings_model.dart create mode 100644 mobile-app/lib/ui/views/learn/settings/settings_view.dart diff --git a/mobile-app/lib/app/app.dart b/mobile-app/lib/app/app.dart index 935aa19e3..8c704f852 100644 --- a/mobile-app/lib/app/app.dart +++ b/mobile-app/lib/app/app.dart @@ -22,6 +22,7 @@ import 'package:freecodecamp/ui/views/news/news-bookmark/news_bookmark_view.dart import 'package:freecodecamp/ui/views/news/news-feed/news_feed_view.dart'; import 'package:freecodecamp/ui/views/settings/forumSettings/forum_settings_view.dart'; import 'package:freecodecamp/ui/views/settings/podcastSettings/podcast_settings_view.dart'; +import 'package:freecodecamp/ui/views/learn/settings/settings_view.dart'; import 'package:freecodecamp/ui/views/learn/learn-builders/superblock_builder.dart'; import 'package:freecodecamp/ui/views/learn/challenge_editor/challenge_view.dart'; import 'package:freecodecamp/ui/views/web_view/web_view_view.dart'; @@ -55,7 +56,8 @@ import 'package:sqflite_migration_service/sqflite_migration_service.dart'; MaterialRoute(page: SuperBlockView), MaterialRoute(page: ChallengeView), MaterialRoute(page: ProfileView), - MaterialRoute(page: WebViewView) + MaterialRoute(page: WebViewView), + MaterialRoute(page: SettingsView) ], dependencies: [ LazySingleton(classType: NavigationService), diff --git a/mobile-app/lib/app/app.router.dart b/mobile-app/lib/app/app.router.dart index fe0c1dbfe..1078591b5 100644 --- a/mobile-app/lib/app/app.router.dart +++ b/mobile-app/lib/app/app.router.dart @@ -24,6 +24,7 @@ import '../ui/views/forum/forum-user/forum_user_view.dart'; import '../ui/views/home/home_view.dart'; import '../ui/views/learn/challenge_editor/challenge_view.dart'; import '../ui/views/learn/learn-builders/superblock_builder.dart'; +import '../ui/views/learn/settings/settings_view.dart'; import '../ui/views/news/news-article/news_article_view.dart'; import '../ui/views/news/news-author/news_author_view.dart'; import '../ui/views/news/news-bookmark/news_bookmark_view.dart'; @@ -58,6 +59,7 @@ class Routes { static const String challengeView = '/challenge-view'; static const String profileView = '/profile-view'; static const String webViewView = '/web-view-view'; + static const String settingsView = '/settings-view'; static const all = { homeView, podcastListView, @@ -80,6 +82,7 @@ class Routes { challengeView, profileView, webViewView, + settingsView, }; } @@ -108,6 +111,7 @@ class StackedRouter extends RouterBase { RouteDef(Routes.challengeView, page: ChallengeView), RouteDef(Routes.profileView, page: ProfileView), RouteDef(Routes.webViewView, page: WebViewView), + RouteDef(Routes.settingsView, page: SettingsView), ]; @override Map get pagesMap => _pagesMap; @@ -308,6 +312,12 @@ class StackedRouter extends RouterBase { settings: data, ); }, + SettingsView: (data) { + return MaterialPageRoute( + builder: (context) => const SettingsView(), + settings: data, + ); + }, }; } @@ -846,4 +856,20 @@ extension NavigatorStateExtension on NavigationService { transition: transition, ); } + + Future navigateToSettingsView({ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo( + Routes.settingsView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition, + ); + } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart new file mode 100644 index 000000000..26f632939 --- /dev/null +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -0,0 +1,3 @@ +import 'package:stacked/stacked.dart'; + +class SettingsModel extends BaseViewModel {} diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart new file mode 100644 index 000000000..6d66e0272 --- /dev/null +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:freecodecamp/ui/views/learn/settings/settings_model.dart'; +import 'package:stacked/stacked.dart'; + +class SettingsView extends StatelessWidget { + const SettingsView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => SettingsModel(), + builder: ((context, model, child) { + return Scaffold( + appBar: AppBar( + title: const Text('LEARN SETTINGS'), + centerTitle: true, + ), + body: Column( + children: const [], + ), + ); + })); + } + + Container button() { + return Container(); + } +} diff --git a/mobile-app/lib/ui/views/profile/profile_viemodel.dart b/mobile-app/lib/ui/views/profile/profile_viemodel.dart index 4d7101320..8d951568c 100644 --- a/mobile-app/lib/ui/views/profile/profile_viemodel.dart +++ b/mobile-app/lib/ui/views/profile/profile_viemodel.dart @@ -1,8 +1,15 @@ import 'package:freecodecamp/app/app.locator.dart'; +import 'package:freecodecamp/app/app.router.dart'; import 'package:freecodecamp/service/authentication_service.dart'; import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; // import 'dart:developer'; class ProfileViewModel extends BaseViewModel { final AuthenticationService auth = locator(); + final NavigationService navigationService = locator(); + + void gotoSettings() { + navigationService.navigateTo(Routes.settingsView); + } } diff --git a/mobile-app/lib/ui/views/profile/profile_view.dart b/mobile-app/lib/ui/views/profile/profile_view.dart index 72f3f5525..47722a0fc 100644 --- a/mobile-app/lib/ui/views/profile/profile_view.dart +++ b/mobile-app/lib/ui/views/profile/profile_view.dart @@ -200,6 +200,11 @@ class ProfileView extends StatelessWidget { ], ), buildDivider(), + TextButton( + child: const Text('settings'), + onPressed: () { + model.gotoSettings(); + }), ListView( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, From 035c81c61d9930035ca9546ade7666e4ec3aa15f Mon Sep 17 00:00:00 2001 From: SemBauke Date: Mon, 5 Sep 2022 15:36:51 +0200 Subject: [PATCH 02/19] feat: create text fields --- .../views/learn/settings/settings_view.dart | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 6d66e0272..608946ebe 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -15,13 +15,53 @@ class SettingsView extends StatelessWidget { title: const Text('LEARN SETTINGS'), centerTitle: true, ), - body: Column( - children: const [], + body: SingleChildScrollView( + child: Row( + children: [ + Expanded( + child: Column( + children: [ + textfield(context, 'Username'), + textfield(context, 'Name'), + textfield(context, 'Location'), + textfield(context, 'Picture'), + textfield(context, 'About', 5), + ], + ), + ) + ], + ), ), ); })); } + Container textfield(BuildContext context, String label, [int? maxLines]) { + return Container( + padding: const EdgeInsets.all(16), + width: MediaQuery.of(context).size.width * 0.8, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + TextField( + maxLines: maxLines ?? 1, + decoration: const InputDecoration( + border: OutlineInputBorder(), + filled: true, + fillColor: Color(0xFF0a0a23)), + ), + ], + ), + ); + } + Container button() { return Container(); } From dfb1dcb40e3a33ffb0753791456de6d8559de7f1 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 10:55:13 +0200 Subject: [PATCH 03/19] feat: add switch buttons --- mobile-app/lib/app/app.router.dart | 17 ++- .../lib/models/main/profile_ui_model.dart | 15 +++ .../views/learn/settings/settings_model.dart | 26 ++++- .../views/learn/settings/settings_view.dart | 107 +++++++++++++++++- .../ui/views/profile/profile_viemodel.dart | 6 +- .../lib/ui/views/profile/profile_view.dart | 2 +- 6 files changed, 165 insertions(+), 8 deletions(-) diff --git a/mobile-app/lib/app/app.router.dart b/mobile-app/lib/app/app.router.dart index 1078591b5..e6d4aa327 100644 --- a/mobile-app/lib/app/app.router.dart +++ b/mobile-app/lib/app/app.router.dart @@ -11,6 +11,7 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import '../models/learn/curriculum_model.dart'; +import '../models/main/user_model.dart'; import '../models/news/bookmarked_article_model.dart'; import '../models/podcasts/episodes_model.dart'; import '../models/podcasts/podcasts_model.dart'; @@ -313,8 +314,12 @@ class StackedRouter extends RouterBase { ); }, SettingsView: (data) { + var args = data.getArgs(nullOk: false); return MaterialPageRoute( - builder: (context) => const SettingsView(), + builder: (context) => SettingsView( + key: args.key, + user: args.user, + ), settings: data, ); }, @@ -448,6 +453,13 @@ class WebViewViewArguments { WebViewViewArguments({this.key, required this.url}); } +/// SettingsView arguments holder class +class SettingsViewArguments { + final Key? key; + final FccUserModel user; + SettingsViewArguments({this.key, required this.user}); +} + /// ************************************************************************ /// Extension for strongly typed navigation /// ************************************************************************* @@ -858,6 +870,8 @@ extension NavigatorStateExtension on NavigationService { } Future navigateToSettingsView({ + Key? key, + required FccUserModel user, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -866,6 +880,7 @@ extension NavigatorStateExtension on NavigationService { }) async { return navigateTo( Routes.settingsView, + arguments: SettingsViewArguments(key: key, user: user), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, diff --git a/mobile-app/lib/models/main/profile_ui_model.dart b/mobile-app/lib/models/main/profile_ui_model.dart index 1c6fe42ce..f205022ad 100644 --- a/mobile-app/lib/models/main/profile_ui_model.dart +++ b/mobile-app/lib/models/main/profile_ui_model.dart @@ -35,4 +35,19 @@ class ProfileUI { showPortfolio: data['showPortfolio'], showTimeLine: data['showTimeLine']); } + + static Map toMap(ProfileUI data) { + return { + 'isLocked': data.isLocked, + 'showAbout': data.showAbout, + 'showCerts': data.showCerts, + 'showDonation': data.showDonation, + 'showHeatMap': data.showHeatMap, + 'showLocation': data.showLocation, + 'showName': data.showName, + 'showPoints': data.showPoints, + 'showPortfolio': data.showPortfolio, + 'showTimeLine': data.showTimeLine, + }; + } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index 26f632939..510b2a0aa 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -1,3 +1,27 @@ +import 'package:freecodecamp/models/main/profile_ui_model.dart'; import 'package:stacked/stacked.dart'; -class SettingsModel extends BaseViewModel {} +class SettingsModel extends BaseViewModel { + Map? profile; + + set setProfile(Map ui) { + profile = ui; + notifyListeners(); + } + + void init(ProfileUI profile) { + setProfile = ProfileUI.toMap(profile); + } + + void setNewValue(String flag, bool value) { + List profileKeys = profile!.keys.toList(); + + Map scopedProfile = profile!; + + if (profileKeys.contains(flag)) { + scopedProfile[flag] = value; + + setProfile = scopedProfile; + } + } +} diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 608946ebe..d69aac371 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -1,14 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:freecodecamp/models/main/user_model.dart'; import 'package:freecodecamp/ui/views/learn/settings/settings_model.dart'; import 'package:stacked/stacked.dart'; class SettingsView extends StatelessWidget { - const SettingsView({Key? key}) : super(key: key); + const SettingsView({Key? key, required this.user}) : super(key: key); + + final FccUserModel user; @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => SettingsModel(), + onModelReady: (model) => model.init(user.profileUI), builder: ((context, model, child) { return Scaffold( appBar: AppBar( @@ -22,10 +26,22 @@ class SettingsView extends StatelessWidget { child: Column( children: [ textfield(context, 'Username'), + button(context), + switchButton('isLocked', 'My profile', model), + switchButton('showName', 'My name', model), + switchButton('showLocation', 'My location', model), + switchButton('showAbout', 'My about', model), + switchButton('showPoints', 'My points', model), + switchButton('showHeatMap', 'My heatmap', model), + switchButton('showCerts', 'My certifications', model), + switchButton('showPortfolio', 'My portfolio', model), + switchButton('showTimeLine', 'My timeline', model), + switchButton('showDonation', 'My donations', model), textfield(context, 'Name'), textfield(context, 'Location'), textfield(context, 'Picture'), textfield(context, 'About', 5), + button(context), ], ), ) @@ -62,7 +78,92 @@ class SettingsView extends StatelessWidget { ); } - Container button() { - return Container(); + Widget button(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.725, + child: TextButton(onPressed: () {}, child: const Text('Save'))); + } + + Widget switchButton(String flag, String title, SettingsModel model) { + bool isPublic = model.profile![flag]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8, top: 16), + child: Text( + title, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + Row(children: [ + InkWell( + onTap: () { + model.setNewValue(flag, false); + }, + child: Container( + decoration: BoxDecoration( + color: !isPublic ? Colors.white : const Color(0x00858591), + border: Border.all( + width: 2, color: const Color.fromARGB(255, 230, 230, 230)), + ), + width: 125, + height: 40, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (!isPublic) + const Icon( + Icons.check, + color: Colors.black, + size: 15, + ), + Text( + 'Private', + style: TextStyle( + color: !isPublic ? Colors.black : Colors.white, + fontSize: 16, + fontWeight: FontWeight.w200, + fontFamily: 'RobotoMono'), + ), + ], + ), + ), + ), + InkWell( + onTap: () { + model.setNewValue(flag, true); + }, + child: Container( + decoration: BoxDecoration( + border: Border.all(width: 2, color: Colors.white), + color: isPublic + ? const Color.fromARGB(255, 230, 230, 230) + : const Color(0x002a2a40)), + width: 125, + height: 40, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Public', + style: TextStyle( + color: isPublic ? Colors.black : Colors.white, + fontSize: 16, + fontWeight: FontWeight.w200, + fontFamily: 'RobotoMono')), + if (isPublic) + const Icon( + Icons.check, + color: Colors.black, + size: 15, + ), + ], + ), + ), + ) + ]), + ], + ); } } diff --git a/mobile-app/lib/ui/views/profile/profile_viemodel.dart b/mobile-app/lib/ui/views/profile/profile_viemodel.dart index 8d951568c..6aad38f86 100644 --- a/mobile-app/lib/ui/views/profile/profile_viemodel.dart +++ b/mobile-app/lib/ui/views/profile/profile_viemodel.dart @@ -1,5 +1,6 @@ import 'package:freecodecamp/app/app.locator.dart'; import 'package:freecodecamp/app/app.router.dart'; +import 'package:freecodecamp/models/main/user_model.dart'; import 'package:freecodecamp/service/authentication_service.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -9,7 +10,8 @@ class ProfileViewModel extends BaseViewModel { final AuthenticationService auth = locator(); final NavigationService navigationService = locator(); - void gotoSettings() { - navigationService.navigateTo(Routes.settingsView); + void gotoSettings(FccUserModel user) { + navigationService.navigateTo(Routes.settingsView, + arguments: SettingsViewArguments(user: user)); } } diff --git a/mobile-app/lib/ui/views/profile/profile_view.dart b/mobile-app/lib/ui/views/profile/profile_view.dart index 47722a0fc..f12f9fc18 100644 --- a/mobile-app/lib/ui/views/profile/profile_view.dart +++ b/mobile-app/lib/ui/views/profile/profile_view.dart @@ -203,7 +203,7 @@ class ProfileView extends StatelessWidget { TextButton( child: const Text('settings'), onPressed: () { - model.gotoSettings(); + model.gotoSettings(user); }), ListView( physics: const NeverScrollableScrollPhysics(), From 361e4bc047572139416ae90b676463b6f98de3e1 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 11:53:15 +0200 Subject: [PATCH 04/19] feat: update profile function --- mobile-app/lib/app/app.router.dart | 17 +----------- .../lib/service/authentication_service.dart | 2 ++ mobile-app/lib/service/learn_service.dart | 20 ++++++++++++++ .../views/learn/settings/settings_model.dart | 27 ++++++++++++++++--- .../views/learn/settings/settings_view.dart | 24 ++++++++++------- .../ui/views/profile/profile_viemodel.dart | 5 ++-- 6 files changed, 63 insertions(+), 32 deletions(-) diff --git a/mobile-app/lib/app/app.router.dart b/mobile-app/lib/app/app.router.dart index e6d4aa327..1078591b5 100644 --- a/mobile-app/lib/app/app.router.dart +++ b/mobile-app/lib/app/app.router.dart @@ -11,7 +11,6 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import '../models/learn/curriculum_model.dart'; -import '../models/main/user_model.dart'; import '../models/news/bookmarked_article_model.dart'; import '../models/podcasts/episodes_model.dart'; import '../models/podcasts/podcasts_model.dart'; @@ -314,12 +313,8 @@ class StackedRouter extends RouterBase { ); }, SettingsView: (data) { - var args = data.getArgs(nullOk: false); return MaterialPageRoute( - builder: (context) => SettingsView( - key: args.key, - user: args.user, - ), + builder: (context) => const SettingsView(), settings: data, ); }, @@ -453,13 +448,6 @@ class WebViewViewArguments { WebViewViewArguments({this.key, required this.url}); } -/// SettingsView arguments holder class -class SettingsViewArguments { - final Key? key; - final FccUserModel user; - SettingsViewArguments({this.key, required this.user}); -} - /// ************************************************************************ /// Extension for strongly typed navigation /// ************************************************************************* @@ -870,8 +858,6 @@ extension NavigatorStateExtension on NavigationService { } Future navigateToSettingsView({ - Key? key, - required FccUserModel user, int? routerId, bool preventDuplicates = true, Map? parameters, @@ -880,7 +866,6 @@ extension NavigatorStateExtension on NavigationService { }) async { return navigateTo( Routes.settingsView, - arguments: SettingsViewArguments(key: key, user: user), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, diff --git a/mobile-app/lib/service/authentication_service.dart b/mobile-app/lib/service/authentication_service.dart index 27075821c..614755497 100644 --- a/mobile-app/lib/service/authentication_service.dart +++ b/mobile-app/lib/service/authentication_service.dart @@ -149,6 +149,8 @@ class AuthenticationService { } Future fetchUser() async { + log('fetching user'); + Response res = await _dio.get( '/user/get-session-user', options: Options( diff --git a/mobile-app/lib/service/learn_service.dart b/mobile-app/lib/service/learn_service.dart index 046ac0942..4f9fc47f3 100644 --- a/mobile-app/lib/service/learn_service.dart +++ b/mobile-app/lib/service/learn_service.dart @@ -23,6 +23,26 @@ class LearnService { _dio.interceptors.add(CurlLoggerDioInterceptor()); } + Future updateMyProfileUI(Map data) async { + Response res = await _dio.put( + '${AuthenticationService.baseApiURL}/update-my-profileui', + data: data, + options: Options( + headers: { + 'CSRF-Token': _authenticationService.csrfToken, + 'Cookie': + 'jwt_access_token=${_authenticationService.jwtAccessToken}; _csrf=${_authenticationService.csrf};', + }, + ), + ); + + if (res.statusCode == 200) { + log('it works on the first try surprisingly'); + } else { + log('KEKW'); + } + } + Future postChallengeCompleted( Challenge challenge, List challengeFiles) async { // NOTE: Assuming for now it's just HTML and JS challenges are being submitted diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index 510b2a0aa..333960a53 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -1,16 +1,28 @@ +import 'dart:developer'; + +import 'package:freecodecamp/app/app.locator.dart'; import 'package:freecodecamp/models/main/profile_ui_model.dart'; +import 'package:freecodecamp/models/main/user_model.dart'; +import 'package:freecodecamp/service/authentication_service.dart'; +import 'package:freecodecamp/service/learn_service.dart'; import 'package:stacked/stacked.dart'; class SettingsModel extends BaseViewModel { - Map? profile; + late Map? profile; + + final AuthenticationService _auth = locator(); + final LearnService _learnService = locator(); set setProfile(Map ui) { profile = ui; notifyListeners(); } - void init(ProfileUI profile) { - setProfile = ProfileUI.toMap(profile); + void init() async { + await _auth.init(); + FccUserModel? user = await _auth.userModel; + + setProfile = ProfileUI.toMap(user!.profileUI); } void setNewValue(String flag, bool value) { @@ -19,9 +31,16 @@ class SettingsModel extends BaseViewModel { Map scopedProfile = profile!; if (profileKeys.contains(flag)) { - scopedProfile[flag] = value; + flag != 'isLocked' + ? scopedProfile[flag] = value + : scopedProfile[flag] = !value; setProfile = scopedProfile; } } + + save() { + log(profile.toString()); + _learnService.updateMyProfileUI(profile!); + } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index d69aac371..4d0112530 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -1,18 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:freecodecamp/models/main/user_model.dart'; + import 'package:freecodecamp/ui/views/learn/settings/settings_model.dart'; import 'package:stacked/stacked.dart'; class SettingsView extends StatelessWidget { - const SettingsView({Key? key, required this.user}) : super(key: key); - - final FccUserModel user; + const SettingsView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => SettingsModel(), - onModelReady: (model) => model.init(user.profileUI), + onModelReady: (model) => model.init(), builder: ((context, model, child) { return Scaffold( appBar: AppBar( @@ -26,7 +24,7 @@ class SettingsView extends StatelessWidget { child: Column( children: [ textfield(context, 'Username'), - button(context), + //button(context), switchButton('isLocked', 'My profile', model), switchButton('showName', 'My name', model), switchButton('showLocation', 'My location', model), @@ -37,11 +35,12 @@ class SettingsView extends StatelessWidget { switchButton('showPortfolio', 'My portfolio', model), switchButton('showTimeLine', 'My timeline', model), switchButton('showDonation', 'My donations', model), + button(context, model), textfield(context, 'Name'), textfield(context, 'Location'), textfield(context, 'Picture'), textfield(context, 'About', 5), - button(context), + //button(context), ], ), ) @@ -78,14 +77,19 @@ class SettingsView extends StatelessWidget { ); } - Widget button(BuildContext context) { + Widget button(BuildContext context, SettingsModel model) { return SizedBox( width: MediaQuery.of(context).size.width * 0.725, - child: TextButton(onPressed: () {}, child: const Text('Save'))); + child: TextButton( + onPressed: () { + model.save(); + }, + child: const Text('Save'))); } Widget switchButton(String flag, String title, SettingsModel model) { - bool isPublic = model.profile![flag]; + bool isPublic = + flag != 'isLocked' ? model.profile![flag] : !model.profile![flag]; return Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/mobile-app/lib/ui/views/profile/profile_viemodel.dart b/mobile-app/lib/ui/views/profile/profile_viemodel.dart index 6aad38f86..7599721fd 100644 --- a/mobile-app/lib/ui/views/profile/profile_viemodel.dart +++ b/mobile-app/lib/ui/views/profile/profile_viemodel.dart @@ -11,7 +11,8 @@ class ProfileViewModel extends BaseViewModel { final NavigationService navigationService = locator(); void gotoSettings(FccUserModel user) { - navigationService.navigateTo(Routes.settingsView, - arguments: SettingsViewArguments(user: user)); + navigationService.navigateTo( + Routes.settingsView, + ); } } From af539ce1d3dc026064c1cad68e23171744df0745 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 12:04:37 +0200 Subject: [PATCH 05/19] fix: profile data --- mobile-app/lib/ui/views/learn/settings/settings_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index 333960a53..e62da47e3 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -41,6 +41,6 @@ class SettingsModel extends BaseViewModel { save() { log(profile.toString()); - _learnService.updateMyProfileUI(profile!); + _learnService.updateMyProfileUI({'profileUI': profile!}); } } From 89fa9013cd9e9f337668828ec63915801dc3e455 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 12:32:43 +0200 Subject: [PATCH 06/19] fix: add future builder for user fetching --- .../views/learn/settings/settings_model.dart | 31 ++++- .../views/learn/settings/settings_view.dart | 114 +++++++++++------- 2 files changed, 99 insertions(+), 46 deletions(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index e62da47e3..254718f8a 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -10,19 +10,42 @@ import 'package:stacked/stacked.dart'; class SettingsModel extends BaseViewModel { late Map? profile; - final AuthenticationService _auth = locator(); + final AuthenticationService auth = locator(); final LearnService _learnService = locator(); + Future? userFuture; + set setProfile(Map ui) { profile = ui; notifyListeners(); } + set setUserFuture(Future userLoaded) { + userFuture = userLoaded; + notifyListeners(); + } + void init() async { - await _auth.init(); - FccUserModel? user = await _auth.userModel; + await auth.init(); + + setUserFuture = auth.userModel!; + + FccUserModel? user = await userFuture!; + + setProfile = ProfileUI.toMap(user.profileUI); + } + + String? getDescriptions(String flag) { + switch (flag) { + case 'isLocked': + return '''Your certifications will be disabled, if set to private.'''; + case 'showName': + return '''Your name will not appear on your certifications, if this is set to private.'''; + case 'showCerts': + return '''Your certifications will be disabled, if set to private.'''; + } - setProfile = ProfileUI.toMap(user!.profileUI); + return null; } void setNewValue(String flag, bool value) { diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 4d0112530..418790e5c 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -18,43 +18,55 @@ class SettingsView extends StatelessWidget { centerTitle: true, ), body: SingleChildScrollView( - child: Row( - children: [ - Expanded( - child: Column( - children: [ - textfield(context, 'Username'), - //button(context), - switchButton('isLocked', 'My profile', model), - switchButton('showName', 'My name', model), - switchButton('showLocation', 'My location', model), - switchButton('showAbout', 'My about', model), - switchButton('showPoints', 'My points', model), - switchButton('showHeatMap', 'My heatmap', model), - switchButton('showCerts', 'My certifications', model), - switchButton('showPortfolio', 'My portfolio', model), - switchButton('showTimeLine', 'My timeline', model), - switchButton('showDonation', 'My donations', model), - button(context, model), - textfield(context, 'Name'), - textfield(context, 'Location'), - textfield(context, 'Picture'), - textfield(context, 'About', 5), - //button(context), - ], - ), - ) - ], - ), - ), + child: FutureBuilder( + future: model.userFuture, + builder: ((context, snapshot) { + if (snapshot.hasData) { + return Row( + children: [ + Expanded( + child: Column( + children: [ + textfield('Username'), + //button(context), + switchButton('isLocked', 'My profile', model), + switchButton('showName', 'My name', model), + switchButton('showLocation', 'My location', model), + switchButton('showAbout', 'My about', model), + switchButton('showPoints', 'My points', model), + switchButton('showHeatMap', 'My heatmap', model), + switchButton( + 'showCerts', 'My certifications', model), + switchButton( + 'showPortfolio', 'My portfolio', model), + switchButton('showTimeLine', 'My timeline', model), + switchButton('showDonation', 'My donations', model), + button(model), + textfield('Name'), + textfield('Location'), + textfield('Picture'), + textfield('About', 5), + //button(context), + ], + ), + ) + ], + ); + } + + return const Center( + child: CircularProgressIndicator(), + ); + }), + )), ); })); } - Container textfield(BuildContext context, String label, [int? maxLines]) { + Container textfield(String label, [int? maxLines]) { return Container( padding: const EdgeInsets.all(16), - width: MediaQuery.of(context).size.width * 0.8, + width: 340, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -77,9 +89,9 @@ class SettingsView extends StatelessWidget { ); } - Widget button(BuildContext context, SettingsModel model) { + Widget button(SettingsModel model) { return SizedBox( - width: MediaQuery.of(context).size.width * 0.725, + width: 300, child: TextButton( onPressed: () { model.save(); @@ -91,17 +103,35 @@ class SettingsView extends StatelessWidget { bool isPublic = flag != 'isLocked' ? model.profile![flag] : !model.profile![flag]; + String? description = model.getDescriptions(flag); + return Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8, top: 16), - child: Text( - title, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + Container( + width: 300, + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(bottom: 8, top: 16), + child: Text( + title, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), ), ), - Row(children: [ + if (description != null) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: SizedBox( + width: 300, + child: Text( + description, + style: const TextStyle( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + color: Color.fromRGBO(0xd0, 0xd0, 0xd5, 1)), + )), + ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ InkWell( onTap: () { model.setNewValue(flag, false); @@ -112,7 +142,7 @@ class SettingsView extends StatelessWidget { border: Border.all( width: 2, color: const Color.fromARGB(255, 230, 230, 230)), ), - width: 125, + width: 150, height: 40, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -145,7 +175,7 @@ class SettingsView extends StatelessWidget { color: isPublic ? const Color.fromARGB(255, 230, 230, 230) : const Color(0x002a2a40)), - width: 125, + width: 150, height: 40, child: Row( mainAxisAlignment: MainAxisAlignment.center, From 5421b23c0791f1d573ff4f46731ea6ce8876cee3 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 12:38:12 +0200 Subject: [PATCH 07/19] feat: show message on success update --- mobile-app/lib/service/learn_service.dart | 8 ++++---- .../ui/views/learn/settings/settings_model.dart | 17 ++++++++++++----- .../ui/views/learn/settings/settings_view.dart | 12 ++++++------ 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/mobile-app/lib/service/learn_service.dart b/mobile-app/lib/service/learn_service.dart index 4f9fc47f3..d10cabefa 100644 --- a/mobile-app/lib/service/learn_service.dart +++ b/mobile-app/lib/service/learn_service.dart @@ -23,7 +23,7 @@ class LearnService { _dio.interceptors.add(CurlLoggerDioInterceptor()); } - Future updateMyProfileUI(Map data) async { + Future updateMyProfileUI(Map data) async { Response res = await _dio.put( '${AuthenticationService.baseApiURL}/update-my-profileui', data: data, @@ -37,10 +37,10 @@ class LearnService { ); if (res.statusCode == 200) { - log('it works on the first try surprisingly'); - } else { - log('KEKW'); + return true; } + + return false; } Future postChallengeCompleted( diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index 254718f8a..435530869 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -1,17 +1,17 @@ -import 'dart:developer'; - import 'package:freecodecamp/app/app.locator.dart'; import 'package:freecodecamp/models/main/profile_ui_model.dart'; import 'package:freecodecamp/models/main/user_model.dart'; import 'package:freecodecamp/service/authentication_service.dart'; import 'package:freecodecamp/service/learn_service.dart'; import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; class SettingsModel extends BaseViewModel { late Map? profile; final AuthenticationService auth = locator(); final LearnService _learnService = locator(); + final SnackbarService _snackbarService = locator(); Future? userFuture; @@ -62,8 +62,15 @@ class SettingsModel extends BaseViewModel { } } - save() { - log(profile.toString()); - _learnService.updateMyProfileUI({'profileUI': profile!}); + void save() async { + bool updated = + await _learnService.updateMyProfileUI({'profileUI': profile!}); + + if (updated) { + _snackbarService.showSnackbar( + title: 'updated settings successfully', message: ''); + } else { + _snackbarService.showSnackbar(title: 'something went wrong', message: ''); + } } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 418790e5c..23ec3f539 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -28,7 +28,12 @@ class SettingsView extends StatelessWidget { child: Column( children: [ textfield('Username'), - //button(context), + button(model), + textfield('Name'), + textfield('Location'), + textfield('Picture'), + textfield('About', 5), + button(model), switchButton('isLocked', 'My profile', model), switchButton('showName', 'My name', model), switchButton('showLocation', 'My location', model), @@ -42,11 +47,6 @@ class SettingsView extends StatelessWidget { switchButton('showTimeLine', 'My timeline', model), switchButton('showDonation', 'My donations', model), button(model), - textfield('Name'), - textfield('Location'), - textfield('Picture'), - textfield('About', 5), - //button(context), ], ), ) From 83719f2200a2ad9f462a91baf7cec9bdcca22ee7 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 12:45:46 +0200 Subject: [PATCH 08/19] feat: inital values for textfields --- .../views/learn/settings/settings_view.dart | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 23ec3f539..5ee9e0def 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:freecodecamp/models/main/user_model.dart'; import 'package:freecodecamp/ui/views/learn/settings/settings_model.dart'; import 'package:stacked/stacked.dart'; @@ -18,21 +19,23 @@ class SettingsView extends StatelessWidget { centerTitle: true, ), body: SingleChildScrollView( - child: FutureBuilder( + child: FutureBuilder( future: model.userFuture, builder: ((context, snapshot) { if (snapshot.hasData) { + FccUserModel user = snapshot.data as FccUserModel; + return Row( children: [ Expanded( child: Column( children: [ - textfield('Username'), + textfield('Username', user.username), button(model), - textfield('Name'), - textfield('Location'), - textfield('Picture'), - textfield('About', 5), + textfield('Name', user.name), + textfield('Location', user.location), + textfield('Picture', user.picture), + textfield('About', user.about, 5), button(model), switchButton('isLocked', 'My profile', model), switchButton('showName', 'My name', model), @@ -63,7 +66,7 @@ class SettingsView extends StatelessWidget { })); } - Container textfield(String label, [int? maxLines]) { + Container textfield(String label, String? initValue, [int? maxLines]) { return Container( padding: const EdgeInsets.all(16), width: 340, @@ -77,7 +80,8 @@ class SettingsView extends StatelessWidget { style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), ), - TextField( + TextFormField( + initialValue: initValue, maxLines: maxLines ?? 1, decoration: const InputDecoration( border: OutlineInputBorder(), From d4b7e3b460670baf894f2028a79f99d07c0687af Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 13:53:53 +0200 Subject: [PATCH 09/19] feat: check if username is taken --- mobile-app/lib/service/learn_service.dart | 19 ++++++++ .../views/learn/settings/settings_model.dart | 44 +++++++++++++++++++ .../views/learn/settings/settings_view.dart | 42 +++++++++++++++++- 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/mobile-app/lib/service/learn_service.dart b/mobile-app/lib/service/learn_service.dart index d10cabefa..30c9b4d58 100644 --- a/mobile-app/lib/service/learn_service.dart +++ b/mobile-app/lib/service/learn_service.dart @@ -43,6 +43,25 @@ class LearnService { return false; } + Future checkIfUsernameIsTaken(String name) async { + Response res = await _dio.get( + '${AuthenticationService.baseApiURL}/api/users/exists?username=$name', + options: Options( + headers: { + 'CSRF-Token': _authenticationService.csrfToken, + 'Cookie': + 'jwt_access_token=${_authenticationService.jwtAccessToken}; _csrf=${_authenticationService.csrf};', + }, + ), + ); + + if (res.statusCode == 200) { + return res.data['exists']; + } + + return true; + } + Future postChallengeCompleted( Challenge challenge, List challengeFiles) async { // NOTE: Assuming for now it's just HTML and JS challenges are being submitted diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index 435530869..1e7b03c30 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:freecodecamp/app/app.locator.dart'; import 'package:freecodecamp/models/main/profile_ui_model.dart'; import 'package:freecodecamp/models/main/user_model.dart'; @@ -15,6 +17,11 @@ class SettingsModel extends BaseViewModel { Future? userFuture; + String? helperText; + String? errorText; + + Timer? usernameSearchCoolDown; + set setProfile(Map ui) { profile = ui; notifyListeners(); @@ -25,6 +32,16 @@ class SettingsModel extends BaseViewModel { notifyListeners(); } + set setHelperText(String? text) { + helperText = text; + notifyListeners(); + } + + set setErrorText(String? text) { + errorText = text; + notifyListeners(); + } + void init() async { await auth.init(); @@ -73,4 +90,31 @@ class SettingsModel extends BaseViewModel { _snackbarService.showSnackbar(title: 'something went wrong', message: ''); } } + + void searchUsername(String username) { + void searchUsername() async { + bool isUsernameTaken = + await _learnService.checkIfUsernameIsTaken(username); + + if (isUsernameTaken) { + setHelperText = null; + setErrorText = 'username is taken'; + } else { + setErrorText = null; + setHelperText = 'username is available'; + } + } + + if (usernameSearchCoolDown == null) { + setHelperText = 'searching..'; + setErrorText = null; + usernameSearchCoolDown = + Timer(const Duration(seconds: 2), searchUsername); + } else if (!usernameSearchCoolDown!.isActive) { + setErrorText = null; + setHelperText = 'searching..'; + usernameSearchCoolDown = + Timer(const Duration(seconds: 2), searchUsername); + } + } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 5ee9e0def..94c7dedd2 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -30,7 +30,7 @@ class SettingsView extends StatelessWidget { Expanded( child: Column( children: [ - textfield('Username', user.username), + textfieldUsername('Username', user.username, model), button(model), textfield('Name', user.name), textfield('Location', user.location), @@ -93,6 +93,46 @@ class SettingsView extends StatelessWidget { ); } + Widget textfieldUsername( + String label, String? initValue, SettingsModel model) { + return Column( + children: [ + Container( + padding: const EdgeInsets.all(16), + width: 340, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + label, + style: const TextStyle( + fontWeight: FontWeight.bold, fontSize: 16), + ), + ), + TextFormField( + initialValue: initValue, + onChanged: (String changedName) { + model.searchUsername(changedName); + }, + decoration: InputDecoration( + helperText: model.helperText, + errorText: model.errorText, + helperStyle: model.helperText == 'username is available' + ? const TextStyle(color: Colors.green) + : null, + border: const OutlineInputBorder(), + filled: true, + fillColor: const Color(0xFF0a0a23)), + ), + ], + ), + ), + ], + ); + } + Widget button(SettingsModel model) { return SizedBox( width: 300, From efac38b8033ff454cea9ae1d93118c9f53b62be1 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 14:41:01 +0200 Subject: [PATCH 10/19] fix: use correct color scheme --- mobile-app/lib/ui/views/learn/settings/settings_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 94c7dedd2..fb1534fae 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -14,6 +14,7 @@ class SettingsView extends StatelessWidget { onModelReady: (model) => model.init(), builder: ((context, model, child) { return Scaffold( + backgroundColor: const Color.fromRGBO(0x1b, 0x1b, 0x32, 1), appBar: AppBar( title: const Text('LEARN SETTINGS'), centerTitle: true, @@ -182,7 +183,9 @@ class SettingsView extends StatelessWidget { }, child: Container( decoration: BoxDecoration( - color: !isPublic ? Colors.white : const Color(0x00858591), + color: !isPublic + ? Colors.white + : const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1), border: Border.all( width: 2, color: const Color.fromARGB(255, 230, 230, 230)), ), From ad94d56c42759b68451bc7b65e96eaf1d86142d0 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 14:46:24 +0200 Subject: [PATCH 11/19] fix: use correct color on public button --- mobile-app/lib/ui/views/learn/settings/settings_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index fb1534fae..40ceb2aaf 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -221,7 +221,7 @@ class SettingsView extends StatelessWidget { border: Border.all(width: 2, color: Colors.white), color: isPublic ? const Color.fromARGB(255, 230, 230, 230) - : const Color(0x002a2a40)), + : const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1)), width: 150, height: 40, child: Row( From 879f79ddc4a8f2d229d60493e84134f1bbcabf14 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Wed, 7 Sep 2022 15:30:07 +0200 Subject: [PATCH 12/19] feat: enhance username validation --- .../views/learn/settings/settings_model.dart | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index 1e7b03c30..d0f71e638 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -92,29 +92,54 @@ class SettingsModel extends BaseViewModel { } void searchUsername(String username) { + Future validateUsername() async { + RegExp validChars = + RegExp(r'^[a-zA-Z0-9\-_+]*$', multiLine: true, unicode: true); + + int? intUsername = int.tryParse(username); + + if (username.length <= 2) { + setErrorText = 'name is too short'; + return false; + } + + if (!validChars.hasMatch(username)) { + setErrorText = 'contains invalid characters'; + return false; + } + + if (intUsername != null) { + if (intUsername >= 100 && intUsername <= 599) { + setErrorText = '$username is a reserved error code'; + return false; + } + } + + if (await _learnService.checkIfUsernameIsTaken(username)) { + setErrorText = 'username is already taken'; + return false; + } + + return true; + } + void searchUsername() async { - bool isUsernameTaken = - await _learnService.checkIfUsernameIsTaken(username); + bool usernameIsValid = await validateUsername(); - if (isUsernameTaken) { - setHelperText = null; - setErrorText = 'username is taken'; - } else { - setErrorText = null; + if (usernameIsValid) { setHelperText = 'username is available'; + setErrorText = null; } } if (usernameSearchCoolDown == null) { setHelperText = 'searching..'; setErrorText = null; - usernameSearchCoolDown = - Timer(const Duration(seconds: 2), searchUsername); + searchUsername(); } else if (!usernameSearchCoolDown!.isActive) { setErrorText = null; setHelperText = 'searching..'; - usernameSearchCoolDown = - Timer(const Duration(seconds: 2), searchUsername); + searchUsername(); } } } From 8983aa69c4bb00d326c76ed72ddaca8ada3e3c03 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Thu, 8 Sep 2022 13:21:47 +0200 Subject: [PATCH 13/19] feat: add update username endpoint --- mobile-app/lib/service/learn_service.dart | 20 +++++ .../views/learn/settings/settings_model.dart | 76 ++++++++++--------- .../views/learn/settings/settings_view.dart | 16 +++- mobile-app/pubspec.lock | 41 +++++----- 4 files changed, 94 insertions(+), 59 deletions(-) diff --git a/mobile-app/lib/service/learn_service.dart b/mobile-app/lib/service/learn_service.dart index 30c9b4d58..1f610bd99 100644 --- a/mobile-app/lib/service/learn_service.dart +++ b/mobile-app/lib/service/learn_service.dart @@ -43,6 +43,26 @@ class LearnService { return false; } + Future updateUsername(String name) async { + Response res = await _dio.put( + '${AuthenticationService.baseApiURL}/update-my-username', + data: {'username': name}, + options: Options( + headers: { + 'CSRF-Token': _authenticationService.csrfToken, + 'Cookie': + 'jwt_access_token=${_authenticationService.jwtAccessToken}; _csrf=${_authenticationService.csrf};', + }, + ), + ); + + if (res.statusCode == 200) { + return true; + } else { + return false; + } + } + Future checkIfUsernameIsTaken(String name) async { Response res = await _dio.get( '${AuthenticationService.baseApiURL}/api/users/exists?username=$name', diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index d0f71e638..a65e2aea5 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -17,11 +17,11 @@ class SettingsModel extends BaseViewModel { Future? userFuture; + String? username; + String? helperText; String? errorText; - Timer? usernameSearchCoolDown; - set setProfile(Map ui) { profile = ui; notifyListeners(); @@ -32,6 +32,11 @@ class SettingsModel extends BaseViewModel { notifyListeners(); } + set setUsername(String name) { + username = name; + notifyListeners(); + } + set setHelperText(String? text) { helperText = text; notifyListeners(); @@ -91,40 +96,49 @@ class SettingsModel extends BaseViewModel { } } - void searchUsername(String username) { - Future validateUsername() async { - RegExp validChars = - RegExp(r'^[a-zA-Z0-9\-_+]*$', multiLine: true, unicode: true); + void updateUsername(String username) async { + if (await _learnService.updateUsername(username)) { + _snackbarService.showSnackbar( + title: 'username updated successfully', message: ''); + } else { + _snackbarService.showSnackbar(title: 'something went wrong', message: ''); + } + } - int? intUsername = int.tryParse(username); + Future validateUsername(String username) async { + RegExp validChars = + RegExp(r'^[a-zA-Z0-9\-_+]*$', multiLine: true, unicode: true); - if (username.length <= 2) { - setErrorText = 'name is too short'; - return false; - } + int? intUsername = int.tryParse(username); - if (!validChars.hasMatch(username)) { - setErrorText = 'contains invalid characters'; - return false; - } + if (username.length <= 2 || username.isEmpty) { + setErrorText = 'name is too short'; + return false; + } - if (intUsername != null) { - if (intUsername >= 100 && intUsername <= 599) { - setErrorText = '$username is a reserved error code'; - return false; - } - } + if (!validChars.hasMatch(username)) { + setErrorText = 'contains invalid characters'; + return false; + } - if (await _learnService.checkIfUsernameIsTaken(username)) { - setErrorText = 'username is already taken'; + if (intUsername != null) { + if (intUsername >= 100 && intUsername <= 599) { + setErrorText = '$username is a reserved error code'; return false; } + } - return true; + if (await _learnService.checkIfUsernameIsTaken(username)) { + setErrorText = 'username is already taken'; + return false; } + return true; + } + + void searchUsername(String username) { void searchUsername() async { - bool usernameIsValid = await validateUsername(); + bool usernameIsValid = await validateUsername(username); if (usernameIsValid) { setHelperText = 'username is available'; @@ -132,14 +146,8 @@ class SettingsModel extends BaseViewModel { } } - if (usernameSearchCoolDown == null) { - setHelperText = 'searching..'; - setErrorText = null; - searchUsername(); - } else if (!usernameSearchCoolDown!.isActive) { - setErrorText = null; - setHelperText = 'searching..'; - searchUsername(); - } + setHelperText = 'searching..'; + setErrorText = null; + searchUsername(); } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 40ceb2aaf..d65422f9a 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -32,7 +32,6 @@ class SettingsView extends StatelessWidget { child: Column( children: [ textfieldUsername('Username', user.username, model), - button(model), textfield('Name', user.name), textfield('Location', user.location), textfield('Picture', user.picture), @@ -116,6 +115,7 @@ class SettingsView extends StatelessWidget { initialValue: initValue, onChanged: (String changedName) { model.searchUsername(changedName); + model.setUsername = changedName; }, decoration: InputDecoration( helperText: model.helperText, @@ -130,6 +130,20 @@ class SettingsView extends StatelessWidget { ], ), ), + SizedBox( + width: 310, + child: TextButton( + style: TextButton.styleFrom( + side: const BorderSide(width: 2, color: Colors.white), + padding: const EdgeInsets.all(0), + backgroundColor: const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1), + ), + onPressed: () async { + if (model.errorText == null) { + model.updateUsername(model.username!); + } + }, + child: const Text('Save'))) ], ); } diff --git a/mobile-app/pubspec.lock b/mobile-app/pubspec.lock index e30996310..dd4c3a871 100644 --- a/mobile-app/pubspec.lock +++ b/mobile-app/pubspec.lock @@ -28,7 +28,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.11" + version: "3.3.0" args: dependency: transitive description: @@ -42,7 +42,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" audio_service: dependency: "direct main" description: @@ -161,14 +161,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" checked_yaml: dependency: transitive description: @@ -196,7 +189,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -231,7 +224,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" csslib: dependency: transitive description: @@ -336,7 +329,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -799,21 +792,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -855,7 +848,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -1196,7 +1189,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" sqflite: dependency: "direct main" description: @@ -1273,14 +1266,14 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" sync_http: dependency: transitive description: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.1" synchronized: dependency: transitive description: @@ -1294,14 +1287,14 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" timezone: dependency: transitive description: @@ -1329,7 +1322,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" universal_io: dependency: transitive description: @@ -1448,7 +1441,7 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "8.2.2" + version: "9.0.0" wakelock: dependency: transitive description: From 3dcf77ce1427db3a1cda1711fd28d3ed394cb470 Mon Sep 17 00:00:00 2001 From: SemBauke Date: Thu, 8 Sep 2022 13:53:54 +0200 Subject: [PATCH 14/19] feat: further improve username validation --- mobile-app/lib/service/learn_service.dart | 6 ++- .../views/learn/settings/settings_model.dart | 43 +++++++++++++------ .../views/learn/settings/settings_view.dart | 4 +- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/mobile-app/lib/service/learn_service.dart b/mobile-app/lib/service/learn_service.dart index 1f610bd99..c989982f0 100644 --- a/mobile-app/lib/service/learn_service.dart +++ b/mobile-app/lib/service/learn_service.dart @@ -10,8 +10,6 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart'; class LearnService { static final LearnService _learnService = LearnService._internal(); final _authenticationService = locator(); - - // TODO: make a Dio service instead of initialising it everywhere final Dio _dio = Dio(); factory LearnService() { @@ -57,6 +55,10 @@ class LearnService { ); if (res.statusCode == 200) { + if (res.data['type'] == 'info') { + return false; + } + return true; } else { return false; diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index a65e2aea5..cbc9a0a78 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:freecodecamp/app/app.locator.dart'; import 'package:freecodecamp/models/main/profile_ui_model.dart'; @@ -19,6 +20,8 @@ class SettingsModel extends BaseViewModel { String? username; + Timer? requestCooldonwTimer; + String? helperText; String? errorText; @@ -100,12 +103,14 @@ class SettingsModel extends BaseViewModel { if (await _learnService.updateUsername(username)) { _snackbarService.showSnackbar( title: 'username updated successfully', message: ''); + init(); } else { _snackbarService.showSnackbar(title: 'something went wrong', message: ''); } } - Future validateUsername(String username) async { + Future validateUsername(String username, String currName) async { + log(username); RegExp validChars = RegExp(r'^[a-zA-Z0-9\-_+]*$', multiLine: true, unicode: true); @@ -116,6 +121,11 @@ class SettingsModel extends BaseViewModel { return false; } + if (currName == username) { + setErrorText = 'this already is your username'; + return false; + } + if (!validChars.hasMatch(username)) { setErrorText = 'contains invalid characters'; return false; @@ -128,26 +138,35 @@ class SettingsModel extends BaseViewModel { } } - if (await _learnService.checkIfUsernameIsTaken(username)) { - setErrorText = 'username is already taken'; - return false; - } - return true; } - void searchUsername(String username) { - void searchUsername() async { - bool usernameIsValid = await validateUsername(username); + void searchUsername(String username, String currentName) { + void search() async { + bool usernameIsValid = await validateUsername(username, currentName); if (usernameIsValid) { - setHelperText = 'username is available'; - setErrorText = null; + if (await _learnService.checkIfUsernameIsTaken(username)) { + setErrorText = 'username is already taken'; + return; + } else { + setHelperText = 'username is available'; + setErrorText = null; + } } } + if (requestCooldonwTimer == null) { + log('timer was not set to begin with'); + requestCooldonwTimer = Timer(const Duration(seconds: 1), search); + } else if (!requestCooldonwTimer!.isActive) { + requestCooldonwTimer = Timer(const Duration(seconds: 1), search); + } else { + requestCooldonwTimer!.cancel(); + requestCooldonwTimer = Timer(const Duration(seconds: 1), search); + } + setHelperText = 'searching..'; setErrorText = null; - searchUsername(); } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index d65422f9a..72810ee8c 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -114,7 +114,7 @@ class SettingsView extends StatelessWidget { TextFormField( initialValue: initValue, onChanged: (String changedName) { - model.searchUsername(changedName); + model.searchUsername(changedName, initValue ?? ''); model.setUsername = changedName; }, decoration: InputDecoration( @@ -137,6 +137,8 @@ class SettingsView extends StatelessWidget { side: const BorderSide(width: 2, color: Colors.white), padding: const EdgeInsets.all(0), backgroundColor: const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1), + disabledBackgroundColor: + model.errorText != null ? const Color(0xFF0a0a23) : null, ), onPressed: () async { if (model.errorText == null) { From 9f1a20002af202aba39ff67e92377d412fd027da Mon Sep 17 00:00:00 2001 From: SemBauke Date: Tue, 13 Sep 2022 16:04:42 +0200 Subject: [PATCH 15/19] feat: save about --- mobile-app/lib/service/learn_service.dart | 20 +++++++++ .../views/learn/settings/settings_model.dart | 43 ++++++++++++++++++- .../views/learn/settings/settings_view.dart | 34 +++++++++++---- 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/mobile-app/lib/service/learn_service.dart b/mobile-app/lib/service/learn_service.dart index c989982f0..388342f73 100644 --- a/mobile-app/lib/service/learn_service.dart +++ b/mobile-app/lib/service/learn_service.dart @@ -21,6 +21,26 @@ class LearnService { _dio.interceptors.add(CurlLoggerDioInterceptor()); } + Future updateMyAbout(Map data) async { + Response res = await _dio.put( + '${AuthenticationService.baseApiURL}/update-my-about', + data: data, + options: Options( + headers: { + 'CSRF-Token': _authenticationService.csrfToken, + 'Cookie': + 'jwt_access_token=${_authenticationService.jwtAccessToken}; _csrf=${_authenticationService.csrf};', + }, + ), + ); + + if (res.statusCode == 200) { + return true; + } + + return false; + } + Future updateMyProfileUI(Map data) async { Response res = await _dio.put( '${AuthenticationService.baseApiURL}/update-my-profileui', diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index cbc9a0a78..e6e4d1893 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -25,11 +25,18 @@ class SettingsModel extends BaseViewModel { String? helperText; String? errorText; + Map? userInfo; + set setProfile(Map ui) { profile = ui; notifyListeners(); } + set setUserInfo(Map data) { + userInfo = data; + notifyListeners(); + } + set setUserFuture(Future userLoaded) { userFuture = userLoaded; notifyListeners(); @@ -58,6 +65,13 @@ class SettingsModel extends BaseViewModel { FccUserModel? user = await userFuture!; setProfile = ProfileUI.toMap(user.profileUI); + + setUserInfo = { + 'name': user.name, + 'location': user.location, + 'picture': user.picture, + 'about': user.about, + }; } String? getDescriptions(String flag) { @@ -87,7 +101,7 @@ class SettingsModel extends BaseViewModel { } } - void save() async { + save() async { bool updated = await _learnService.updateMyProfileUI({'profileUI': profile!}); @@ -169,4 +183,31 @@ class SettingsModel extends BaseViewModel { setHelperText = 'searching..'; setErrorText = null; } + + saveAbout() async { + if (userInfo != null) { + bool complete = await _learnService.updateMyAbout(userInfo!); + + if (complete) { + _snackbarService.showSnackbar( + title: 'info updated successfully', message: ''); + } else { + _snackbarService.showSnackbar( + title: 'something went wrong', message: ''); + } + } + } + + upateMyAbout(String flag, String value) { + if (userInfo != null) { + Map localUserInfo = userInfo!; + + List possibleFlags = ['location', 'about', 'picture', 'name']; + + if (possibleFlags.contains(flag)) { + localUserInfo[flag] = value; + setUserInfo = localUserInfo; + } + } + } } diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 72810ee8c..09feceeed 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -32,11 +32,11 @@ class SettingsView extends StatelessWidget { child: Column( children: [ textfieldUsername('Username', user.username, model), - textfield('Name', user.name), - textfield('Location', user.location), - textfield('Picture', user.picture), - textfield('About', user.about, 5), - button(model), + textfield('Name', model, user.name), + textfield('Location', model, user.location), + textfield('Picture', model, user.picture), + textfield('About', model, user.about, 5), + saveAboutButton(model), switchButton('isLocked', 'My profile', model), switchButton('showName', 'My name', model), switchButton('showLocation', 'My location', model), @@ -49,7 +49,7 @@ class SettingsView extends StatelessWidget { 'showPortfolio', 'My portfolio', model), switchButton('showTimeLine', 'My timeline', model), switchButton('showDonation', 'My donations', model), - button(model), + saveProfileButton(model), ], ), ) @@ -66,7 +66,12 @@ class SettingsView extends StatelessWidget { })); } - Container textfield(String label, String? initValue, [int? maxLines]) { + Container textfield( + String label, + SettingsModel model, + String? initValue, [ + int? maxLines, + ]) { return Container( padding: const EdgeInsets.all(16), width: 340, @@ -87,6 +92,9 @@ class SettingsView extends StatelessWidget { border: OutlineInputBorder(), filled: true, fillColor: Color(0xFF0a0a23)), + onChanged: (change) { + model.upateMyAbout(label.toLowerCase(), change); + }, ), ], ), @@ -150,7 +158,17 @@ class SettingsView extends StatelessWidget { ); } - Widget button(SettingsModel model) { + Widget saveAboutButton(SettingsModel model) { + return SizedBox( + width: 300, + child: TextButton( + onPressed: () { + model.saveAbout(); + }, + child: const Text('Save'))); + } + + Widget saveProfileButton(SettingsModel model) { return SizedBox( width: 300, child: TextButton( From 0afd1b3a232719b683d8d3c7679aca36cdf2adbb Mon Sep 17 00:00:00 2001 From: SemBauke Date: Tue, 13 Sep 2022 16:18:41 +0200 Subject: [PATCH 16/19] fix: state issues between views --- .../views/learn/settings/settings_view.dart | 100 +++++++++++------- .../ui/views/profile/profile_viemodel.dart | 2 +- 2 files changed, 60 insertions(+), 42 deletions(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 09feceeed..d9cc2854b 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:freecodecamp/models/main/user_model.dart'; import 'package:freecodecamp/ui/views/learn/settings/settings_model.dart'; +import 'package:freecodecamp/ui/views/profile/profile_view.dart'; import 'package:stacked/stacked.dart'; class SettingsView extends StatelessWidget { @@ -19,49 +20,66 @@ class SettingsView extends StatelessWidget { title: const Text('LEARN SETTINGS'), centerTitle: true, ), - body: SingleChildScrollView( - child: FutureBuilder( - future: model.userFuture, - builder: ((context, snapshot) { - if (snapshot.hasData) { - FccUserModel user = snapshot.data as FccUserModel; + body: WillPopScope( + onWillPop: () { + Navigator.pushReplacement( + context, + PageRouteBuilder( + transitionDuration: Duration.zero, + pageBuilder: (context, animation1, animation2) => + const ProfileView(), + ), + ); + return Future.delayed(const Duration(seconds: 0)); + }, + child: SingleChildScrollView( + child: FutureBuilder( + future: model.userFuture, + builder: ((context, snapshot) { + if (snapshot.hasData) { + FccUserModel user = snapshot.data as FccUserModel; - return Row( - children: [ - Expanded( - child: Column( - children: [ - textfieldUsername('Username', user.username, model), - textfield('Name', model, user.name), - textfield('Location', model, user.location), - textfield('Picture', model, user.picture), - textfield('About', model, user.about, 5), - saveAboutButton(model), - switchButton('isLocked', 'My profile', model), - switchButton('showName', 'My name', model), - switchButton('showLocation', 'My location', model), - switchButton('showAbout', 'My about', model), - switchButton('showPoints', 'My points', model), - switchButton('showHeatMap', 'My heatmap', model), - switchButton( - 'showCerts', 'My certifications', model), - switchButton( - 'showPortfolio', 'My portfolio', model), - switchButton('showTimeLine', 'My timeline', model), - switchButton('showDonation', 'My donations', model), - saveProfileButton(model), - ], - ), - ) - ], - ); - } + return Row( + children: [ + Expanded( + child: Column( + children: [ + textfieldUsername( + 'Username', user.username, model), + textfield('Name', model, user.name), + textfield('Location', model, user.location), + textfield('Picture', model, user.picture), + textfield('About', model, user.about, 5), + saveAboutButton(model), + switchButton('isLocked', 'My profile', model), + switchButton('showName', 'My name', model), + switchButton( + 'showLocation', 'My location', model), + switchButton('showAbout', 'My about', model), + switchButton('showPoints', 'My points', model), + switchButton('showHeatMap', 'My heatmap', model), + switchButton( + 'showCerts', 'My certifications', model), + switchButton( + 'showPortfolio', 'My portfolio', model), + switchButton( + 'showTimeLine', 'My timeline', model), + switchButton( + 'showDonation', 'My donations', model), + saveProfileButton(model), + ], + ), + ) + ], + ); + } - return const Center( - child: CircularProgressIndicator(), - ); - }), - )), + return const Center( + child: CircularProgressIndicator(), + ); + }), + )), + ), ); })); } diff --git a/mobile-app/lib/ui/views/profile/profile_viemodel.dart b/mobile-app/lib/ui/views/profile/profile_viemodel.dart index 7599721fd..324e26e9a 100644 --- a/mobile-app/lib/ui/views/profile/profile_viemodel.dart +++ b/mobile-app/lib/ui/views/profile/profile_viemodel.dart @@ -11,7 +11,7 @@ class ProfileViewModel extends BaseViewModel { final NavigationService navigationService = locator(); void gotoSettings(FccUserModel user) { - navigationService.navigateTo( + navigationService.pushNamedAndRemoveUntil( Routes.settingsView, ); } From e1482cf9e438e513decacd052cb0949ca470abaa Mon Sep 17 00:00:00 2001 From: SemBauke Date: Tue, 13 Sep 2022 16:42:36 +0200 Subject: [PATCH 17/19] feat: add settings to drawer instead --- mobile-app/lib/app/app.dart | 4 - mobile-app/lib/app/app.router.dart | 52 --------- .../views/learn/settings/settings_view.dart | 100 +++++++----------- .../ui/views/profile/profile_viemodel.dart | 8 -- .../lib/ui/views/profile/profile_view.dart | 5 - .../forumSettings/forum_settings_view.dart | 82 -------------- .../forum_settings_viewmodel.dart | 31 ------ .../podcast_settings_view.dart | 21 ---- .../podcast_settings_viewmodel.dart | 3 - .../lib/ui/views/settings/settings_view.dart | 77 -------------- .../ui/views/settings/settings_viewmodel.dart | 16 --- .../drawer_widget/drawer_widget_view.dart | 12 ++- .../drawer_widget_viewmodel.dart | 6 +- 13 files changed, 51 insertions(+), 366 deletions(-) delete mode 100644 mobile-app/lib/ui/views/settings/forumSettings/forum_settings_view.dart delete mode 100644 mobile-app/lib/ui/views/settings/forumSettings/forum_settings_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_view.dart delete mode 100644 mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/settings/settings_view.dart delete mode 100644 mobile-app/lib/ui/views/settings/settings_viewmodel.dart diff --git a/mobile-app/lib/app/app.dart b/mobile-app/lib/app/app.dart index 8c704f852..ebdfdd858 100644 --- a/mobile-app/lib/app/app.dart +++ b/mobile-app/lib/app/app.dart @@ -20,8 +20,6 @@ import 'package:freecodecamp/ui/views/podcast/episode-view/episode_view.dart'; import 'package:freecodecamp/ui/views/news/news-article/news_article_view.dart'; import 'package:freecodecamp/ui/views/news/news-bookmark/news_bookmark_view.dart'; import 'package:freecodecamp/ui/views/news/news-feed/news_feed_view.dart'; -import 'package:freecodecamp/ui/views/settings/forumSettings/forum_settings_view.dart'; -import 'package:freecodecamp/ui/views/settings/podcastSettings/podcast_settings_view.dart'; import 'package:freecodecamp/ui/views/learn/settings/settings_view.dart'; import 'package:freecodecamp/ui/views/learn/learn-builders/superblock_builder.dart'; import 'package:freecodecamp/ui/views/learn/challenge_editor/challenge_view.dart'; @@ -38,7 +36,6 @@ import 'package:sqflite_migration_service/sqflite_migration_service.dart'; routes: [ MaterialRoute(page: HomeView, initial: true), MaterialRoute(page: PodcastListView), - MaterialRoute(page: PodcastSettingsView), MaterialRoute(page: EpisodeView), MaterialRoute(page: NewsArticleView), MaterialRoute(page: NewsBookmarkPostView), @@ -50,7 +47,6 @@ import 'package:sqflite_migration_service/sqflite_migration_service.dart'; MaterialRoute(page: ForumPostView), MaterialRoute(page: ForumLoginView), MaterialRoute(page: ForumUserView), - MaterialRoute(page: ForumSettingsView), MaterialRoute(page: ForumUserProfileView), MaterialRoute(page: CodeRadioView), MaterialRoute(page: SuperBlockView), diff --git a/mobile-app/lib/app/app.router.dart b/mobile-app/lib/app/app.router.dart index 1078591b5..64a2a165a 100644 --- a/mobile-app/lib/app/app.router.dart +++ b/mobile-app/lib/app/app.router.dart @@ -33,14 +33,11 @@ import '../ui/views/news/news-image-viewer/news_image_viewer.dart'; import '../ui/views/podcast/episode-view/episode_view.dart'; import '../ui/views/podcast/podcast-list/podcast_list_view.dart'; import '../ui/views/profile/profile_view.dart'; -import '../ui/views/settings/forumSettings/forum_settings_view.dart'; -import '../ui/views/settings/podcastSettings/podcast_settings_view.dart'; import '../ui/views/web_view/web_view_view.dart'; class Routes { static const String homeView = '/'; static const String podcastListView = '/podcast-list-view'; - static const String podcastSettingsView = '/podcast-settings-view'; static const String episodeView = '/episode-view'; static const String newsArticleView = '/news-article-view'; static const String newsBookmarkPostView = '/news-bookmark-post-view'; @@ -52,7 +49,6 @@ class Routes { static const String forumPostView = '/forum-post-view'; static const String forumLoginView = '/forum-login-view'; static const String forumUserView = '/forum-user-view'; - static const String forumSettingsView = '/forum-settings-view'; static const String forumUserProfileView = '/forum-user-profile-view'; static const String codeRadioView = '/code-radio-view'; static const String superBlockView = '/super-block-view'; @@ -63,7 +59,6 @@ class Routes { static const all = { homeView, podcastListView, - podcastSettingsView, episodeView, newsArticleView, newsBookmarkPostView, @@ -75,7 +70,6 @@ class Routes { forumPostView, forumLoginView, forumUserView, - forumSettingsView, forumUserProfileView, codeRadioView, superBlockView, @@ -92,7 +86,6 @@ class StackedRouter extends RouterBase { final _routes = [ RouteDef(Routes.homeView, page: HomeView), RouteDef(Routes.podcastListView, page: PodcastListView), - RouteDef(Routes.podcastSettingsView, page: PodcastSettingsView), RouteDef(Routes.episodeView, page: EpisodeView), RouteDef(Routes.newsArticleView, page: NewsArticleView), RouteDef(Routes.newsBookmarkPostView, page: NewsBookmarkPostView), @@ -104,7 +97,6 @@ class StackedRouter extends RouterBase { RouteDef(Routes.forumPostView, page: ForumPostView), RouteDef(Routes.forumLoginView, page: ForumLoginView), RouteDef(Routes.forumUserView, page: ForumUserView), - RouteDef(Routes.forumSettingsView, page: ForumSettingsView), RouteDef(Routes.forumUserProfileView, page: ForumUserProfileView), RouteDef(Routes.codeRadioView, page: CodeRadioView), RouteDef(Routes.superBlockView, page: SuperBlockView), @@ -128,12 +120,6 @@ class StackedRouter extends RouterBase { settings: data, ); }, - PodcastSettingsView: (data) { - return MaterialPageRoute( - builder: (context) => const PodcastSettingsView(), - settings: data, - ); - }, EpisodeView: (data) { var args = data.getArgs(nullOk: false); return MaterialPageRoute( @@ -255,12 +241,6 @@ class StackedRouter extends RouterBase { settings: data, ); }, - ForumSettingsView: (data) { - return MaterialPageRoute( - builder: (context) => const ForumSettingsView(), - settings: data, - ); - }, ForumUserProfileView: (data) { return MaterialPageRoute( builder: (context) => const ForumUserProfileView(), @@ -485,22 +465,6 @@ extension NavigatorStateExtension on NavigationService { ); } - Future navigateToPodcastSettingsView({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.podcastSettingsView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - Future navigateToEpisodeView({ Key? key, required Episodes episode, @@ -726,22 +690,6 @@ extension NavigatorStateExtension on NavigationService { ); } - Future navigateToForumSettingsView({ - int? routerId, - bool preventDuplicates = true, - Map? parameters, - Widget Function(BuildContext, Animation, Animation, Widget)? - transition, - }) async { - return navigateTo( - Routes.forumSettingsView, - id: routerId, - preventDuplicates: preventDuplicates, - parameters: parameters, - transition: transition, - ); - } - Future navigateToForumUserProfileView({ int? routerId, bool preventDuplicates = true, diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index d9cc2854b..09feceeed 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:freecodecamp/models/main/user_model.dart'; import 'package:freecodecamp/ui/views/learn/settings/settings_model.dart'; -import 'package:freecodecamp/ui/views/profile/profile_view.dart'; import 'package:stacked/stacked.dart'; class SettingsView extends StatelessWidget { @@ -20,66 +19,49 @@ class SettingsView extends StatelessWidget { title: const Text('LEARN SETTINGS'), centerTitle: true, ), - body: WillPopScope( - onWillPop: () { - Navigator.pushReplacement( - context, - PageRouteBuilder( - transitionDuration: Duration.zero, - pageBuilder: (context, animation1, animation2) => - const ProfileView(), - ), - ); - return Future.delayed(const Duration(seconds: 0)); - }, - child: SingleChildScrollView( - child: FutureBuilder( - future: model.userFuture, - builder: ((context, snapshot) { - if (snapshot.hasData) { - FccUserModel user = snapshot.data as FccUserModel; - - return Row( - children: [ - Expanded( - child: Column( - children: [ - textfieldUsername( - 'Username', user.username, model), - textfield('Name', model, user.name), - textfield('Location', model, user.location), - textfield('Picture', model, user.picture), - textfield('About', model, user.about, 5), - saveAboutButton(model), - switchButton('isLocked', 'My profile', model), - switchButton('showName', 'My name', model), - switchButton( - 'showLocation', 'My location', model), - switchButton('showAbout', 'My about', model), - switchButton('showPoints', 'My points', model), - switchButton('showHeatMap', 'My heatmap', model), - switchButton( - 'showCerts', 'My certifications', model), - switchButton( - 'showPortfolio', 'My portfolio', model), - switchButton( - 'showTimeLine', 'My timeline', model), - switchButton( - 'showDonation', 'My donations', model), - saveProfileButton(model), - ], - ), - ) - ], - ); - } + body: SingleChildScrollView( + child: FutureBuilder( + future: model.userFuture, + builder: ((context, snapshot) { + if (snapshot.hasData) { + FccUserModel user = snapshot.data as FccUserModel; - return const Center( - child: CircularProgressIndicator(), + return Row( + children: [ + Expanded( + child: Column( + children: [ + textfieldUsername('Username', user.username, model), + textfield('Name', model, user.name), + textfield('Location', model, user.location), + textfield('Picture', model, user.picture), + textfield('About', model, user.about, 5), + saveAboutButton(model), + switchButton('isLocked', 'My profile', model), + switchButton('showName', 'My name', model), + switchButton('showLocation', 'My location', model), + switchButton('showAbout', 'My about', model), + switchButton('showPoints', 'My points', model), + switchButton('showHeatMap', 'My heatmap', model), + switchButton( + 'showCerts', 'My certifications', model), + switchButton( + 'showPortfolio', 'My portfolio', model), + switchButton('showTimeLine', 'My timeline', model), + switchButton('showDonation', 'My donations', model), + saveProfileButton(model), + ], + ), + ) + ], ); - }), - )), - ), + } + + return const Center( + child: CircularProgressIndicator(), + ); + }), + )), ); })); } diff --git a/mobile-app/lib/ui/views/profile/profile_viemodel.dart b/mobile-app/lib/ui/views/profile/profile_viemodel.dart index 324e26e9a..5f669b03e 100644 --- a/mobile-app/lib/ui/views/profile/profile_viemodel.dart +++ b/mobile-app/lib/ui/views/profile/profile_viemodel.dart @@ -1,6 +1,4 @@ import 'package:freecodecamp/app/app.locator.dart'; -import 'package:freecodecamp/app/app.router.dart'; -import 'package:freecodecamp/models/main/user_model.dart'; import 'package:freecodecamp/service/authentication_service.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -9,10 +7,4 @@ import 'package:stacked_services/stacked_services.dart'; class ProfileViewModel extends BaseViewModel { final AuthenticationService auth = locator(); final NavigationService navigationService = locator(); - - void gotoSettings(FccUserModel user) { - navigationService.pushNamedAndRemoveUntil( - Routes.settingsView, - ); - } } diff --git a/mobile-app/lib/ui/views/profile/profile_view.dart b/mobile-app/lib/ui/views/profile/profile_view.dart index f12f9fc18..72f3f5525 100644 --- a/mobile-app/lib/ui/views/profile/profile_view.dart +++ b/mobile-app/lib/ui/views/profile/profile_view.dart @@ -200,11 +200,6 @@ class ProfileView extends StatelessWidget { ], ), buildDivider(), - TextButton( - child: const Text('settings'), - onPressed: () { - model.gotoSettings(user); - }), ListView( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, diff --git a/mobile-app/lib/ui/views/settings/forumSettings/forum_settings_view.dart b/mobile-app/lib/ui/views/settings/forumSettings/forum_settings_view.dart deleted file mode 100644 index fb588cecd..000000000 --- a/mobile-app/lib/ui/views/settings/forumSettings/forum_settings_view.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:freecodecamp/ui/views/settings/forumSettings/forum_settings_viewmodel.dart'; -import 'package:stacked/stacked.dart'; - -class ForumSettingsView extends StatelessWidget { - const ForumSettingsView({Key? key}) : super(key: key); - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - onModelReady: (model) => model.init(), - viewModelBuilder: () => ForumSettingsViewModel(), - builder: (context, model, child) => Scaffold( - appBar: AppBar( - title: const Text('FORUM SETTINGS'), - ), - body: ListView( - children: [ - model.isLoggedIn ? loginButton(model) : Container(), - Container( - decoration: const BoxDecoration( - border: - Border(bottom: BorderSide(color: Colors.white))), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListTile( - title: const Text( - 'REGISTER', - ), - subtitle: const Text( - 'register on the forum', - ), - onTap: () { - model.gotoForum(); - }, - ), - ), - ), - Container( - decoration: const BoxDecoration( - border: - Border(bottom: BorderSide(color: Colors.white))), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListTile( - title: const Text( - 'RESET PASSWORD', - ), - subtitle: const Text( - 'reset your password on the forum', - ), - onTap: () { - model.gotoForum(); - }, - ), - ), - ), - ], - ), - )); - } - - Container loginButton(ForumSettingsViewModel model) { - return Container( - decoration: const BoxDecoration( - border: Border(bottom: BorderSide(color: Colors.white))), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListTile( - title: const Text( - 'LOGOUT', - ), - subtitle: const Text( - 'logout from the forum', - ), - onTap: () { - model.forumLogout(); - }, - ), - ), - ); - } -} diff --git a/mobile-app/lib/ui/views/settings/forumSettings/forum_settings_viewmodel.dart b/mobile-app/lib/ui/views/settings/forumSettings/forum_settings_viewmodel.dart deleted file mode 100644 index e60b67aa9..000000000 --- a/mobile-app/lib/ui/views/settings/forumSettings/forum_settings_viewmodel.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:stacked/stacked.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - -class ForumSettingsViewModel extends BaseViewModel { - bool _isLoggedIn = false; - bool get isLoggedIn => _isLoggedIn; - - void init() async { - _isLoggedIn = await checkLoggedIn(); - notifyListeners(); - } - - void forumLogout() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - - prefs.setBool('loggedIn', false); - prefs.remove('username'); - _isLoggedIn = false; - notifyListeners(); - } - - void gotoForum() { - launchUrlString('https://forum.freecodecamp.org/'); - } - - Future checkLoggedIn() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - return prefs.getBool('loggedIn') ?? false; - } -} diff --git a/mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_view.dart b/mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_view.dart deleted file mode 100644 index 8e6e955b2..000000000 --- a/mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_view.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:freecodecamp/ui/views/settings/podcastSettings/podcast_settings_viewmodel.dart'; -import 'package:stacked/stacked.dart'; - -class PodcastSettingsView extends StatelessWidget { - const PodcastSettingsView({Key? key}) : super(key: key); - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - viewModelBuilder: () => PodcastSettingsViewModel(), - builder: (context, model, child) => Scaffold( - backgroundColor: const Color.fromRGBO(0x2A, 0x2A, 0x40, 1), - appBar: AppBar( - backgroundColor: const Color(0xFF0a0a23), - title: const Text('PODCAST SETTINGS'), - centerTitle: true, - ), - )); - } -} diff --git a/mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_viewmodel.dart b/mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_viewmodel.dart deleted file mode 100644 index e2b1de119..000000000 --- a/mobile-app/lib/ui/views/settings/podcastSettings/podcast_settings_viewmodel.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:stacked/stacked.dart'; - -class PodcastSettingsViewModel extends BaseViewModel {} diff --git a/mobile-app/lib/ui/views/settings/settings_view.dart b/mobile-app/lib/ui/views/settings/settings_view.dart deleted file mode 100644 index c0c733cbd..000000000 --- a/mobile-app/lib/ui/views/settings/settings_view.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:freecodecamp/ui/views/settings/settings_viewmodel.dart'; -import 'package:freecodecamp/ui/widgets/drawer_widget/drawer_widget_view.dart'; -import 'package:stacked/stacked.dart'; - -class SettingsView extends StatelessWidget { - const SettingsView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - viewModelBuilder: () => SettingsViewModel(), - builder: (context, model, child) => Scaffold( - backgroundColor: const Color.fromRGBO(0x2A, 0x2A, 0x40, 1), - appBar: AppBar( - backgroundColor: const Color(0xFF0a0a23), - title: const Text('SETTINGS'), - centerTitle: true, - ), - drawer: const DrawerWidgetView(), - body: ListView( - children: [ - Container( - decoration: const BoxDecoration( - border: - Border(bottom: BorderSide(color: Colors.white))), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListTile( - onTap: () { - model.goToForumSettings(); - }, - title: const Text( - 'FORUM', - style: TextStyle(color: Colors.white), - ), - subtitle: const Text( - 'You can find the forum settings here', - style: TextStyle(color: Colors.white, fontSize: 18.0), - ), - trailing: const Icon( - Icons.arrow_forward_ios_sharp, - color: Colors.white, - ), - ), - ), - ), - Container( - decoration: const BoxDecoration( - border: - Border(bottom: BorderSide(color: Colors.white))), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListTile( - onTap: () { - model.goToPodastSettings(); - }, - title: const Text( - 'PODCAST', - style: TextStyle(color: Colors.white), - ), - subtitle: const Text( - 'You can find the podcast settings here', - style: TextStyle(color: Colors.white, fontSize: 18.0), - ), - trailing: const Icon( - Icons.arrow_forward_ios_sharp, - color: Colors.white, - ), - ), - ), - ) - ], - ), - )); - } -} diff --git a/mobile-app/lib/ui/views/settings/settings_viewmodel.dart b/mobile-app/lib/ui/views/settings/settings_viewmodel.dart deleted file mode 100644 index 3304bc704..000000000 --- a/mobile-app/lib/ui/views/settings/settings_viewmodel.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:freecodecamp/app/app.locator.dart'; -import 'package:freecodecamp/app/app.router.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked_services/stacked_services.dart'; - -class SettingsViewModel extends BaseViewModel { - final _navigationService = locator(); - - void goToForumSettings() { - _navigationService.navigateTo(Routes.forumSettingsView); - } - - void goToPodastSettings() { - _navigationService.navigateTo(Routes.podcastSettingsView); - } -} diff --git a/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_view.dart b/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_view.dart index dc04f1ba0..c9afb4b11 100644 --- a/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_view.dart +++ b/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_view.dart @@ -92,11 +92,13 @@ class DrawerWidgetView extends StatelessWidget { icon: Icons.info_outline, url: 'https://www.freecodecamp.org/news/privacy-policy/', ), - const WebButton( - component: 'DONATE', - url: 'https://www.freecodecamp.org/donate/', - icon: Icons.favorite, - ), + if (model.loggedIn) + DrawerButton( + component: 'SETTINGS', + icon: Icons.settings, + route: () { + model.routeComponent('SETTINGS', context); + }), buildDivider(), DrawerButton( component: model.loggedIn ? 'LOG OUT' : 'LOGIN', diff --git a/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_viewmodel.dart b/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_viewmodel.dart index 00ef201bd..82485d3cc 100644 --- a/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_viewmodel.dart +++ b/mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_viewmodel.dart @@ -3,13 +3,13 @@ import 'package:freecodecamp/service/authentication_service.dart'; import 'package:freecodecamp/app/app.locator.dart'; import 'package:freecodecamp/service/test_service.dart'; import 'package:freecodecamp/ui/views/code_radio/code_radio_view.dart'; -//import 'package:freecodecamp/ui/views/learn/learn_view.dart'; import 'package:freecodecamp/ui/views/forum/forum-categories/forum_category_view.dart'; import 'package:freecodecamp/ui/views/home/home_view.dart'; import 'package:freecodecamp/ui/views/learn/learn/learn_view.dart'; +import 'package:freecodecamp/ui/views/learn/settings/settings_view.dart'; import 'package:freecodecamp/ui/views/podcast/podcast-list/podcast_list_view.dart'; import 'package:freecodecamp/ui/views/profile/profile_view.dart'; -import 'package:freecodecamp/ui/views/settings/settings_view.dart'; + import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; @@ -89,7 +89,7 @@ class DrawerWidgtetViewModel extends BaseViewModel { const PodcastListView())); break; case 'SETTINGS': - Navigator.pushReplacement( + Navigator.push( context, PageRouteBuilder( transitionDuration: Duration.zero, From 436f775fad5a82b0c226e1f3804ef6a9538438aa Mon Sep 17 00:00:00 2001 From: SemBauke Date: Thu, 15 Sep 2022 08:16:26 +0200 Subject: [PATCH 18/19] feat: update user data after success --- mobile-app/lib/ui/views/learn/settings/settings_model.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_model.dart b/mobile-app/lib/ui/views/learn/settings/settings_model.dart index e6e4d1893..1e74a90b5 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_model.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_model.dart @@ -108,6 +108,7 @@ class SettingsModel extends BaseViewModel { if (updated) { _snackbarService.showSnackbar( title: 'updated settings successfully', message: ''); + auth.init(); } else { _snackbarService.showSnackbar(title: 'something went wrong', message: ''); } @@ -118,6 +119,7 @@ class SettingsModel extends BaseViewModel { _snackbarService.showSnackbar( title: 'username updated successfully', message: ''); init(); + auth.init(); } else { _snackbarService.showSnackbar(title: 'something went wrong', message: ''); } @@ -191,6 +193,7 @@ class SettingsModel extends BaseViewModel { if (complete) { _snackbarService.showSnackbar( title: 'info updated successfully', message: ''); + auth.init(); } else { _snackbarService.showSnackbar( title: 'something went wrong', message: ''); From f037161e18fa8c6ff9e779496e0e5c7be713c16d Mon Sep 17 00:00:00 2001 From: SemBauke Date: Thu, 15 Sep 2022 09:55:08 +0200 Subject: [PATCH 19/19] fix: button style --- .../lib/ui/views/learn/settings/settings_view.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mobile-app/lib/ui/views/learn/settings/settings_view.dart b/mobile-app/lib/ui/views/learn/settings/settings_view.dart index 09feceeed..ae1d2f1a5 100644 --- a/mobile-app/lib/ui/views/learn/settings/settings_view.dart +++ b/mobile-app/lib/ui/views/learn/settings/settings_view.dart @@ -145,8 +145,6 @@ class SettingsView extends StatelessWidget { side: const BorderSide(width: 2, color: Colors.white), padding: const EdgeInsets.all(0), backgroundColor: const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1), - disabledBackgroundColor: - model.errorText != null ? const Color(0xFF0a0a23) : null, ), onPressed: () async { if (model.errorText == null) { @@ -162,6 +160,11 @@ class SettingsView extends StatelessWidget { return SizedBox( width: 300, child: TextButton( + style: TextButton.styleFrom( + side: const BorderSide(width: 2, color: Colors.white), + padding: const EdgeInsets.all(0), + backgroundColor: const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1), + ), onPressed: () { model.saveAbout(); }, @@ -172,6 +175,11 @@ class SettingsView extends StatelessWidget { return SizedBox( width: 300, child: TextButton( + style: TextButton.styleFrom( + side: const BorderSide(width: 2, color: Colors.white), + padding: const EdgeInsets.all(0), + backgroundColor: const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1), + ), onPressed: () { model.save(); },