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
This commit is contained in:
ggurdin 2025-09-29 11:28:20 -04:00 committed by GitHub
parent 783085d44c
commit a9510d26f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 691 additions and 1235 deletions

View file

@ -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']!,
),
),

View file

@ -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<NewCourse> createState() => NewCourseController();
}
class NewCourseController extends State<NewCourse> 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<LanguageModel>(
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<LanguageModel>(
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<LanguageLevelTypeEnum>(
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,
),
),
),
);
},
),
],
),
),
),
);
}
}

View file

@ -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,
),
),
],
),
),
),
],
),
),
],
),
],
),
);
},
),
);
},
),
),
);
}

View file

@ -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) {

View file

@ -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<FindYourPeople> createState() => FindYourPeopleState();
}
class FindYourPeopleState extends State<FindYourPeople> {
final TextEditingController searchController = TextEditingController();
Object? error;
bool loading = true;
Timer? _coolDown;
final List<PublicRoomsChunk> 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<void> 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);
}
}

View file

@ -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),
);
},
),
),
],
),
),
],
),
),
);
}
}

View file

@ -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,
),
],
),
),
);
}
}

View file

@ -12,7 +12,7 @@ class PAuthGaurd {
static PangeaController? pController;
/// Redirect for /home routes
static FutureOr<String?> loggedInRedirect(
static FutureOr<String?> 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 doesnt 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<String?> loggedOutRedirect(
/// Redirect for /rooms routes
static FutureOr<String?> roomsRedirect(
BuildContext context,
GoRouterState state,
) async {
@ -42,24 +50,30 @@ class PAuthGaurd {
return '/home';
}
return _onboardingRedirect(context, state);
}
static Future<String?> _onboardingRedirect(
BuildContext context,
GoRouterState state,
) async {
// If user hasn't set their L2,
// and their URL doesnt 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<String?> 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';
}
}

View file

@ -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<CreatePangeaAccountPage> {
bool _loadingProfile = true;
Object? _profileError;
final List<String> 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<void> _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<void> _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,
),
),
);
}
}

View file

@ -29,6 +29,7 @@ class LanguageSelectionPageState extends State<LanguageSelectionPage> {
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<LanguageSelectionPage> {
),
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,

View file

@ -93,7 +93,7 @@ class LoginOrSignupViewState extends State<LoginOrSignupView> {
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,

View file

@ -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<NewTripPage> 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<NewTripPage> 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<NewTripPage> 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<NewTripPage> 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(

View file

@ -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<PlanTripPage> createState() => PlanTripPageState();
}
class PlanTripPageState extends State<PlanTripPage> {
bool _loadingProfile = true;
Object? _profileError;
final List<String> 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<void> _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<void> _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<PlanTripPage> {
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,
),
),
],
),
],
),
),
),
),
);

View file

@ -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

View file

@ -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<PublicTripPage> {
void initState() {
super.initState();
final target = PLanguageStore.byLangCode(widget.langCode);
final target = MatrixState.pangeaController.languageController.userL2;
if (target != null) {
setTargetLanguageFilter(target);
}

View file

@ -178,7 +178,7 @@ class SignupPageController extends State<SignupPage> {
},
);
if (!resp.isError) context.go("/course/${widget.langCode}");
if (!resp.isError) context.go('/registration/${widget.langCode}');
}
Future<void> _signupFuture() async {

View file

@ -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,

View file

@ -80,7 +80,9 @@ class SubscriptionController extends BaseController {
_isInitializing = true;
await _initialize();
_isInitializing = false;
initCompleter.complete();
if (!initCompleter.isCompleted) {
initCompleter.complete();
}
}
Future<void> reinitialize() async {

View file

@ -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,

View file

@ -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