From 968059f81828beb3a7f3a387e8000da8afa74d1d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 3 Dec 2025 09:58:33 -0500 Subject: [PATCH] refactor: use membership summary from room_preview response to tell which users have left an activity session --- .../activity_session_start_page.dart | 10 +++- .../activity_sessions_start_view.dart | 21 ++++++-- .../utils/room_summary_extension.dart | 24 +++++++++ .../course_chats/course_chats_page.dart | 4 +- .../activity_summaries_provider.dart | 51 ++++++++++++++----- 5 files changed, 89 insertions(+), 21 deletions(-) diff --git a/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart b/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart index 30c00937c..72175e6d5 100644 --- a/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart +++ b/lib/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart @@ -154,7 +154,7 @@ class ActivitySessionStartController extends State final availableRoles = activity!.roles; final assignedRoles = activityRoom?.assignedRoles ?? - roomSummaries?[widget.roomId]?.activityRoles.roles ?? + roomSummaries?[widget.roomId]?.joinedUsersWithRoles ?? {}; final unassignedIds = availableRoles.keys .where((id) => !assignedRoles.containsKey(id)) @@ -401,6 +401,14 @@ class ActivitySessionStartController extends State } Future joinActivityByRoomId(String roomId) async { + final room = Matrix.of(context).client.getRoomById(roomId); + if (room != null && room.membership == Membership.join) { + widget.parentId != null + ? context.go("/rooms/spaces/${widget.parentId}/$roomId") + : context.go("/rooms/$roomId"); + return; + } + final resp = await showFutureLoadingDialog( context: context, future: () async { diff --git a/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart b/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart index 82bf7edb0..ba330d719 100644 --- a/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart +++ b/lib/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/activity_feedback/activity_feedback_repo.dart'; import 'package:fluffychat/pangea/activity_feedback/activity_feedback_request.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_feedback_request_dialog.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_feedback_response_dialog.dart'; @@ -153,8 +154,7 @@ class ActivitySessionStartView extends StatelessWidget { assignedRoles: controller .roomSummaries?[ controller.widget.roomId] - ?.activityRoles - .roles ?? + ?.joinedUsersWithRoles ?? {}, ), if (controller.courseParent != null) @@ -456,10 +456,23 @@ class _ActivityStatuses extends StatelessWidget { final roomId = e.key; final room = Matrix.of(context).client.getRoomById(roomId); + final activityPlan = room?.activityPlan ?? e.value.activityPlan; - final activityRoles = - room?.assignedRoles ?? e.value.activityRoles.roles; + + // If activity is completed, show all roles, even for users who have left the + // room (like the bot). Otherwise, show only joined users with roles + Map activityRoles = + status == ActivitySummaryStatus.completed + ? e.value.activityRoles.roles + : e.value.joinedUsersWithRoles; + + // If the user is in the activity room and it's not completed, use the room's + // state events to determine roles to update them in real-time + if (room?.assignedRoles != null && + status != ActivitySummaryStatus.completed) { + activityRoles = room!.assignedRoles!; + } return ListTile( title: OpenRolesIndicator( diff --git a/lib/pangea/chat_settings/utils/room_summary_extension.dart b/lib/pangea/chat_settings/utils/room_summary_extension.dart index ac1eb415c..fd72f3669 100644 --- a/lib/pangea/chat_settings/utils/room_summary_extension.dart +++ b/lib/pangea/chat_settings/utils/room_summary_extension.dart @@ -5,6 +5,7 @@ import 'package:matrix/matrix.dart'; import 'package:matrix/matrix_api_lite/generated/api.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/events/constants/pangea_event_types.dart'; @@ -64,12 +65,31 @@ class RoomSummariesResponse { class RoomSummaryResponse { final ActivityPlanModel activityPlan; final ActivityRolesModel activityRoles; + final Map membershipSummary; RoomSummaryResponse({ required this.activityPlan, required this.activityRoles, + required this.membershipSummary, }); + Membership? getMembershipForUserId(String userId) { + final membershipString = membershipSummary[userId]; + if (membershipString == null) return null; + return Membership.values.firstWhere( + (m) => m.name == membershipString, + orElse: () => Membership.join, + ); + } + + Map get joinedUsersWithRoles { + return Map.fromEntries( + activityRoles.roles.entries.where( + (role) => getMembershipForUserId(role.value.userId) == Membership.join, + ), + ); + } + factory RoomSummaryResponse.fromJson(Map json) { return RoomSummaryResponse( activityPlan: ActivityPlanModel.fromJson( @@ -78,6 +98,9 @@ class RoomSummaryResponse { activityRoles: ActivityRolesModel.fromJson( json[PangeaEventTypes.activityRole]?["default"]?["content"] ?? {}, ), + membershipSummary: Map.from( + json['membership_summary'] ?? {}, + ), ); } @@ -85,6 +108,7 @@ class RoomSummaryResponse { return { PangeaEventTypes.activityPlan: activityPlan.toJson(), PangeaEventTypes.activityRole: activityRoles.toJson(), + 'membership_summary': membershipSummary, }; } } diff --git a/lib/pangea/course_chats/course_chats_page.dart b/lib/pangea/course_chats/course_chats_page.dart index 4f82430b5..13a644763 100644 --- a/lib/pangea/course_chats/course_chats_page.dart +++ b/lib/pangea/course_chats/course_chats_page.dart @@ -131,7 +131,7 @@ class CourseChatsController extends State } final activity = summary.activityPlan; - final users = summary.activityRoles.roles.values.toList(); + final users = summary.joinedUsersWithRoles; if (users.isEmpty || !validIDs.contains(activity.activityId)) { continue; @@ -154,7 +154,7 @@ class CourseChatsController extends State sessionsMap[activity]!.add( ExtendedSpaceRoomsChunk( chunk: chunk, - assignedRoles: users, + assignedRoles: users.values.toList(), ), ); } diff --git a/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart b/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart index af34d12d0..3963caeac 100644 --- a/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart +++ b/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart @@ -41,6 +41,38 @@ mixin ActivitySummariesProvider on State { } } + bool isActivityStarted(String roomId) { + if (isActivityFinished(roomId)) return true; + final roomSummary = roomSummaries?[roomId]; + if (roomSummary == null) return false; + + final activityPlan = roomSummary.activityPlan; + final assignedRoles = roomSummary.joinedUsersWithRoles; + return activityPlan.roles.length - assignedRoles.length <= 0; + } + + bool isActivityFinished(String roomId) { + final roomSummary = roomSummaries?[roomId]; + if (roomSummary == null) return false; + + final activityRoles = roomSummary.activityRoles; + final roles = activityRoles.roles.values.where( + (r) => r.userId != BotName.byEnvironment, + ); + + if (roles.isEmpty) return false; + if (!roles.any((r) => r.isFinished)) return false; + + return roles.every((r) { + if (r.isFinished) return true; + + // if the user is in the chat (not null && membership is join), + // then the activity is not finished for them + final membership = roomSummary.getMembershipForUserId(r.userId); + return membership == null || membership != Membership.join; + }); + } + Map activitySessions(String activityId) => Map.fromEntries( roomSummaries?.entries @@ -62,17 +94,13 @@ mixin ActivitySummariesProvider on State { for (final entry in sessions.entries) { final session = entry.value; final roomId = entry.key; - final roles = session.activityRoles.roles.values; - if (roles.isNotEmpty && - (roles.any((r) => r.isArchived) || - roles.every((r) => r.isFinished))) { + if (isActivityFinished(roomId)) { statuses[ActivitySummaryStatus.completed]![roomId] = session; - } else if (session.activityRoles.roles.length < - session.activityPlan.req.numberOfParticipants) { - statuses[ActivitySummaryStatus.notStarted]![roomId] = session; - } else { + } else if (isActivityStarted(roomId)) { statuses[ActivitySummaryStatus.inProgress]![roomId] = session; + } else { + statuses[ActivitySummaryStatus.notStarted]![roomId] = session; } } @@ -91,12 +119,7 @@ mixin ActivitySummariesProvider on State { continue; } - final isOpen = - !summary.activityRoles.roles.values.any((r) => r.isArchived) && - (summary.activityRoles.roles.length < - summary.activityPlan.req.numberOfParticipants); - - if (isOpen) { + if (!isActivityStarted(roomId)) { sessions.add(roomId); } }