From a9510d26f03d9ee414899f4241b7af2f9c1a625d Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Mon, 29 Sep 2025 11:28:20 -0400 Subject: [PATCH] 4152 onboarding tweaks (#4163) * filter courses by short lang codes * reduce padding in course details page * update home / registration routes * refactor: replace find your people page with new course page from onboarding --- lib/config/routes.dart | 227 +++++---- .../course_creation/new_course_page.dart | 162 ------ .../course_creation/selected_course_view.dart | 478 +++++++++--------- .../course_plans/course_plans_repo.dart | 4 +- .../find_your_people/find_your_people.dart | 102 ---- .../find_your_people_view.dart | 249 --------- .../find_your_people/public_space_tile.dart | 119 ----- lib/pangea/guard/p_vguard.dart | 46 +- .../pages/create_pangea_account_page.dart | 158 ++++++ .../login/pages/language_selection_page.dart | 16 +- .../login/pages/login_or_signup_view.dart | 2 +- lib/pangea/login/pages/new_trip_page.dart | 21 +- lib/pangea/login/pages/plan_trip_page.dart | 315 ++++-------- lib/pangea/login/pages/private_trip_page.dart | 2 - lib/pangea/login/pages/public_trip_page.dart | 5 +- lib/pangea/login/pages/signup.dart | 2 +- lib/pangea/login/pages/signup_view.dart | 2 +- .../controllers/subscription_controller.dart | 4 +- lib/widgets/layouts/two_column_layout.dart | 2 +- lib/widgets/navigation_rail.dart | 10 +- 20 files changed, 691 insertions(+), 1235 deletions(-) delete mode 100644 lib/pangea/course_creation/new_course_page.dart delete mode 100644 lib/pangea/find_your_people/find_your_people.dart delete mode 100644 lib/pangea/find_your_people/find_your_people_view.dart delete mode 100644 lib/pangea/find_your_people/public_space_tile.dart create mode 100644 lib/pangea/login/pages/create_pangea_account_page.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 057b8014b..a99735148 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -37,13 +37,12 @@ import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dar import 'package:fluffychat/pangea/chat_settings/pages/edit_course.dart'; import 'package:fluffychat/pangea/chat_settings/pages/pangea_invitation_selection.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; -import 'package:fluffychat/pangea/course_creation/new_course_page.dart'; import 'package:fluffychat/pangea/course_creation/selected_course_page.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/find_your_people/find_your_people.dart'; import 'package:fluffychat/pangea/find_your_people/find_your_people_constants.dart'; import 'package:fluffychat/pangea/guard/p_vguard.dart'; import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart'; +import 'package:fluffychat/pangea/login/pages/create_pangea_account_page.dart'; import 'package:fluffychat/pangea/login/pages/language_selection_page.dart'; import 'package:fluffychat/pangea/login/pages/login_or_signup_view.dart'; import 'package:fluffychat/pangea/login/pages/new_trip_page.dart'; @@ -72,7 +71,7 @@ abstract class AppRoutes { // Matrix.of(context).widget.clients.any((client) => client.isLogged()) // ? '/rooms' // : null; - return PAuthGaurd.loggedInRedirect(context, state); + return PAuthGaurd.homeRedirect(context, state); // Pangea# } @@ -84,7 +83,7 @@ abstract class AppRoutes { // Matrix.of(context).widget.clients.any((client) => client.isLogged()) // ? null // : '/home'; - return PAuthGaurd.loggedOutRedirect(context, state); + return PAuthGaurd.roomsRedirect(context, state); // Pangea# } @@ -130,20 +129,18 @@ abstract class AppRoutes { state, const Login(withEmail: true), ), - redirect: loggedInRedirect, ), ], // Pangea# ), // #Pangea GoRoute( - path: 'languages', + path: 'signup', pageBuilder: (context, state) => defaultPageBuilder( context, state, const LanguageSelectionPage(), ), - redirect: loggedInRedirect, routes: [ GoRoute( path: ':langcode', @@ -154,7 +151,6 @@ abstract class AppRoutes { langCode: state.pathParameters['langcode']!, ), ), - redirect: loggedInRedirect, routes: [ GoRoute( path: 'email', @@ -166,7 +162,6 @@ abstract class AppRoutes { langCode: state.pathParameters['langcode']!, ), ), - redirect: loggedInRedirect, ), ], ), @@ -192,6 +187,81 @@ abstract class AppRoutes { ), ), // #Pangea + GoRoute( + path: '/registration', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + const LanguageSelectionPage(), + ), + redirect: PAuthGaurd.onboardingRedirect, + routes: [ + GoRoute( + path: 'course', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + const PlanTripPage(route: 'registration'), + ), + routes: [ + GoRoute( + path: 'private', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + const PrivateTripPage(), + ); + }, + ), + GoRoute( + path: 'public', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + const PublicTripPage(), + ); + }, + ), + GoRoute( + path: 'own', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + const NewTripPage(route: 'registration'), + ); + }, + routes: [ + GoRoute( + path: ':courseid', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + SelectedCourse( + state.pathParameters['courseid']!, + ), + ); + }, + ), + ], + ), + ], + ), + GoRoute( + path: ':langcode', + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + CreatePangeaAccountPage( + langCode: state.pathParameters['langcode']!, + ), + ), + ), + ], + ), GoRoute( path: '/join_with_link', pageBuilder: (context, state) => defaultPageBuilder( @@ -210,84 +280,6 @@ abstract class AppRoutes { JoinWithAlias(alias: state.uri.queryParameters['alias']), ), ), - GoRoute( - path: '/course', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const LanguageSelectionPage(), - ), - redirect: loggedOutRedirect, - routes: [ - GoRoute( - path: ':langcode', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - PlanTripPage( - langCode: state.pathParameters['langcode']!, - ), - ), - redirect: loggedOutRedirect, - routes: [ - GoRoute( - path: 'private', - pageBuilder: (context, state) { - return defaultPageBuilder( - context, - state, - PrivateTripPage( - langCode: state.pathParameters['langcode']!, - ), - ); - }, - redirect: loggedOutRedirect, - ), - GoRoute( - path: 'public', - pageBuilder: (context, state) { - return defaultPageBuilder( - context, - state, - PublicTripPage( - langCode: state.pathParameters['langcode']!, - ), - ); - }, - redirect: loggedOutRedirect, - ), - GoRoute( - path: 'own', - pageBuilder: (context, state) { - return defaultPageBuilder( - context, - state, - NewTripPage( - langCode: state.pathParameters['langcode']!, - ), - ); - }, - redirect: loggedOutRedirect, - routes: [ - GoRoute( - path: ':courseid', - pageBuilder: (context, state) { - return defaultPageBuilder( - context, - state, - SelectedCourse( - state.pathParameters['courseid']!, - ), - ); - }, - redirect: loggedOutRedirect, - ), - ], - ), - ], - ), - ], - ), // Pangea# ShellRoute( // Never use a transition on the shell route. Changing the PageBuilder @@ -404,33 +396,55 @@ abstract class AppRoutes { // : child, // ), // routes: [ - // Pangea# GoRoute( - path: 'communities', - redirect: loggedOutRedirect, + path: 'course', pageBuilder: (context, state) => defaultPageBuilder( context, state, - const FindYourPeople(), + const PlanTripPage(route: 'rooms'), ), routes: [ GoRoute( - path: 'newcourse', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const NewCourse(), - ), - redirect: loggedOutRedirect, + path: 'private', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + const PrivateTripPage(), + ); + }, + ), + GoRoute( + path: 'public', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + const PublicTripPage(), + ); + }, + ), + GoRoute( + path: 'own', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + const NewTripPage(route: 'rooms'), + ); + }, routes: [ GoRoute( - path: ':courseId', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - SelectedCourse(state.pathParameters['courseId']!), - ), - redirect: loggedOutRedirect, + path: ':courseid', + pageBuilder: (context, state) { + return defaultPageBuilder( + context, + state, + SelectedCourse( + state.pathParameters['courseid']!, + ), + ); + }, ), ], ), @@ -776,7 +790,8 @@ abstract class AppRoutes { pageBuilder: (context, state) => defaultPageBuilder( context, state, - NewCourse( + NewTripPage( + route: 'rooms', spaceId: state.pathParameters['spaceid']!, ), ), diff --git a/lib/pangea/course_creation/new_course_page.dart b/lib/pangea/course_creation/new_course_page.dart deleted file mode 100644 index 57ba1d55d..000000000 --- a/lib/pangea/course_creation/new_course_page.dart +++ /dev/null @@ -1,162 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:go_router/go_router.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; -import 'package:fluffychat/pangea/course_creation/course_plan_filter_widget.dart'; -import 'package:fluffychat/pangea/course_creation/course_plan_tile_widget.dart'; -import 'package:fluffychat/pangea/course_creation/course_search_provider.dart'; -import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; -import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class NewCourse extends StatefulWidget { - final String? spaceId; - const NewCourse({ - super.key, - this.spaceId, - }); - - @override - State createState() => NewCourseController(); -} - -class NewCourseController extends State with CourseSearchProvider { - @override - Widget build(BuildContext context) { - const double titleFontSize = 16.0; - const double descFontSize = 12.0; - - const double iconSize = 12.0; - final spaceId = widget.spaceId; - - return Scaffold( - appBar: AppBar( - title: Text( - spaceId != null - ? L10n.of(context).addCoursePlan - : L10n.of(context).newCourse, - ), - ), - body: Padding( - padding: const EdgeInsets.all(12.0), - child: MaxWidthBody( - showBorder: false, - withScrolling: false, - maxWidth: 500.0, - child: Column( - spacing: 12.0, - children: [ - Text( - L10n.of(context).newCourseSubtitle, - style: const TextStyle( - fontSize: titleFontSize, - fontStyle: FontStyle.italic, - ), - ), - Padding( - padding: const EdgeInsetsGeometry.symmetric( - vertical: 4.0, - ), - child: Row( - children: [ - Expanded( - child: Wrap( - spacing: 8.0, - runSpacing: 8.0, - alignment: WrapAlignment.start, - children: [ - CoursePlanFilter( - value: instructionLanguageFilter, - onChanged: setInstructionLanguageFilter, - items: MatrixState - .pangeaController.pLanguageStore.baseOptions, - displayname: (v) => - v.getDisplayName(context) ?? v.displayName, - enableSearch: true, - defaultName: - L10n.of(context).languageOfInstructionsLabel, - shortName: L10n.of(context).allLanguages, - ), - CoursePlanFilter( - value: targetLanguageFilter, - onChanged: setTargetLanguageFilter, - items: MatrixState - .pangeaController.pLanguageStore.targetOptions, - displayname: (v) => - v.getDisplayName(context) ?? v.displayName, - enableSearch: true, - defaultName: L10n.of(context).targetLanguageLabel, - shortName: L10n.of(context).allLanguages, - ), - CoursePlanFilter( - value: languageLevelFilter, - onChanged: setLanguageLevelFilter, - items: LanguageLevelTypeEnum.values, - displayname: (v) => v.string, - defaultName: L10n.of(context).cefrLevelLabel, - shortName: L10n.of(context).allCefrLevels, - ), - ], - ), - ), - ], - ), - ), - Builder( - builder: (context) { - if (error != null) { - return Center( - child: ErrorIndicator( - message: L10n.of(context).failedToLoadCourses, - ), - ); - } - - if (loading) { - return const Center( - child: CircularProgressIndicator.adaptive(), - ); - } - - if (courses.isEmpty) { - return Center( - child: Text(L10n.of(context).noCoursesFound), - ); - } - - return Expanded( - child: ListView.builder( - itemCount: courses.length, - itemBuilder: (context, index) => Padding( - padding: const EdgeInsetsGeometry.fromLTRB( - 4.0, - 4.0, - 4.0, - 16.0, - ), - child: CoursePlanTile( - course: courses[index], - onTap: () => context.go( - spaceId != null - ? "/rooms/spaces/$spaceId/addcourse/${courses[index].uuid}" - : "/rooms/communities/newcourse/${courses[index].uuid}", - ), - titleFontSize: titleFontSize, - chipFontSize: descFontSize, - chipIconSize: iconSize, - ), - ), - ), - ); - }, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pangea/course_creation/selected_course_view.dart b/lib/pangea/course_creation/selected_course_view.dart index 7c419fd43..b6d48d1a6 100644 --- a/lib/pangea/course_creation/selected_course_view.dart +++ b/lib/pangea/course_creation/selected_course_view.dart @@ -10,7 +10,6 @@ import 'package:fluffychat/pangea/course_plans/course_plan_builder.dart'; import 'package:fluffychat/pangea/course_plans/map_clipper.dart'; import 'package:fluffychat/pangea/course_settings/pin_clipper.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; class SelectedCourseView extends StatelessWidget { final SelectedCourseController controller; @@ -40,255 +39,266 @@ class SelectedCourseView extends StatelessWidget { : L10n.of(context).newCourse, ), ), - body: CoursePlanBuilder( - courseId: controller.widget.courseId, - onNotFound: () => context.go("/rooms/communities/newcourse"), - builder: (context, courseController) { - final course = courseController.course; - return MaxWidthBody( - showBorder: false, - withScrolling: false, - maxWidth: 500.0, - child: course == null - ? const Center(child: CircularProgressIndicator.adaptive()) - : Column( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(12.0), - child: ListView.builder( - itemCount: course.loadedTopics.length + 1, - itemBuilder: (context, index) { - if (index == 0) { - return Column( - spacing: 8.0, - children: [ - ClipPath( - clipper: MapClipper(), - child: ImageByUrl( - imageUrl: course.imageUrl, - width: 100.0, - borderRadius: - BorderRadius.circular(0.0), - replacement: Container( - width: 100.0, - height: 100.0, - decoration: BoxDecoration( - color: theme.colorScheme.secondary, + body: SafeArea( + child: CoursePlanBuilder( + courseId: controller.widget.courseId, + onNotFound: () => context.go("/rooms/course/own"), + builder: (context, courseController) { + final course = courseController.course; + return Container( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 500.0), + child: course == null + ? const Center(child: CircularProgressIndicator.adaptive()) + : Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: ListView.builder( + itemCount: course.loadedTopics.length + 1, + itemBuilder: (context, index) { + if (index == 0) { + return Column( + spacing: 8.0, + children: [ + ClipPath( + clipper: MapClipper(), + child: ImageByUrl( + imageUrl: course.imageUrl, + width: 100.0, + borderRadius: + BorderRadius.circular(0.0), + replacement: Container( + width: 100.0, + height: 100.0, + decoration: BoxDecoration( + color: + theme.colorScheme.secondary, + ), + ), ), ), - ), - ), - Text( - course.title, - style: const TextStyle( - fontSize: titleFontSize, - ), - ), - Text( - course.description, - style: const TextStyle( - fontSize: descFontSize, - ), - ), - CourseInfoChips( - course, - fontSize: descFontSize, - iconSize: smallIconSize, - ), - Padding( - padding: const EdgeInsets.only( - top: 4.0, - bottom: 8.0, - ), - child: Row( - spacing: 4.0, - children: [ - const Icon( - Icons.map, - size: largeIconSize, - ), - Text( - L10n.of(context).coursePlan, - style: const TextStyle( - fontSize: titleFontSize, - ), - ), - ], - ), - ), - ], - ); - } - - index--; - final topic = course.loadedTopics[index]; - return Padding( - padding: - const EdgeInsets.symmetric(vertical: 4.0), - child: Row( - spacing: 8.0, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ClipPath( - clipper: PinClipper(), - child: ImageByUrl( - imageUrl: topic.imageUrl, - width: 45.0, - replacement: Container( - width: 45.0, - height: 45.0, - decoration: BoxDecoration( - color: theme.colorScheme.secondary, + Text( + course.title, + style: const TextStyle( + fontSize: titleFontSize, ), ), - ), - ), - Flexible( - child: Column( - spacing: 4.0, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - topic.title, - style: const TextStyle( - fontSize: titleFontSize, - ), + Text( + course.description, + style: const TextStyle( + fontSize: descFontSize, ), - Text( - topic.description, - style: const TextStyle( - fontSize: descFontSize, - ), + ), + CourseInfoChips( + course, + fontSize: descFontSize, + iconSize: smallIconSize, + ), + Padding( + padding: const EdgeInsets.only( + top: 4.0, + bottom: 8.0, ), - Padding( - padding: const EdgeInsetsGeometry - .symmetric( - vertical: 2.0, - ), - child: Row( - spacing: 8.0, - mainAxisSize: MainAxisSize.min, - children: [ - if (topic.location != null) - CourseInfoChip( - icon: Icons.location_on, - text: topic.location!, - fontSize: descFontSize, - iconSize: smallIconSize, - ), - CourseInfoChip( - icon: - Icons.event_note_outlined, - text: L10n.of(context) - .numActivityPlans( - topic.loadedActivities - .length, - ), - fontSize: descFontSize, - iconSize: smallIconSize, + child: Row( + spacing: 4.0, + children: [ + const Icon( + Icons.map, + size: largeIconSize, + ), + Text( + L10n.of(context).coursePlan, + style: const TextStyle( + fontSize: titleFontSize, ), - ], + ), + ], + ), + ), + ], + ); + } + + index--; + final topic = course.loadedTopics[index]; + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + ), + child: Row( + spacing: 8.0, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + ClipPath( + clipper: PinClipper(), + child: ImageByUrl( + imageUrl: topic.imageUrl, + width: 45.0, + replacement: Container( + width: 45.0, + height: 45.0, + decoration: BoxDecoration( + color: + theme.colorScheme.secondary, + ), ), ), - ], - ), + ), + Flexible( + child: Column( + spacing: 4.0, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + topic.title, + style: const TextStyle( + fontSize: titleFontSize, + ), + ), + Text( + topic.description, + style: const TextStyle( + fontSize: descFontSize, + ), + ), + Padding( + padding: + const EdgeInsetsGeometry + .symmetric( + vertical: 2.0, + ), + child: Row( + spacing: 8.0, + mainAxisSize: + MainAxisSize.min, + children: [ + if (topic.location != null) + CourseInfoChip( + icon: Icons.location_on, + text: topic.location!, + fontSize: descFontSize, + iconSize: smallIconSize, + ), + CourseInfoChip( + icon: Icons + .event_note_outlined, + text: L10n.of(context) + .numActivityPlans( + topic.loadedActivities + .length, + ), + fontSize: descFontSize, + iconSize: smallIconSize, + ), + ], + ), + ), + ], + ), + ), + ], ), - ], - ), - ); - }, - ), - ), - ), - Container( - decoration: BoxDecoration( - color: theme.colorScheme.surface, - border: Border( - top: BorderSide( - color: theme.dividerColor, - width: 1.0, + ); + }, + ), ), ), - ), - padding: const EdgeInsets.all(12.0), - child: Column( - spacing: 8.0, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - spacing: 12.0, - children: [ - const Icon( - Icons.edit, - size: mediumIconSize, - ), - Flexible( - child: Text( - L10n.of(context).editCourseLater, - style: - const TextStyle(fontSize: descFontSize), - ), - ), - ], - ), - Row( - spacing: 12.0, - children: [ - const Icon( - Icons.shield, - size: mediumIconSize, - ), - Flexible( - child: Text( - L10n.of(context).newCourseAccess, - style: - const TextStyle(fontSize: descFontSize), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: - theme.colorScheme.primaryContainer, - foregroundColor: - theme.colorScheme.onPrimaryContainer, - ), - onPressed: () => showFutureLoadingDialog( - context: context, - future: () => spaceId != null - ? controller.addCourseToSpace(course) - : controller.launchCourse(course), - ), - child: Row( - spacing: 8.0, - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.map_outlined), - Text( - spaceId != null - ? L10n.of(context).addCoursePlan - : L10n.of(context).createCourse, - style: const TextStyle( - fontSize: titleFontSize, - ), - ), - ], + Container( + decoration: BoxDecoration( + color: theme.colorScheme.surface, + border: Border( + top: BorderSide( + color: theme.dividerColor, + width: 1.0, ), ), ), - ], - ), + padding: const EdgeInsets.all(12.0), + child: Column( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + spacing: 12.0, + children: [ + const Icon( + Icons.edit, + size: mediumIconSize, + ), + Flexible( + child: Text( + L10n.of(context).editCourseLater, + style: const TextStyle( + fontSize: descFontSize, + ), + ), + ), + ], + ), + Row( + spacing: 12.0, + children: [ + const Icon( + Icons.shield, + size: mediumIconSize, + ), + Flexible( + child: Text( + L10n.of(context).newCourseAccess, + style: const TextStyle( + fontSize: descFontSize, + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: + theme.colorScheme.onPrimaryContainer, + ), + onPressed: () => showFutureLoadingDialog( + context: context, + future: () => spaceId != null + ? controller.addCourseToSpace(course) + : controller.launchCourse(course), + ), + child: Row( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.map_outlined), + Text( + spaceId != null + ? L10n.of(context).addCoursePlan + : L10n.of(context).createCourse, + style: const TextStyle( + fontSize: titleFontSize, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], ), - ], - ), - ); - }, + ), + ); + }, + ), ), ); } diff --git a/lib/pangea/course_plans/course_plans_repo.dart b/lib/pangea/course_plans/course_plans_repo.dart index 44d2a2b58..a19223065 100644 --- a/lib/pangea/course_plans/course_plans_repo.dart +++ b/lib/pangea/course_plans/course_plans_repo.dart @@ -160,13 +160,13 @@ class CoursePlansRepo { if (filter.languageOfInstructions != null) { where["and"].add({ "l1": { - "equals": filter.languageOfInstructions!.langCode, + "equals": filter.languageOfInstructions!.langCodeShort, }, }); } if (filter.targetLanguage != null) { where["and"].add({ - "l2": {"equals": filter.targetLanguage!.langCode}, + "l2": {"equals": filter.targetLanguage!.langCodeShort}, }); } } else if (numberOfFilter == 1) { diff --git a/lib/pangea/find_your_people/find_your_people.dart b/lib/pangea/find_your_people/find_your_people.dart deleted file mode 100644 index eb25239f1..000000000 --- a/lib/pangea/find_your_people/find_your_people.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/find_your_people/find_your_people_view.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class FindYourPeople extends StatefulWidget { - const FindYourPeople({super.key}); - - @override - State createState() => FindYourPeopleState(); -} - -class FindYourPeopleState extends State { - final TextEditingController searchController = TextEditingController(); - - Object? error; - bool loading = true; - - Timer? _coolDown; - - final List spaceItems = []; - - @override - void initState() { - super.initState(); - setSpaceItems(); - } - - @override - void dispose() { - searchController.dispose(); - _coolDown?.cancel(); - super.dispose(); - } - - void onSearchEnter(String text, {bool globalSearch = true}) { - if (text.isEmpty) { - setSpaceItems(); - return; - } - - _coolDown?.cancel(); - _coolDown = Timer(const Duration(milliseconds: 500), setSpaceItems); - } - - Future setSpaceItems() async { - setState(() { - loading = true; - error = null; - spaceItems.clear(); - }); - - try { - final resp = await Matrix.of(context).client.queryPublicRooms( - filter: PublicRoomQueryFilter( - roomTypes: ['m.space'], - genericSearchTerm: searchController.text, - ), - limit: 100, - ); - spaceItems.addAll(resp.chunk); - spaceItems.sort((a, b) { - int getPriority(item) { - final bool hasTopic = item.topic != null && item.topic!.isNotEmpty; - final bool hasAvatar = item.avatarUrl != null; - - if (hasTopic && hasAvatar) return 0; // Highest priority - if (hasAvatar) return 1; // Second priority - if (hasTopic) return 2; // Third priority - return 3; // Lowest priority - } - - return getPriority(a).compareTo(getPriority(b)); - }); - } catch (e, s) { - ErrorHandler.logError( - e: e, - s: s, - data: { - 'searchText': searchController.text, - }, - ); - error = e; - } finally { - if (mounted) { - setState(() { - loading = false; - }); - } - } - } - - @override - Widget build(BuildContext context) { - return FindYourPeopleView(controller: this); - } -} diff --git a/lib/pangea/find_your_people/find_your_people_view.dart b/lib/pangea/find_your_people/find_your_people_view.dart deleted file mode 100644 index 34a024f61..000000000 --- a/lib/pangea/find_your_people/find_your_people_view.dart +++ /dev/null @@ -1,249 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:go_router/go_router.dart'; - -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; -import 'package:fluffychat/pangea/find_your_people/find_your_people.dart'; -import 'package:fluffychat/pangea/find_your_people/public_space_tile.dart'; -import 'package:fluffychat/pangea/spaces/utils/space_code.dart'; - -class FindYourPeopleView extends StatelessWidget { - final FindYourPeopleState controller; - - const FindYourPeopleView({ - super.key, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final isColumnMode = FluffyThemes.isColumnMode(context); - - return Scaffold( - appBar: isColumnMode - ? null - : AppBar( - leading: IconButton( - icon: Icon( - Icons.chevron_left, - color: theme.colorScheme.primary, - ), - onPressed: () => Navigator.of(context).pop(), - ), - title: Icon( - Icons.groups_outlined, - size: 24.0, - color: theme.colorScheme.primary, - ), - centerTitle: false, - leadingWidth: 48.0, - actions: [ - TextButton( - child: Row( - children: [ - Icon( - Icons.join_full, - color: theme.colorScheme.primary, - size: 24.0, - ), - const SizedBox(width: 8.0), - Text( - L10n.of(context).joinWithCode, - style: TextStyle( - color: theme.colorScheme.primary, - fontSize: 14.0, - ), - ), - ], - ), - onPressed: () => - SpaceCodeUtil.joinWithSpaceCodeDialog(context), - ), - ], - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () => context.push('/rooms/communities/newcourse'), - icon: const Icon(Icons.add_box_outlined), - label: Text( - L10n.of(context).newCourse, - overflow: TextOverflow.fade, - ), - ), - body: Padding( - padding: isColumnMode - ? const EdgeInsets.symmetric( - horizontal: 24.0, - vertical: 20.0, - ) - : const EdgeInsets.all(12.0), - child: Column( - spacing: 16.0, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (isColumnMode) - Padding( - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 12.0, - ), - child: Text( - L10n.of(context).findCourse, - style: const TextStyle(fontSize: 32.0), - ), - ), - Expanded( - child: Column( - spacing: isColumnMode ? 32.0 : 16.0, - children: [ - LayoutBuilder( - builder: (context, constraints) { - return SizedBox( - width: constraints.maxWidth, - child: Wrap( - alignment: WrapAlignment.spaceBetween, - spacing: 10, - runSpacing: 10, - children: [ - ConstrainedBox( - constraints: isColumnMode - ? const BoxConstraints( - minWidth: 200.0, - maxWidth: 400.0, - ) - : BoxConstraints( - maxWidth: - MediaQuery.of(context).size.width - - 24.0, - ), - child: SizedBox( - height: 40.0, - width: isColumnMode ? 300.0 : null, - child: TextField( - controller: controller.searchController, - onChanged: controller.onSearchEnter, - textInputAction: TextInputAction.search, - decoration: InputDecoration( - filled: !isColumnMode, - fillColor: isColumnMode - ? null - : theme.colorScheme.secondaryContainer, - border: OutlineInputBorder( - borderSide: isColumnMode - ? const BorderSide() - : BorderSide.none, - borderRadius: BorderRadius.circular(100), - ), - contentPadding: const EdgeInsets.fromLTRB( - 0, - 0, - 20.0, - 0, - ), - hintText: L10n.of(context).findCourse, - hintStyle: TextStyle( - color: - theme.colorScheme.onPrimaryContainer, - fontWeight: FontWeight.normal, - fontSize: 16.0, - ), - floatingLabelBehavior: - FloatingLabelBehavior.never, - prefixIcon: IconButton( - onPressed: () {}, - icon: Icon( - Icons.search_outlined, - color: theme - .colorScheme.onPrimaryContainer, - ), - ), - ), - ), - ), - ), - if (isColumnMode) - Wrap( - children: [ - TextButton( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.join_full, - color: theme - .colorScheme.onPrimaryContainer, - size: 24.0, - ), - const SizedBox(width: 8.0), - Text( - L10n.of(context).joinWithCode, - style: TextStyle( - color: theme - .colorScheme.onPrimaryContainer, - fontSize: 16.0, - ), - ), - ], - ), - onPressed: () => - SpaceCodeUtil.joinWithSpaceCodeDialog( - context, - ), - ), - ], - ), - ], - ), - ); - }, - ), - controller.error != null - ? Column( - spacing: 8.0, - mainAxisSize: MainAxisSize.min, - children: [ - ErrorIndicator( - message: L10n.of(context).oopsSomethingWentWrong, - ), - IconButton( - onPressed: controller.setSpaceItems, - icon: const Icon(Icons.refresh), - ), - ], - ) - : controller.loading - ? const CircularProgressIndicator.adaptive() - : controller.spaceItems.isEmpty - ? Text( - L10n.of(context).nothingFound, - ) - : Expanded( - child: ListView.builder( - itemCount: controller.spaceItems.length, - itemBuilder: (context, index) { - final space = - controller.spaceItems[index]; - return Padding( - padding: isColumnMode - ? const EdgeInsets.only( - bottom: 32.0, - ) - : const EdgeInsets.only( - bottom: 16.0, - ), - child: PublicSpaceTile(space: space), - ); - }, - ), - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/pangea/find_your_people/public_space_tile.dart b/lib/pangea/find_your_people/public_space_tile.dart deleted file mode 100644 index 5dd19f68f..000000000 --- a/lib/pangea/find_your_people/public_space_tile.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicator_button.dart'; -import 'package:fluffychat/pangea/extensions/pangea_rooms_chunk_extension.dart'; -import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; -import 'package:fluffychat/widgets/avatar.dart'; - -class PublicSpaceTile extends StatelessWidget { - final PublicRoomsChunk space; - const PublicSpaceTile({super.key, required this.space}); - - @override - Widget build(BuildContext context) { - final bool isColumnMode = FluffyThemes.isColumnMode(context); - - return HoverButton( - onPressed: () => PublicRoomBottomSheet.show( - context: context, - chunk: space, - ), - borderRadius: BorderRadius.circular(10.0), - hoverOpacity: 0.1, - child: Padding( - padding: isColumnMode - ? const EdgeInsets.all(12.0) - : const EdgeInsets.all(0.0), - child: Column( - spacing: 12, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: isColumnMode ? 80.0 : 58.0, - child: Row( - children: [ - (space.avatarUrl != null) - ? Avatar( - mxContent: space.avatarUrl, - name: space.name, - size: isColumnMode ? 80.0 : 58.0, - borderRadius: BorderRadius.circular( - 10, - ), - ) - : ClipRRect( - borderRadius: BorderRadius.circular( - 10, - ), - child: CachedNetworkImage( - imageUrl: space.defaultAvatar(), - width: isColumnMode ? 80.0 : 58.0, - height: isColumnMode ? 80.0 : 58.0, - fit: BoxFit.cover, - ), - ), - Flexible( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - spacing: 8.0, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - space.name ?? '', - style: TextStyle( - fontSize: isColumnMode ? 20.0 : 14.0, - height: 1.2, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - Row( - spacing: 10, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon( - Icons.group, - size: isColumnMode ? 20.0 : 16.0, - ), - Text( - L10n.of(context).countParticipants( - space.numJoinedMembers, - ), - style: TextStyle( - fontSize: isColumnMode ? 16.0 : 12.0, - height: 1.2, - ), - ), - ], - ), - ], - ), - ), - ), - ], - ), - ), - if (isColumnMode && space.topic != null && space.topic!.isNotEmpty) - Text( - space.topic!, - style: const TextStyle( - fontSize: 16.0, - height: 1.2, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - ); - } -} diff --git a/lib/pangea/guard/p_vguard.dart b/lib/pangea/guard/p_vguard.dart index 65bcffd9a..034a91f24 100644 --- a/lib/pangea/guard/p_vguard.dart +++ b/lib/pangea/guard/p_vguard.dart @@ -12,7 +12,7 @@ class PAuthGaurd { static PangeaController? pController; /// Redirect for /home routes - static FutureOr loggedInRedirect( + static FutureOr homeRedirect( BuildContext context, GoRouterState state, ) async { @@ -24,11 +24,19 @@ class PAuthGaurd { Matrix.of(context).widget.clients.any((client) => client.isLogged()); if (!isLogged) return null; - return _onboardingRedirect(context, state); + // If user hasn't set their L2, + // and their URL doesn’t include ‘course,’ redirect + final bool hasSetL2 = await pController!.userController.isUserL2Set; + final langCode = state.pathParameters['langcode']; + return !hasSetL2 + ? langCode != null + ? '/registration/$langCode' + : '/registration' + : '/rooms'; } - /// Redirect for onboarding and /rooms routes - static FutureOr loggedOutRedirect( + /// Redirect for /rooms routes + static FutureOr roomsRedirect( BuildContext context, GoRouterState state, ) async { @@ -42,24 +50,30 @@ class PAuthGaurd { return '/home'; } - return _onboardingRedirect(context, state); - } - - static Future _onboardingRedirect( - BuildContext context, - GoRouterState state, - ) async { // If user hasn't set their L2, // and their URL doesn’t include ‘course,’ redirect final bool hasSetL2 = await pController!.userController.isUserL2Set; - final bool shouldRedirect = - !hasSetL2 && !(state.fullPath?.contains('course') ?? false); final langCode = state.pathParameters['langcode']; - return shouldRedirect + return !hasSetL2 ? langCode != null - ? '/course/$langCode' - : '/course' + ? '/registration/$langCode' + : '/registration' : null; } + + /// Redirect for onboarding routes + static FutureOr onboardingRedirect( + BuildContext context, + GoRouterState state, + ) async { + if (pController == null) { + return Matrix.of(context).client.isLogged() ? null : '/home'; + } + + final isLogged = Matrix.of(context).widget.clients.any( + (client) => client.isLogged(), + ); + return isLogged ? null : '/home'; + } } diff --git a/lib/pangea/login/pages/create_pangea_account_page.dart b/lib/pangea/login/pages/create_pangea_account_page.dart new file mode 100644 index 000000000..853e4f943 --- /dev/null +++ b/lib/pangea/login/pages/create_pangea_account_page.dart @@ -0,0 +1,158 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:go_router/go_router.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; +import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class CreatePangeaAccountPage extends StatefulWidget { + final String langCode; + const CreatePangeaAccountPage({ + super.key, + required this.langCode, + }); + + @override + CreatePangeaAccountPageState createState() => CreatePangeaAccountPageState(); +} + +class CreatePangeaAccountPageState extends State { + bool _loadingProfile = true; + Object? _profileError; + + final List avatarPaths = const [ + "assets/pangea/Avatar_1.png", + "assets/pangea/Avatar_2.png", + "assets/pangea/Avatar_3.png", + "assets/pangea/Avatar_4.png", + "assets/pangea/Avatar_5.png", + ]; + + @override + void initState() { + super.initState(); + _createUserInPangea(); + } + + Future _setAvatar() async { + final client = Matrix.of(context).client; + try { + final random = Random(); + final selectedAvatarPath = + avatarPaths[random.nextInt(avatarPaths.length)]; + + final ByteData byteData = await rootBundle.load(selectedAvatarPath); + final Uint8List bytes = byteData.buffer.asUint8List(); + final file = MatrixFile( + bytes: bytes, + name: selectedAvatarPath, + ); + await client.setAvatar(file); + } catch (err, s) { + ErrorHandler.logError( + e: err, + s: s, + data: {}, + ); + } + } + + Future _createUserInPangea() async { + setState(() { + _loadingProfile = true; + _profileError = null; + }); + + final l2Set = await MatrixState.pangeaController.userController.isUserL2Set; + if (l2Set) { + context.go('/registration/course'); + return; + } + + try { + final updateFuture = [ + _setAvatar(), + MatrixState.pangeaController.userController.updateProfile( + (profile) { + final systemLang = MatrixState + .pangeaController.languageController.systemLanguage?.langCode; + + if (systemLang != null) { + profile.userSettings.sourceLanguage = systemLang; + } + + profile.userSettings.targetLanguage = widget.langCode; + profile.userSettings.createdAt = DateTime.now(); + return profile; + }, + waitForDataInSync: true, + ), + MatrixState.pangeaController.userController.updateAnalyticsProfile( + targetLanguage: PLanguageStore.byLangCode(widget.langCode), + baseLanguage: + MatrixState.pangeaController.languageController.systemLanguage, + level: 1, + ), + ]; + + await Future.wait(updateFuture).timeout( + const Duration(seconds: 30), + onTimeout: () { + throw TimeoutException(L10n.of(context).oopsSomethingWentWrong); + }, + ); + + await MatrixState.pangeaController.subscriptionController.reinitialize(); + context.go('/registration/course'); + } catch (err) { + if (err is MatrixException) { + _profileError = err.errorMessage; + } else { + _profileError = err; + } + } finally { + if (mounted) { + setState(() => _loadingProfile = false); + } + } + } + + @override + Widget build(BuildContext context) { + if (_loadingProfile && _profileError != null) { + context.go('/registration/course'); + } + + return Scaffold( + body: SafeArea( + child: Center( + child: _loadingProfile + ? const CircularProgressIndicator.adaptive() + : _profileError != null + ? Column( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: [ + ErrorIndicator( + message: L10n.of(context).oopsSomethingWentWrong, + ), + TextButton( + onPressed: _createUserInPangea, + child: Text(L10n.of(context).tryAgain), + ), + ], + ) + : null, + ), + ), + ); + } +} diff --git a/lib/pangea/login/pages/language_selection_page.dart b/lib/pangea/login/pages/language_selection_page.dart index 339cc65c1..7e080aa6d 100644 --- a/lib/pangea/login/pages/language_selection_page.dart +++ b/lib/pangea/login/pages/language_selection_page.dart @@ -29,6 +29,7 @@ class LanguageSelectionPageState extends State { Widget build(BuildContext context) { final theme = Theme.of(context); final languages = MatrixState.pangeaController.pLanguageStore.targetOptions; + return Scaffold( appBar: AppBar( title: Text(L10n.of(context).languages), @@ -114,11 +115,16 @@ class LanguageSelectionPageState extends State { ), ElevatedButton( onPressed: _selectedLanguage != null - ? () => context.go( - Matrix.of(context).client.isLogged() - ? "/course/${_selectedLanguage!.langCode}" - : "/home/languages/${_selectedLanguage!.langCode}", - ) + ? () { + context.go( + GoRouterState.of(context) + .fullPath + ?.contains('home') == + true + ? '/home/signup/${_selectedLanguage!.langCode}' + : '/registration/${_selectedLanguage!.langCode}', + ); + } : null, style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.surface, diff --git a/lib/pangea/login/pages/login_or_signup_view.dart b/lib/pangea/login/pages/login_or_signup_view.dart index 8d20a710e..161722b2f 100644 --- a/lib/pangea/login/pages/login_or_signup_view.dart +++ b/lib/pangea/login/pages/login_or_signup_view.dart @@ -93,7 +93,7 @@ class LoginOrSignupViewState extends State { mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( - onPressed: () => context.go('/home/languages'), + onPressed: () => context.go('/home/signup'), style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.surface, foregroundColor: theme.colorScheme.onSurface, diff --git a/lib/pangea/login/pages/new_trip_page.dart b/lib/pangea/login/pages/new_trip_page.dart index 6c99d7ff2..2f3716ec6 100644 --- a/lib/pangea/login/pages/new_trip_page.dart +++ b/lib/pangea/login/pages/new_trip_page.dart @@ -10,14 +10,16 @@ import 'package:fluffychat/pangea/course_creation/course_plan_filter_widget.dart import 'package:fluffychat/pangea/course_creation/course_search_provider.dart'; import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; -import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/widgets/matrix.dart'; class NewTripPage extends StatefulWidget { - final String langCode; + final String route; + final String? spaceId; + const NewTripPage({ super.key, - required this.langCode, + required this.route, + this.spaceId, }); @override @@ -29,7 +31,7 @@ class NewTripPageState extends State with CourseSearchProvider { void initState() { super.initState(); - final target = PLanguageStore.byLangCode(widget.langCode); + final target = MatrixState.pangeaController.languageController.userL2; if (target != null) { setTargetLanguageFilter(target); } @@ -43,6 +45,7 @@ class NewTripPageState extends State with CourseSearchProvider { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final spaceId = widget.spaceId; return Scaffold( appBar: AppBar( title: Row( @@ -50,7 +53,11 @@ class NewTripPageState extends State with CourseSearchProvider { mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.map_outlined), - Text(L10n.of(context).startOwnTripTitle), + Text( + spaceId != null + ? L10n.of(context).addCoursePlan + : L10n.of(context).startOwnTripTitle, + ), ], ), ), @@ -133,7 +140,9 @@ class NewTripPageState extends State with CourseSearchProvider { padding: const EdgeInsets.only(bottom: 10.0), child: InkWell( onTap: () => context.go( - '/course/${widget.langCode}/own/${course.uuid}', + spaceId != null + ? '/rooms/spaces/$spaceId/addcourse/${courses[index].uuid}' + : '/${widget.route}/course/own/${course.uuid}', ), borderRadius: BorderRadius.circular(12.0), child: Container( diff --git a/lib/pangea/login/pages/plan_trip_page.dart b/lib/pangea/login/pages/plan_trip_page.dart index ab1397a8a..4a1c028ec 100644 --- a/lib/pangea/login/pages/plan_trip_page.dart +++ b/lib/pangea/login/pages/plan_trip_page.dart @@ -1,134 +1,18 @@ -import 'dart:async'; -import 'dart:math'; - import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:matrix/matrix.dart'; import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/common/widgets/pangea_logo_svg.dart'; -import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; -import 'package:fluffychat/utils/localized_exception_extension.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -class PlanTripPage extends StatefulWidget { - final String langCode; +class PlanTripPage extends StatelessWidget { + final String route; const PlanTripPage({ + required this.route, super.key, - required this.langCode, }); - @override - State createState() => PlanTripPageState(); -} - -class PlanTripPageState extends State { - bool _loadingProfile = true; - Object? _profileError; - - final List avatarPaths = const [ - "assets/pangea/Avatar_1.png", - "assets/pangea/Avatar_2.png", - "assets/pangea/Avatar_3.png", - "assets/pangea/Avatar_4.png", - "assets/pangea/Avatar_5.png", - ]; - - @override - void initState() { - super.initState(); - _createUserInPangea(); - } - - Future _setAvatar() async { - final client = Matrix.of(context).client; - try { - final random = Random(); - final selectedAvatarPath = - avatarPaths[random.nextInt(avatarPaths.length)]; - - final ByteData byteData = await rootBundle.load(selectedAvatarPath); - final Uint8List bytes = byteData.buffer.asUint8List(); - final file = MatrixFile( - bytes: bytes, - name: selectedAvatarPath, - ); - await client.setAvatar(file); - } catch (err, s) { - ErrorHandler.logError( - e: err, - s: s, - data: {}, - ); - } - } - - Future _createUserInPangea() async { - final l2Set = await MatrixState.pangeaController.userController.isUserL2Set; - if (l2Set) { - if (mounted) setState(() => _loadingProfile = false); - return; - } - - if (mounted) { - setState(() { - _loadingProfile = true; - _profileError = null; - }); - } - - try { - final updateFuture = [ - _setAvatar(), - MatrixState.pangeaController.userController.updateProfile( - (profile) { - final systemLang = MatrixState - .pangeaController.languageController.systemLanguage?.langCode; - - if (systemLang != null) { - profile.userSettings.sourceLanguage = systemLang; - } - - profile.userSettings.targetLanguage = widget.langCode; - profile.userSettings.createdAt = DateTime.now(); - return profile; - }, - waitForDataInSync: true, - ), - MatrixState.pangeaController.userController.updateAnalyticsProfile( - targetLanguage: PLanguageStore.byLangCode(widget.langCode), - baseLanguage: - MatrixState.pangeaController.languageController.systemLanguage, - level: 1, - ), - ]; - - await Future.wait(updateFuture).timeout( - const Duration(seconds: 30), - onTimeout: () { - throw TimeoutException(L10n.of(context).oopsSomethingWentWrong); - }, - ); - - await MatrixState.pangeaController.subscriptionController.reinitialize(); - } catch (err) { - if (err is MatrixException) { - _profileError = err.errorMessage; - } else { - _profileError = err.toLocalizedString(context); - } - } finally { - if (mounted) { - setState(() => _loadingProfile = false); - } - } - } - @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -142,113 +26,108 @@ class PlanTripPageState extends State { Text(L10n.of(context).planTrip), ], ), + automaticallyImplyLeading: route == 'registration', ), body: SafeArea( child: Center( - child: _loadingProfile - ? const CircularProgressIndicator.adaptive() - : _profileError != null - ? const ErrorIndicator( - message: "Failed to create profile", - ) - : Container( - padding: const EdgeInsets.all(30.0), - constraints: const BoxConstraints( - maxWidth: 350, - maxHeight: 600, + child: Container( + padding: const EdgeInsets.all(30.0), + constraints: const BoxConstraints( + maxWidth: 350, + maxHeight: 600, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PangeaLogoSvg( + width: 100.0, + forceColor: theme.colorScheme.onSurface, + ), + Column( + spacing: 16.0, + children: [ + Text( + L10n.of(context).howAreYouTraveling, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + ElevatedButton( + onPressed: () => context.go( + '/$route/course/private', + ), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.surface, + foregroundColor: theme.colorScheme.onSurface, + side: BorderSide( + width: 1, + color: theme.colorScheme.onSurface, + ), + ), + child: Row( + spacing: 4.0, + mainAxisAlignment: MainAxisAlignment.center, children: [ - PangeaLogoSvg( - width: 100.0, - forceColor: theme.colorScheme.onSurface, - ), - Column( - spacing: 16.0, - children: [ - Text( - L10n.of(context).howAreYouTraveling, - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ElevatedButton( - onPressed: () => context.go( - "/course/${widget.langCode}/private", - ), - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.surface, - foregroundColor: theme.colorScheme.onSurface, - side: BorderSide( - width: 1, - color: theme.colorScheme.onSurface, - ), - ), - child: Row( - spacing: 4.0, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.map_outlined), - Text(L10n.of(context).unlockPrivateTrip), - ], - ), - ), - ElevatedButton( - onPressed: () => context.go( - "/course/${widget.langCode}/public", - ), - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.surface, - foregroundColor: theme.colorScheme.onSurface, - side: BorderSide( - width: 1, - color: theme.colorScheme.onSurface, - ), - ), - child: Row( - spacing: 4.0, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Symbols.map_search), - Text(L10n.of(context).joinPublicTrip), - ], - ), - ), - ElevatedButton( - onPressed: () => context.go( - "/course/${widget.langCode}/own", - ), - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.surface, - foregroundColor: theme.colorScheme.onSurface, - side: BorderSide( - width: 1, - color: theme.colorScheme.onSurface, - ), - ), - child: Row( - spacing: 4.0, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.map_outlined), - Text(L10n.of(context).startOwnTrip), - ], - ), - ), - ListTile( - contentPadding: const EdgeInsets.all(0.0), - leading: const Icon(Icons.school), - title: Text( - L10n.of(context).tripPlanDesc, - style: theme.textTheme.labelLarge, - ), - ), - ], - ), + const Icon(Icons.map_outlined), + Text(L10n.of(context).unlockPrivateTrip), ], ), ), + ElevatedButton( + onPressed: () => context.go( + '/$route/course/public', + ), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.surface, + foregroundColor: theme.colorScheme.onSurface, + side: BorderSide( + width: 1, + color: theme.colorScheme.onSurface, + ), + ), + child: Row( + spacing: 4.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Symbols.map_search), + Text(L10n.of(context).joinPublicTrip), + ], + ), + ), + ElevatedButton( + onPressed: () => context.go( + '/$route/course/own', + ), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.surface, + foregroundColor: theme.colorScheme.onSurface, + side: BorderSide( + width: 1, + color: theme.colorScheme.onSurface, + ), + ), + child: Row( + spacing: 4.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.map_outlined), + Text(L10n.of(context).startOwnTrip), + ], + ), + ), + ListTile( + contentPadding: const EdgeInsets.all(0.0), + leading: const Icon(Icons.school), + title: Text( + L10n.of(context).tripPlanDesc, + style: theme.textTheme.labelLarge, + ), + ), + ], + ), + ], + ), + ), ), ), ); diff --git a/lib/pangea/login/pages/private_trip_page.dart b/lib/pangea/login/pages/private_trip_page.dart index 18efb578c..8d6c3533c 100644 --- a/lib/pangea/login/pages/private_trip_page.dart +++ b/lib/pangea/login/pages/private_trip_page.dart @@ -5,10 +5,8 @@ import 'package:fluffychat/pangea/common/widgets/pangea_logo_svg.dart'; import 'package:fluffychat/widgets/matrix.dart'; class PrivateTripPage extends StatefulWidget { - final String langCode; const PrivateTripPage({ super.key, - required this.langCode, }); @override diff --git a/lib/pangea/login/pages/public_trip_page.dart b/lib/pangea/login/pages/public_trip_page.dart index 97a214b62..e47f0b117 100644 --- a/lib/pangea/login/pages/public_trip_page.dart +++ b/lib/pangea/login/pages/public_trip_page.dart @@ -5,14 +5,11 @@ import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; import 'package:fluffychat/pangea/course_creation/course_plan_filter_widget.dart'; import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; -import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/widgets/matrix.dart'; class PublicTripPage extends StatefulWidget { - final String langCode; const PublicTripPage({ super.key, - required this.langCode, }); @override @@ -31,7 +28,7 @@ class PublicTripPageState extends State { void initState() { super.initState(); - final target = PLanguageStore.byLangCode(widget.langCode); + final target = MatrixState.pangeaController.languageController.userL2; if (target != null) { setTargetLanguageFilter(target); } diff --git a/lib/pangea/login/pages/signup.dart b/lib/pangea/login/pages/signup.dart index 1f3c6bc37..8062c91cf 100644 --- a/lib/pangea/login/pages/signup.dart +++ b/lib/pangea/login/pages/signup.dart @@ -178,7 +178,7 @@ class SignupPageController extends State { }, ); - if (!resp.isError) context.go("/course/${widget.langCode}"); + if (!resp.isError) context.go('/registration/${widget.langCode}'); } Future _signupFuture() async { diff --git a/lib/pangea/login/pages/signup_view.dart b/lib/pangea/login/pages/signup_view.dart index a9fd8e8cb..f753755c8 100644 --- a/lib/pangea/login/pages/signup_view.dart +++ b/lib/pangea/login/pages/signup_view.dart @@ -59,7 +59,7 @@ class SignupPageView extends StatelessWidget { ), ElevatedButton( onPressed: () => context.go( - '/home/languages/${controller.widget.langCode}/email', + '/home/signup/${controller.widget.langCode}/email', ), style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.surface, diff --git a/lib/pangea/subscription/controllers/subscription_controller.dart b/lib/pangea/subscription/controllers/subscription_controller.dart index 73f216099..395a1c07f 100644 --- a/lib/pangea/subscription/controllers/subscription_controller.dart +++ b/lib/pangea/subscription/controllers/subscription_controller.dart @@ -80,7 +80,9 @@ class SubscriptionController extends BaseController { _isInitializing = true; await _initialize(); _isInitializing = false; - initCompleter.complete(); + if (!initCompleter.isCompleted) { + initCompleter.complete(); + } } Future reinitialize() async { diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index 0b5b538eb..292c17d60 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -134,7 +134,7 @@ class _MainView extends StatelessWidget { return Settings(key: state.pageKey); } - if (path.contains('communities')) { + if (path.contains('course')) { return Center( child: SizedBox( width: 250.0, diff --git a/lib/widgets/navigation_rail.dart b/lib/widgets/navigation_rail.dart index 92485bf58..2c36b67e4 100644 --- a/lib/widgets/navigation_rail.dart +++ b/lib/widgets/navigation_rail.dart @@ -43,7 +43,7 @@ class SpacesNavigationRail extends StatelessWidget { .startsWith('/rooms/settings'); // #Pangea final isAnalytics = path?.contains('analytics') ?? false; - final isCommunities = path?.contains('communities') ?? false; + final isCourse = path?.contains('course') ?? false; final isColumnMode = FluffyThemes.isColumnMode(context); final width = isColumnMode @@ -125,7 +125,7 @@ class SpacesNavigationRail extends StatelessWidget { isSelected: activeSpaceId == null && !isSettings && !isAnalytics && - !isCommunities, + !isCourse, // onTap: onGoToChats, // icon: const Padding( // padding: EdgeInsets.all(10.0), @@ -159,16 +159,16 @@ class SpacesNavigationRail extends StatelessWidget { // toolTip: L10n.of(context).createNewSpace, backgroundColor: Colors.transparent, borderRadius: BorderRadius.circular(0), - isSelected: isCommunities, + isSelected: isCourse, onTap: () { - context.go('/rooms/communities'); + context.go('/rooms/course'); }, icon: ClipPath( clipper: MapClipper(), child: Container( width: width - (isColumnMode ? 32.0 : 24.0), height: width - (isColumnMode ? 32.0 : 24.0), - color: isCommunities + color: isCourse ? Theme.of(context) .colorScheme .primaryContainer