diff --git a/assets/pangea/check.svg b/assets/pangea/check.svg new file mode 100644 index 000000000..93ad710f3 --- /dev/null +++ b/assets/pangea/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart index 52e53ea6c..4f0696cd1 100644 --- a/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_finished_status_message.dart @@ -36,9 +36,9 @@ class ActivityFinishedStatusMessage extends StatelessWidget { }, ); - if (navigate == true) { + if (navigate == true && controller.room.courseParent != null) { context.go( - "/rooms/analytics?mode=activities", + "/rooms/spaces/${controller.room.courseParent!.id}/details?tab=course", ); } } diff --git a/lib/pangea/activity_suggestions/activity_suggestion_card.dart b/lib/pangea/activity_suggestions/activity_suggestion_card.dart index 68023526d..bb97b58b4 100644 --- a/lib/pangea/activity_suggestions/activity_suggestion_card.dart +++ b/lib/pangea/activity_suggestions/activity_suggestion_card.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pangea/common/widgets/url_image_widget.dart'; class ActivitySuggestionCard extends StatelessWidget { final ActivityPlanModel activity; - final VoidCallback onPressed; final double width; final double height; @@ -18,7 +17,6 @@ class ActivitySuggestionCard extends StatelessWidget { const ActivitySuggestionCard({ super.key, required this.activity, - required this.onPressed, required this.width, required this.height, this.fontSize, @@ -29,87 +27,81 @@ class ActivitySuggestionCard extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onPressed, - child: SizedBox( - height: height, - width: width, - child: ClipRRect( - borderRadius: BorderRadius.circular(12.0), - child: Container( - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainer, + return SizedBox( + height: height, + width: width, + child: ClipRRect( + borderRadius: BorderRadius.circular(12.0), + child: Container( + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainer, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImageByUrl( + imageUrl: activity.imageURL, + width: width, + borderRadius: const BorderRadius.all(Radius.zero), + replacement: SizedBox(height: width), ), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ImageByUrl( - imageUrl: activity.imageURL, - width: width, - borderRadius: const BorderRadius.all(Radius.zero), - replacement: SizedBox(height: width), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, + Expanded( + child: Padding( + padding: const EdgeInsets.all(4.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + activity.title, + style: TextStyle( + fontSize: fontSize, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + Row( + mainAxisSize: MainAxisSize.min, + spacing: 8.0, children: [ - Text( - activity.title, - style: TextStyle( - fontSize: fontSize, - ), - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - Row( - mainAxisSize: MainAxisSize.min, - spacing: 8.0, - children: [ - if (activity.req.mode.isNotEmpty) - Padding( - padding: const EdgeInsets.all(4.0), - child: Text( - activity.req.mode, - style: fontSizeSmall != null - ? TextStyle(fontSize: fontSizeSmall) - : theme.textTheme.labelSmall, - overflow: TextOverflow.ellipsis, - ), - ), - Padding( - padding: const EdgeInsets.all(4.0), - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 4.0, - children: [ - Icon( - Icons.group_outlined, - size: iconSize ?? 12.0, - ), - Text( - "${activity.req.numberOfParticipants}", - style: fontSizeSmall != null - ? TextStyle(fontSize: fontSizeSmall) - : theme.textTheme.labelSmall, - ), - ], - ), + if (activity.req.mode.isNotEmpty) + Padding( + padding: const EdgeInsets.all(4.0), + child: Text( + activity.req.mode, + style: fontSizeSmall != null + ? TextStyle(fontSize: fontSizeSmall) + : theme.textTheme.labelSmall, + overflow: TextOverflow.ellipsis, ), - ], + ), + Padding( + padding: const EdgeInsets.all(4.0), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 4.0, + children: [ + Icon( + Icons.group_outlined, + size: iconSize ?? 12.0, + ), + Text( + "${activity.req.numberOfParticipants}", + style: fontSizeSmall != null + ? TextStyle(fontSize: fontSizeSmall) + : theme.textTheme.labelSmall, + ), + ], + ), ), ], ), - ), + ], ), - ], + ), ), - ), + ], ), ), ), diff --git a/lib/pangea/course_plans/course_plan_room_extension.dart b/lib/pangea/course_plans/course_plan_room_extension.dart index 69e4ec29b..ab4a3649a 100644 --- a/lib/pangea/course_plans/course_plan_room_extension.dart +++ b/lib/pangea/course_plans/course_plan_room_extension.dart @@ -3,6 +3,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_roles_model.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart'; @@ -32,6 +33,15 @@ extension CoursePlanRoomExtension on Room { CourseUserState? get _ownCourseState => _courseUserState(client.userID!); + bool hasCompletedActivity( + String userID, + String activityID, + ) { + final state = _courseUserState(userID); + if (state == null) return false; + return state.hasCompletedActivity(activityID); + } + bool _hasCompletedTopic( String userID, String topicID, @@ -90,6 +100,17 @@ extension CoursePlanRoomExtension on Room { int ownCurrentTopicIndex(CoursePlanModel course) => currentTopicIndex(client.userID!, course); + String? activeActivityRoomId(String activityId) { + for (final child in spaceChildren) { + if (child.roomId == null) continue; + final room = client.getRoomById(child.roomId!); + if (room?.activityId == activityId && !room!.isHiddenActivityRoom) { + return room.id; + } + } + return null; + } + Future>> topicsToUsers(CoursePlanModel course) async { final Map> topicUserMap = {}; final users = await requestParticipants( diff --git a/lib/pangea/course_plans/course_user_event.dart b/lib/pangea/course_plans/course_user_event.dart index f2c0078a6..4a4c57a6b 100644 --- a/lib/pangea/course_plans/course_user_event.dart +++ b/lib/pangea/course_plans/course_user_event.dart @@ -21,6 +21,14 @@ class CourseUserState { return _completedActivities[topicID] ?? []; } + bool hasCompletedActivity( + String activityID, + ) { + return _completedActivities.values.any( + (activities) => activities.contains(activityID), + ); + } + factory CourseUserState.fromJson(Map json) { final Map> activities = {}; final activityEntry = diff --git a/lib/pangea/course_settings/course_settings.dart b/lib/pangea/course_settings/course_settings.dart index 1912f7eb2..2cc0b27ec 100644 --- a/lib/pangea/course_settings/course_settings.dart +++ b/lib/pangea/course_settings/course_settings.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; @@ -164,24 +165,65 @@ class CourseSettings extends StatelessWidget { ), if (unlocked) SizedBox( - height: 210.0, + height: isColumnMode ? 290.0 : 210.0, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: topic.loadedActivities.length, itemBuilder: (context, index) { + final activityId = + topic.loadedActivities[index].activityId; + + final complete = room.hasCompletedActivity( + room.client.userID!, + activityId, + ); + + final activityRoomId = room.activeActivityRoomId( + activityId, + ); + return Padding( padding: const EdgeInsets.only(right: 24.0), - child: ActivitySuggestionCard( - activity: topic.loadedActivities[index], - // TODO: go to activity start page - onPressed: () => context.go( - "/rooms/spaces/${room.id}/activity/${topic.loadedActivities[index].activityId}", + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => context.go( + activityRoomId != null + ? "/rooms/spaces/${room.id}/$activityRoomId" + : "/rooms/spaces/${room.id}/activity/$activityId", + ), + child: Stack( + children: [ + ActivitySuggestionCard( + activity: topic.loadedActivities[index], + width: isColumnMode ? 160.0 : 120.0, + height: isColumnMode ? 280.0 : 200.0, + fontSize: isColumnMode ? 20.0 : 12.0, + fontSizeSmall: + isColumnMode ? 12.0 : 8.0, + iconSize: isColumnMode ? 12.0 : 8.0, + ), + if (complete) + Container( + width: isColumnMode ? 160.0 : 120.0, + height: isColumnMode ? 280.0 : 200.0, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(12.0), + color: theme.colorScheme.surface + .withAlpha(180), + ), + child: Center( + child: SvgPicture.asset( + "assets/pangea/check.svg", + width: 48.0, + height: 48.0, + ), + ), + ), + ], + ), ), - width: 120.0, - height: 200.0, - fontSize: 12.0, - fontSizeSmall: 8.0, - iconSize: 8.0, ), ); },