From b66aa5f9db633fdcbde1556b0c113c610a63ee74 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:06:40 -0500 Subject: [PATCH] fix: update room summary model (#5543) --- .../activity_session_start_page.dart | 2 +- .../activity_sessions_start_view.dart | 4 +- .../utils/room_summary_extension.dart | 121 ++++++++++++++---- .../course_chats/course_chats_page.dart | 8 +- .../activity_summaries_provider.dart | 16 ++- pubspec.yaml | 2 +- 6 files changed, 119 insertions(+), 34 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..c2c39fb85 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 @@ -191,7 +191,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)) 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..4315a11a7 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'; @@ -7,6 +8,8 @@ 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/activity_summary/activity_summary_model.dart'; +import 'package:fluffychat/pangea/course_plans/courses/course_plan_event.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; extension RoomSummaryExtension on Api { @@ -52,27 +55,40 @@ 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 ActivitySummaryModel? activitySummary; + final CoursePlanEvent? coursePlan; + + final JoinRules? joinRule; + final Map? powerLevels; final Map membershipSummary; + final String? displayName; + final String? avatarUrl; RoomSummaryResponse({ - required this.activityPlan, - required this.activityRoles, required this.membershipSummary, + this.activityPlan, + this.activityRoles, + this.activitySummary, + this.coursePlan, + this.joinRule, + this.powerLevels, + this.displayName, + this.avatarUrl, }); + List get adminUserIDs { + if (powerLevels == null) return []; + return powerLevels!.entries + .where((entry) => entry.value >= 100) + .map((entry) => entry.key) + .toList(); + } + Membership? getMembershipForUserId(String userId) { final membershipString = membershipSummary[userId]; if (membershipString == null) return null; @@ -83,32 +99,93 @@ 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 summaryEntry = + json[PangeaEventTypes.activitySummary]?["default"]?["content"]; + ActivitySummaryModel? summary; + if (summaryEntry != null && summaryEntry is Map) { + summary = ActivitySummaryModel.fromJson(summaryEntry); + } + + final coursePlanEntry = + json[PangeaEventTypes.coursePlan]?["default"]?["content"]; + CoursePlanEvent? coursePlan; + if (coursePlanEntry != null && coursePlanEntry is Map) { + coursePlan = CoursePlanEvent.fromJson(coursePlanEntry); + } + + 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); + } + + final displayName = + json[EventTypes.RoomName]?['default']?['content']?['name'] as String?; + + String? avatarUrl = + json[EventTypes.RoomAvatar]?['default']?['content']?['url'] as String?; + if (avatarUrl != null && Uri.tryParse(avatarUrl) == null) { + avatarUrl = null; + } + return RoomSummaryResponse( - activityPlan: ActivityPlanModel.fromJson( - json[PangeaEventTypes.activityPlan]?["default"]?["content"] ?? {}, - ), - activityRoles: ActivityRolesModel.fromJson( - json[PangeaEventTypes.activityRole]?["default"]?["content"] ?? {}, - ), + activityPlan: plan, + activityRoles: roles, + activitySummary: summary, + coursePlan: coursePlan, + powerLevels: powerLevels, + joinRule: joinRule, membershipSummary: Map.from( json['membership_summary'] ?? {}, ), + displayName: displayName, + avatarUrl: avatarUrl, ); } Map toJson() { return { - PangeaEventTypes.activityPlan: activityPlan.toJson(), - PangeaEventTypes.activityRole: activityRoles.toJson(), - 'membership_summary': membershipSummary, + 'activityPlan': activityPlan?.toJson(), + 'activityRoles': activityRoles?.toJson(), + 'activitySummary': activitySummary?.toJson(), + 'coursePlan': coursePlan?.toJson(), + 'joinRule': joinRule?.text, + 'powerLevels': powerLevels, + 'membershipSummary': membershipSummary, + 'displayName': displayName, + 'avatarUrl': avatarUrl, }; } } diff --git a/lib/pangea/course_chats/course_chats_page.dart b/lib/pangea/course_chats/course_chats_page.dart index 1b962497b..6dc095122 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(); } diff --git a/pubspec.yaml b/pubspec.yaml index 6dfb452fe..cfc708560 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.16+5 +version: 4.1.16+6 environment: sdk: ">=3.0.0 <4.0.0"