From 506e069997c11a6fa45170b5c64c9341395b2c1e Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 13 Aug 2025 15:25:11 -0400 Subject: [PATCH] chore: display activity role state events (#3732) --- lib/pages/chat/events/message.dart | 5 ++ .../activity_role_model.dart | 17 ++++ .../activity_roles_event.dart | 90 +++++++++++++++++++ .../filtered_timeline_extension.dart | 2 + 4 files changed, 114 insertions(+) create mode 100644 lib/pangea/activity_sessions/activity_roles_event.dart diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 80d66b007..b4dc08406 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/pangea_message_reactions.dart'; import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_creation_state_event.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_roles_event.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_state_event.dart'; import 'package:fluffychat/pangea/chat/extensions/custom_room_display_extension.dart'; @@ -140,6 +141,10 @@ class Message extends StatelessWidget { if (event.type == PangeaEventTypes.activityPlan) { return ActivityStateEvent(event: event); } + + if (event.type == PangeaEventTypes.activityRole) { + return ActivityRolesEvent(event: event); + } // Pangea# return StateMessage(event); diff --git a/lib/pangea/activity_sessions/activity_role_model.dart b/lib/pangea/activity_sessions/activity_role_model.dart index 82dee4d60..d827cd784 100644 --- a/lib/pangea/activity_sessions/activity_role_model.dart +++ b/lib/pangea/activity_sessions/activity_role_model.dart @@ -1,3 +1,5 @@ +import 'package:fluffychat/l10n/l10n.dart'; + class ActivityRoleModel { final String id; final String userId; @@ -17,6 +19,21 @@ class ActivityRoleModel { bool get isArchived => archivedAt != null; + String? stateEventMessage(String displayName, L10n l10n) { + if (isArchived) { + return null; + } + + if (isFinished) { + return l10n.finishedTheActivity(displayName); + } + + return l10n.joinedTheActivity( + displayName, + role ?? l10n.participant, + ); + } + factory ActivityRoleModel.fromJson(Map json) { return ActivityRoleModel( id: json['id'] as String, diff --git a/lib/pangea/activity_sessions/activity_roles_event.dart b/lib/pangea/activity_sessions/activity_roles_event.dart new file mode 100644 index 000000000..5d7fde685 --- /dev/null +++ b/lib/pangea/activity_sessions/activity_roles_event.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +import 'package:collection/collection.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart'; + +class ActivityRolesEvent extends StatelessWidget { + final Event event; + const ActivityRolesEvent({ + super.key, + required this.event, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + Set difference = {}; + try { + final currentRoles = (event.content['roles'] as Map) + .values + .map((v) => ActivityRoleModel.fromJson(v)) + .toSet(); + + final previousRoles = + (event.prevContent?['roles'] as Map?) + ?.values + .map((v) => ActivityRoleModel.fromJson(v)) + .toSet() ?? + {}; + + difference = currentRoles.difference(previousRoles); + } catch (e) { + debugPrint("Failed to parse activity roles: $e"); + } + + if (difference.isEmpty) { + return const SizedBox(); + } + + return Column( + children: difference.map((role) { + final user = event.room.getParticipants().firstWhereOrNull( + (u) => u.id == role.userId, + ); + + final displayName = + user?.calcDisplayname() ?? role.userId.localpart ?? role.userId; + + final message = role.stateEventMessage(displayName, L10n.of(context)); + if (message == null) { + return const SizedBox(); + } + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Material( + color: theme.colorScheme.surface.withAlpha(128), + borderRadius: BorderRadius.circular(AppConfig.borderRadius / 3), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 4.0, + ), + child: Text( + "${role.stateEventMessage(displayName, L10n.of(context))}", + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12 * AppConfig.fontSizeFactor, + decoration: + event.redacted ? TextDecoration.lineThrough : null, + ), + ), + ), + ), + ), + ), + ); + }).toList(), + ); + } +} diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index 173fe5de4..b10adec5d 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -73,6 +73,7 @@ extension IsStateExtension on Event { isEventTypeKnown || [ PangeaEventTypes.activityPlan, + PangeaEventTypes.activityRole, ].contains(type); // we're filtering out some state events that we don't want to render @@ -83,6 +84,7 @@ extension IsStateExtension on Event { EventTypes.RoomTombstone, EventTypes.CallInvite, PangeaEventTypes.activityPlan, + PangeaEventTypes.activityRole, }; // Pangea# }