diff --git a/lib/pangea/activity_sessions/activity_participant_list.dart b/lib/pangea/activity_sessions/activity_participant_list.dart index d3decbf81..fc86daded 100644 --- a/lib/pangea/activity_sessions/activity_participant_list.dart +++ b/lib/pangea/activity_sessions/activity_participant_list.dart @@ -42,7 +42,8 @@ class ActivityParticipantList extends StatelessWidget { room: room, builder: (context, participants) { final theme = Theme.of(context); - final availableRoles = activity.roles; + final availableRoles = + activity.roles.values.sorted((a, b) => a.id.compareTo(b.id)); final remainingMembers = participants.participants.where( (p) => !assignedRoles.values.any((r) => r.userId == p.id), @@ -55,7 +56,7 @@ class ActivityParticipantList extends StatelessWidget { alignment: WrapAlignment.center, spacing: 12.0, runSpacing: 12.0, - children: availableRoles.values.map((availableRole) { + children: availableRoles.map((availableRole) { final selected = isSelected != null ? isSelected!(availableRole.id) : false; 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 c471376df..7377eacaa 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 @@ -161,7 +161,7 @@ class ActivitySessionStartController extends State if (activityRoom != null && activityRoom!.membership == Membership.join) { return activityRoom!.assignedRoles ?? {}; } - return roomSummaries?[widget.roomId]?.activityRoles.roles ?? {}; + return roomSummaries?[widget.roomId]?.joinedUsersWithRoles ?? {}; } bool canSelectParticipant(String id) { @@ -172,7 +172,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)) @@ -441,6 +441,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 dd32046c2..641e8fded 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 @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; @@ -7,6 +8,8 @@ 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_response_dialog.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart'; @@ -459,13 +462,35 @@ class _ActivityStatuses extends StatelessWidget { ), ), ...entry.entries.map((e) { - final summary = e.value; + // if user is in the room, use the room info instead of the + // room summary response to get real-time activity roles info final roomId = e.key; + final room = + Matrix.of(context).client.getRoomById(roomId); + + final activityPlan = + room?.activityPlan ?? e.value.activityPlan; + + // 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( - roles: summary.activityPlan.roles.values.toList(), - assignedRoles: - summary.activityRoles.roles.values.toList(), + roles: activityPlan.roles.values + .sorted((a, b) => a.id.compareTo(b.id)) + .toList(), + assignedRoles: activityRoles.values.toList(), size: 40.0, spacing: 8.0, space: space, 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/chat_settings/widgets/chat_context_menu_action.dart b/lib/pangea/chat_settings/widgets/chat_context_menu_action.dart index 281311ff7..db4f4ddde 100644 --- a/lib/pangea/chat_settings/widgets/chat_context_menu_action.dart +++ b/lib/pangea/chat_settings/widgets/chat_context_menu_action.dart @@ -252,6 +252,12 @@ void chatContextMenuAction( context: context, future: room.isSpace ? room.leaveSpace : room.leave, ); + + final r = room.client.getRoomById(room.id); + if (r != null && r.membership != Membership.leave) { + await room.client.waitForRoomInSync(room.id, leave: true); + } + if (!resp.isError) { outerContext.go( room.courseParent != null diff --git a/lib/pangea/course_chats/course_chats_page.dart b/lib/pangea/course_chats/course_chats_page.dart index f2308a145..1c9af4e2f 100644 --- a/lib/pangea/course_chats/course_chats_page.dart +++ b/lib/pangea/course_chats/course_chats_page.dart @@ -132,7 +132,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; @@ -155,7 +155,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 26e261910..26806e52d 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); } } diff --git a/pubspec.yaml b/pubspec.yaml index 2d61d5620..2dd3cc8b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ description: Learn a language while texting your friends. # Pangea# publish_to: none # On version bump also increase the build number for F-Droid -version: 4.1.15+9 +version: 4.1.15+10 environment: sdk: ">=3.0.0 <4.0.0"