feat: add selected course page for public courses, redirect there on click public course, filter out already-joined public courses (#4276)
This commit is contained in:
parent
0c5a57bc3b
commit
c13c2eb1f6
5 changed files with 158 additions and 27 deletions
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix_api_lite/generated/model.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
|
|
@ -226,10 +227,27 @@ abstract class AppRoutes {
|
|||
context,
|
||||
state,
|
||||
const PublicTripPage(
|
||||
route: 'registration',
|
||||
showFilters: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':courseid',
|
||||
pageBuilder: (context, state) {
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
SelectedCourse(
|
||||
state.pathParameters['courseid']!,
|
||||
SelectedCourseMode.join,
|
||||
roomChunk: state.extra as PublicRoomsChunk?,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: 'own',
|
||||
|
|
@ -252,6 +270,7 @@ abstract class AppRoutes {
|
|||
state,
|
||||
SelectedCourse(
|
||||
state.pathParameters['courseid']!,
|
||||
SelectedCourseMode.launch,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -428,9 +447,27 @@ abstract class AppRoutes {
|
|||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const PublicTripPage(),
|
||||
const PublicTripPage(
|
||||
route: 'rooms',
|
||||
),
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':courseid',
|
||||
pageBuilder: (context, state) {
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
SelectedCourse(
|
||||
state.pathParameters['courseid']!,
|
||||
SelectedCourseMode.join,
|
||||
roomChunk: state.extra as PublicRoomsChunk?,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: 'own',
|
||||
|
|
@ -450,6 +487,7 @@ abstract class AppRoutes {
|
|||
state,
|
||||
SelectedCourse(
|
||||
state.pathParameters['courseid']!,
|
||||
SelectedCourseMode.launch,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -838,6 +876,7 @@ abstract class AppRoutes {
|
|||
state,
|
||||
SelectedCourse(
|
||||
state.pathParameters['courseId']!,
|
||||
SelectedCourseMode.addToSpace,
|
||||
spaceId: state.pathParameters['spaceid']!,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart' as sdk;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/course_creation/selected_course_view.dart';
|
||||
|
|
@ -13,16 +14,65 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
|||
import 'package:fluffychat/pangea/spaces/utils/client_spaces_extension.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
enum SelectedCourseMode { launch, addToSpace, join }
|
||||
|
||||
class SelectedCourse extends StatefulWidget {
|
||||
final String courseId;
|
||||
final SelectedCourseMode mode;
|
||||
|
||||
/// In addToSpace mode, the ID of the space to add the course to.
|
||||
/// In join mode, the ID of the space to join that already has this course.
|
||||
final String? spaceId;
|
||||
const SelectedCourse(this.courseId, {super.key, this.spaceId});
|
||||
|
||||
/// In join mode, the room info for the space that already has this course.
|
||||
final PublicRoomsChunk? roomChunk;
|
||||
|
||||
const SelectedCourse(
|
||||
this.courseId,
|
||||
this.mode, {
|
||||
super.key,
|
||||
this.spaceId,
|
||||
this.roomChunk,
|
||||
});
|
||||
|
||||
@override
|
||||
SelectedCourseController createState() => SelectedCourseController();
|
||||
}
|
||||
|
||||
class SelectedCourseController extends State<SelectedCourse> {
|
||||
String get title {
|
||||
switch (widget.mode) {
|
||||
case SelectedCourseMode.launch:
|
||||
return L10n.of(context).newCourse;
|
||||
case SelectedCourseMode.addToSpace:
|
||||
return L10n.of(context).addCoursePlan;
|
||||
case SelectedCourseMode.join:
|
||||
return L10n.of(context).joinWithClassCode;
|
||||
}
|
||||
}
|
||||
|
||||
String get buttonText {
|
||||
switch (widget.mode) {
|
||||
case SelectedCourseMode.launch:
|
||||
return L10n.of(context).createCourse;
|
||||
case SelectedCourseMode.addToSpace:
|
||||
return L10n.of(context).addCoursePlan;
|
||||
case SelectedCourseMode.join:
|
||||
return L10n.of(context).joinWithClassCode;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> submit(CoursePlanModel course) async {
|
||||
switch (widget.mode) {
|
||||
case SelectedCourseMode.launch:
|
||||
return launchCourse(course);
|
||||
case SelectedCourseMode.addToSpace:
|
||||
return addCourseToSpace(course);
|
||||
case SelectedCourseMode.join:
|
||||
return joinCourse(course);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> launchCourse(CoursePlanModel course) async {
|
||||
final client = Matrix.of(context).client;
|
||||
final Completer<String> completer = Completer<String>();
|
||||
|
|
@ -55,7 +105,10 @@ class SelectedCourseController extends State<SelectedCourse> {
|
|||
}
|
||||
|
||||
Future<void> addCourseToSpace(CoursePlanModel course) async {
|
||||
if (widget.spaceId == null) return;
|
||||
if (widget.spaceId == null) {
|
||||
throw Exception("Space ID is null");
|
||||
}
|
||||
|
||||
final space = Matrix.of(context).client.getRoomById(widget.spaceId!);
|
||||
|
||||
if (space == null) {
|
||||
|
|
@ -76,6 +129,30 @@ class SelectedCourseController extends State<SelectedCourse> {
|
|||
context.go("/rooms/spaces/${space.id}/details");
|
||||
}
|
||||
|
||||
Future<void> joinCourse(CoursePlanModel course) async {
|
||||
if (widget.roomChunk == null) {
|
||||
throw Exception("Room chunk is null");
|
||||
}
|
||||
|
||||
final client = Matrix.of(context).client;
|
||||
final roomId = await client.joinRoom(
|
||||
widget.roomChunk!.roomId,
|
||||
);
|
||||
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room == null || room.membership != Membership.join) {
|
||||
await client.waitForRoomInSync(roomId, join: true);
|
||||
}
|
||||
|
||||
if (client.getRoomById(roomId) == null) {
|
||||
throw Exception("Failed to join room");
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
context.go("/rooms/spaces/$roomId/details");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SelectedCourseView(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,10 @@ class SelectedCourseView extends StatelessWidget {
|
|||
const double mediumIconSize = 16.0;
|
||||
const double smallIconSize = 12.0;
|
||||
|
||||
final spaceId = controller.widget.spaceId;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
spaceId != null
|
||||
? L10n.of(context).addCoursePlan
|
||||
: L10n.of(context).newCourse,
|
||||
controller.title,
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
|
|
@ -63,6 +59,14 @@ class SelectedCourseView extends StatelessWidget {
|
|||
child: ListView.builder(
|
||||
itemCount: course.loadedTopics.length + 2,
|
||||
itemBuilder: (context, index) {
|
||||
String displayname = course.title;
|
||||
final roomChunk = controller.widget.roomChunk;
|
||||
if (roomChunk != null) {
|
||||
displayname = roomChunk.name ??
|
||||
roomChunk.canonicalAlias ??
|
||||
L10n.of(context).emptyChat;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
return Column(
|
||||
spacing: 8.0,
|
||||
|
|
@ -70,7 +74,10 @@ class SelectedCourseView extends StatelessWidget {
|
|||
ClipPath(
|
||||
clipper: MapClipper(),
|
||||
child: ImageByUrl(
|
||||
imageUrl: course.imageUrl,
|
||||
imageUrl: controller
|
||||
.widget.roomChunk?.avatarUrl
|
||||
?.toString() ??
|
||||
course.imageUrl,
|
||||
width: 100.0,
|
||||
borderRadius:
|
||||
BorderRadius.circular(0.0),
|
||||
|
|
@ -85,7 +92,7 @@ class SelectedCourseView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Text(
|
||||
course.title,
|
||||
displayname,
|
||||
style: const TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
),
|
||||
|
|
@ -278,9 +285,7 @@ class SelectedCourseView extends StatelessWidget {
|
|||
),
|
||||
onPressed: () => showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => spaceId != null
|
||||
? controller.addCourseToSpace(course)
|
||||
: controller.launchCourse(course),
|
||||
future: () => controller.submit(course),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
|
|
@ -288,9 +293,7 @@ class SelectedCourseView extends StatelessWidget {
|
|||
children: [
|
||||
const Icon(Icons.map_outlined),
|
||||
Text(
|
||||
spaceId != null
|
||||
? L10n.of(context).addCoursePlan
|
||||
: L10n.of(context).createCourse,
|
||||
controller.buttonText,
|
||||
style: const TextStyle(
|
||||
fontSize: titleFontSize,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -62,17 +62,19 @@ class CoursePlanController extends State<CoursePlanBuilder> {
|
|||
}
|
||||
|
||||
Future<void> _loadCourse() async {
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = null;
|
||||
course = null;
|
||||
});
|
||||
|
||||
if (widget.courseId == null) {
|
||||
widget.onNotFound?.call();
|
||||
setState(() => loading = false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setState(() {
|
||||
loading = true;
|
||||
error = null;
|
||||
course = null;
|
||||
});
|
||||
|
||||
course = await CoursePlansRepo.get(widget.courseId!);
|
||||
widget.onLoaded?.call(course!);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart';
|
||||
|
|
@ -17,9 +18,11 @@ import 'package:fluffychat/widgets/avatar.dart';
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class PublicTripPage extends StatefulWidget {
|
||||
final String route;
|
||||
final bool showFilters;
|
||||
const PublicTripPage({
|
||||
super.key,
|
||||
required this.route,
|
||||
this.showFilters = true,
|
||||
});
|
||||
|
||||
|
|
@ -69,7 +72,13 @@ class PublicTripPageState extends State<PublicTripPage> {
|
|||
}
|
||||
|
||||
List<PublicCoursesChunk> get filteredCourses {
|
||||
List<PublicCoursesChunk> filtered = discoveredCourses;
|
||||
List<PublicCoursesChunk> filtered = discoveredCourses
|
||||
.where(
|
||||
(c) => !Matrix.of(context).client.rooms.any(
|
||||
(r) => r.id == c.room.roomId && r.membership == Membership.join,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (languageLevelFilter != null) {
|
||||
filtered = filtered.where(
|
||||
|
|
@ -131,8 +140,6 @@ class PublicTripPageState extends State<PublicTripPage> {
|
|||
'nextBatch': nextBatch,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -155,7 +162,7 @@ class PublicTripPageState extends State<PublicTripPage> {
|
|||
);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
setState(() => loading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -297,7 +304,10 @@ class PublicTripPageState extends State<PublicTripPage> {
|
|||
L10n.of(context).emptyChat;
|
||||
|
||||
return InkWell(
|
||||
onTap: () {},
|
||||
onTap: () => context.go(
|
||||
'/${widget.route}/course/public/${filteredCourses[index].courseId}',
|
||||
extra: roomChunk,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue