From 597387def425911f4a394834b1b6b3d99544da2e Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:13:50 -0500 Subject: [PATCH] chore: update room summary model (#5502) --- .../activity_session_start_page.dart | 14 +++- .../activity_sessions_start_view.dart | 4 +- .../utils/room_summary_extension.dart | 71 ++++++++++++------- .../course_chats/course_chats_page.dart | 8 ++- .../activity_summaries_provider.dart | 16 +++-- 5 files changed, 74 insertions(+), 39 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 95a3366e2..ecfaeae14 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 @@ -14,6 +14,7 @@ import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activ import 'package:fluffychat/pangea/activity_sessions/activity_session_start/bot_join_error_dialog.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/pangea/chat_settings/utils/room_summary_extension.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/course_plans/course_activities/activity_summaries_provider.dart'; import 'package:fluffychat/pangea/course_plans/course_activities/course_activity_repo.dart'; import 'package:fluffychat/pangea/course_plans/course_activities/course_activity_translation_request.dart'; @@ -191,7 +192,7 @@ class ActivitySessionStartController extends State final availableRoles = activity!.roles; final assignedRoles = activityRoom?.assignedRoles ?? - roomSummaries?[widget.roomId]?.activityRoles.roles ?? + roomSummaries?[widget.roomId]?.activityRoles?.roles ?? {}; final unassignedIds = availableRoles.keys .where((id) => !assignedRoles.containsKey(id)) @@ -293,8 +294,17 @@ class ActivitySessionStartController extends State ); } await Future.wait(futures); - } catch (e) { + } catch (e, s) { error = e; + ErrorHandler.logError( + e: e, + s: s, + data: { + "activityId": widget.activityId, + "roomId": widget.roomId, + "parentId": widget.parentId, + }, + ); } finally { if (mounted) { setState(() => loading = false); 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 788a94f21..eb11a47fa 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 @@ -518,7 +518,7 @@ class _ActivityStatuses extends StatelessWidget { // room (like the bot). Otherwise, show only joined users with roles Map activityRoles = status == ActivitySummaryStatus.completed - ? e.value.activityRoles.roles + ? (e.value.activityRoles?.roles ?? {}) : e.value.joinedUsersWithRoles; // If the user is in the activity room and it's not completed, use the room's @@ -530,7 +530,7 @@ class _ActivityStatuses extends StatelessWidget { return ListTile( title: OpenRolesIndicator( - roles: activityPlan.roles.values + roles: (activityPlan?.roles.values ?? []) .sorted((a, b) => a.id.compareTo(b.id)) .toList(), assignedRoles: activityRoles.values.toList(), diff --git a/lib/pangea/chat_settings/utils/room_summary_extension.dart b/lib/pangea/chat_settings/utils/room_summary_extension.dart index fd72f3669..bcb2bed1b 100644 --- a/lib/pangea/chat_settings/utils/room_summary_extension.dart +++ b/lib/pangea/chat_settings/utils/room_summary_extension.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:http/http.dart' hide Client; import 'package:matrix/matrix.dart'; import 'package:matrix/matrix_api_lite/generated/api.dart'; @@ -52,25 +53,21 @@ class RoomSummariesResponse { }); return RoomSummariesResponse(summaries: summaries); } - - Map toJson() { - final json = {}; - summaries.forEach((key, value) { - json[key] = value.toJson(); - }); - return json; - } } class RoomSummaryResponse { - final ActivityPlanModel activityPlan; - final ActivityRolesModel activityRoles; + final ActivityPlanModel? activityPlan; + final ActivityRolesModel? activityRoles; + final JoinRules? joinRule; + final Map? powerLevels; final Map membershipSummary; RoomSummaryResponse({ - required this.activityPlan, - required this.activityRoles, required this.membershipSummary, + this.activityPlan, + this.activityRoles, + this.joinRule, + this.powerLevels, }); Membership? getMembershipForUserId(String userId) { @@ -83,32 +80,52 @@ class RoomSummaryResponse { } Map get joinedUsersWithRoles { + if (activityRoles == null) return {}; return Map.fromEntries( - activityRoles.roles.entries.where( + activityRoles!.roles.entries.where( (role) => getMembershipForUserId(role.value.userId) == Membership.join, ), ); } factory RoomSummaryResponse.fromJson(Map json) { + final planEntry = + json[PangeaEventTypes.activityPlan]?["default"]?["content"]; + ActivityPlanModel? plan; + if (planEntry != null && planEntry is Map) { + plan = ActivityPlanModel.fromJson(planEntry); + } + + final rolesEntry = + json[PangeaEventTypes.activityRole]?["default"]?["content"]; + ActivityRolesModel? roles; + if (rolesEntry != null && rolesEntry is Map) { + roles = ActivityRolesModel.fromJson(rolesEntry); + } + + final powerLevelsEntry = + json[EventTypes.RoomPowerLevels]?['default']?['content']?['users']; + Map? powerLevels; + if (powerLevelsEntry != null) { + powerLevels = Map.from(powerLevelsEntry); + } + + final joinRulesString = + json[EventTypes.RoomJoinRules]?['default']?['content']?['join_rule']; + JoinRules? joinRule; + if (joinRulesString != null && joinRulesString is String) { + joinRule = JoinRules.values + .singleWhereOrNull((element) => element.text == joinRulesString); + } + return RoomSummaryResponse( - activityPlan: ActivityPlanModel.fromJson( - json[PangeaEventTypes.activityPlan]?["default"]?["content"] ?? {}, - ), - activityRoles: ActivityRolesModel.fromJson( - json[PangeaEventTypes.activityRole]?["default"]?["content"] ?? {}, - ), + activityPlan: plan, + activityRoles: roles, + powerLevels: powerLevels, + joinRule: joinRule, membershipSummary: Map.from( json['membership_summary'] ?? {}, ), ); } - - Map toJson() { - 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 b01f8d04c..525852251 100644 --- a/lib/pangea/course_chats/course_chats_page.dart +++ b/lib/pangea/course_chats/course_chats_page.dart @@ -133,9 +133,13 @@ class CourseChatsController extends State } final activity = summary.activityPlan; + final roles = summary.activityRoles; final users = summary.joinedUsersWithRoles; - if (users.isEmpty || !validIDs.contains(activity.activityId)) { + if (activity == null || + roles == null || + users.isEmpty || + !validIDs.contains(activity.activityId)) { continue; } @@ -148,7 +152,7 @@ class CourseChatsController extends State // It's possible for users to finish an activity and then for some of the // users to leave, but if the activity was archived by anyone, that means // it was full at some point. - if (summary.activityRoles.roles.values.any((role) => role.isArchived)) { + if (roles.roles.values.any((role) => role.isArchived)) { continue; } 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 6b87e1f75..daf7be1a1 100644 --- a/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart +++ b/lib/pangea/course_plans/course_activities/activity_summaries_provider.dart @@ -48,6 +48,7 @@ mixin ActivitySummariesProvider on State { final activityPlan = roomSummary.activityPlan; final assignedRoles = roomSummary.joinedUsersWithRoles; + if (activityPlan == null) return false; return activityPlan.roles.length - assignedRoles.length <= 0; } @@ -56,6 +57,7 @@ mixin ActivitySummariesProvider on State { if (roomSummary == null) return false; final activityRoles = roomSummary.activityRoles; + if (activityRoles == null) return false; final roles = activityRoles.roles.values.where( (r) => r.userId != BotName.byEnvironment, ); @@ -76,7 +78,7 @@ mixin ActivitySummariesProvider on State { Map activitySessions(String activityId) => Map.fromEntries( roomSummaries?.entries - .where((v) => v.value.activityPlan.activityId == activityId) ?? + .where((v) => v.value.activityPlan?.activityId == activityId) ?? [], ); @@ -115,7 +117,7 @@ mixin ActivitySummariesProvider on State { final summary = entry.value; final roomId = entry.key; - if (summary.activityPlan.activityId != activityId) { + if (summary.activityPlan?.activityId != activityId) { continue; } @@ -132,11 +134,13 @@ mixin ActivitySummariesProvider on State { if (roomSummaries == null || roomSummaries!.isEmpty) return {}; return roomSummaries!.values .where( - (entry) => entry.activityRoles.roles.values.any( - (v) => v.userId == userID && v.isArchived, - ), + (entry) => + entry.activityRoles != null && + entry.activityRoles!.roles.values.any( + (v) => v.userId == userID && v.isArchived, + ), ) - .map((e) => e.activityPlan.activityId) + .map((e) => e.activityPlan?.activityId) .whereType() .toSet(); }