From 2b118e8185ab8e473613221882b01beed49201d7 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Fri, 9 Jan 2026 10:49:36 -0500 Subject: [PATCH] fix: add reaction notifier to rebuild reaction picker and reaction display on reaction change (#5151) --- .../chat/events/pangea_message_reactions.dart | 35 +++++++------------ lib/pages/chat/events/reaction_listener.dart | 34 ++++++++++++++++++ .../layout/message_selection_positioner.dart | 21 ++++++++++- .../toolbar/layout/over_message_overlay.dart | 2 +- .../layout/overlay_center_content.dart | 17 +++++---- .../practice_mode_transition_animation.dart | 2 ++ .../toolbar/word_card/word_card_switcher.dart | 7 ++-- 7 files changed, 84 insertions(+), 34 deletions(-) create mode 100644 lib/pages/chat/events/reaction_listener.dart diff --git a/lib/pages/chat/events/pangea_message_reactions.dart b/lib/pages/chat/events/pangea_message_reactions.dart index b1197a97b..30b497909 100644 --- a/lib/pages/chat/events/pangea_message_reactions.dart +++ b/lib/pages/chat/events/pangea_message_reactions.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/emoji_burst.dart'; +import 'package:fluffychat/pages/chat/events/reaction_listener.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -35,10 +36,10 @@ class PangeaMessageReactions extends StatefulWidget { } class _PangeaMessageReactionsState extends State { - StreamSubscription? _reactionSubscription; Map _reactionMap = {}; Set _newlyAddedReactions = {}; late Client client; + ReactionListener? _reactionListener; @override void initState() { @@ -48,23 +49,17 @@ class _PangeaMessageReactionsState extends State { _setupReactionStream(); } - void _setupReactionStream() { - _reactionSubscription = widget.controller.room.client.onSync.stream.where( - (update) { - final room = widget.controller.room; - final timelineEvents = update.rooms?.join?[room.id]?.timeline?.events; - if (timelineEvents == null) return false; + @override + void dispose() { + _reactionListener?.dispose(); + super.dispose(); + } - final eventID = widget.event.eventId; - return timelineEvents.any( - (e) => - e.type == EventTypes.Redaction || - (e.type == EventTypes.Reaction && - Event.fromMatrixEvent(e, room).relationshipEventId == - eventID), - ); - }, - ).listen(_onReactionUpdate); + void _setupReactionStream() { + _reactionListener = ReactionListener( + event: widget.event, + onUpdate: _onReactionUpdate, + ); } void _onReactionUpdate(SyncUpdate update) { @@ -106,12 +101,6 @@ class _PangeaMessageReactionsState extends State { _reactionMap = newReactionMap; } - @override - void dispose() { - _reactionSubscription?.cancel(); - super.dispose(); - } - @override Widget build(BuildContext context) { final reactionList = _reactionMap.values.toList() diff --git a/lib/pages/chat/events/reaction_listener.dart b/lib/pages/chat/events/reaction_listener.dart new file mode 100644 index 000000000..f0f51c0ee --- /dev/null +++ b/lib/pages/chat/events/reaction_listener.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:matrix/matrix.dart'; + +class ReactionListener { + final Event event; + final Function(SyncUpdate) onUpdate; + + StreamSubscription? _reactionSub; + + ReactionListener({required this.event, required this.onUpdate}) { + _reactionSub = event.room.client.onSync.stream.where( + (update) { + final room = event.room; + final timelineEvents = update.rooms?.join?[room.id]?.timeline?.events; + if (timelineEvents == null) return false; + + final eventID = event.eventId; + return timelineEvents.any( + (e) => + e.type == EventTypes.Redaction || + (e.type == EventTypes.Reaction && + Event.fromMatrixEvent(e, room).relationshipEventId == + eventID), + ); + }, + ).listen(onUpdate); + } + + void dispose() { + _reactionSub?.cancel(); + _reactionSub = null; + } +} diff --git a/lib/pangea/toolbar/layout/message_selection_positioner.dart b/lib/pangea/toolbar/layout/message_selection_positioner.dart index 469176cef..a905a1fa1 100644 --- a/lib/pangea/toolbar/layout/message_selection_positioner.dart +++ b/lib/pangea/toolbar/layout/message_selection_positioner.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/events/reaction_listener.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; @@ -62,6 +63,9 @@ class MessageSelectionPositionerState extends State PangeaMessageEvent get pangeaMessageEvent => widget.overlayController.pangeaMessageEvent; + ReactionListener? _reactionListener; + final ValueNotifier reactionNotifier = ValueNotifier(null); + @override void initState() { super.initState(); @@ -76,10 +80,25 @@ class MessageSelectionPositionerState extends State }); }, ); + + reactionNotifier.value = _reactionsWidth; + _reactionListener = ReactionListener( + event: widget.event, + onUpdate: (update) { + if (mounted) { + final newWidth = _reactionsWidth; + if (newWidth != reactionNotifier.value) { + reactionNotifier.value = newWidth; + } + } + }, + ); } @override void dispose() { + reactionNotifier.dispose(); + _reactionListener?.dispose(); scrollController?.dispose(); super.dispose(); } @@ -127,7 +146,7 @@ class MessageSelectionPositionerState extends State return hasReactions ? 28.0 : 0.0; } - double? get reactionsWidth { + double? get _reactionsWidth { if (_reactionsRenderBox != null) { return _reactionsRenderBox!.size.width; } diff --git a/lib/pangea/toolbar/layout/over_message_overlay.dart b/lib/pangea/toolbar/layout/over_message_overlay.dart index 9dccb773d..fd378b002 100644 --- a/lib/pangea/toolbar/layout/over_message_overlay.dart +++ b/lib/pangea/toolbar/layout/over_message_overlay.dart @@ -67,7 +67,7 @@ class OverMessageOverlay extends StatelessWidget { readingAssistanceMode: controller.readingAssistanceMode, overlayKey: 'overlay_message_${controller.widget.event.eventId}', - reactionsWidth: controller.reactionsWidth, + reactionsWidth: controller.reactionNotifier, ); }, ), diff --git a/lib/pangea/toolbar/layout/overlay_center_content.dart b/lib/pangea/toolbar/layout/overlay_center_content.dart index 0a5f03df7..d2af9e89a 100644 --- a/lib/pangea/toolbar/layout/overlay_center_content.dart +++ b/lib/pangea/toolbar/layout/overlay_center_content.dart @@ -25,7 +25,7 @@ class OverlayCenterContent extends StatelessWidget { final double? messageWidth; final bool hasReactions; - final double? reactionsWidth; + final ValueNotifier reactionsWidth; final bool isTransitionAnimation; final ReadingAssistanceMode? readingAssistanceMode; @@ -42,7 +42,7 @@ class OverlayCenterContent extends StatelessWidget { required this.nextEvent, required this.prevEvent, required this.hasReactions, - this.reactionsWidth, + required this.reactionsWidth, this.onChangeMessageSize, this.sizeAnimation, this.isTransitionAnimation = false, @@ -96,11 +96,14 @@ class OverlayCenterContent extends StatelessWidget { top: 4.0, left: ownMessage ? 0.0 : 4.0, ), - child: PangeaMessageReactions( - event, - chatController.timeline!, - chatController, - width: reactionsWidth, + child: ValueListenableBuilder( + valueListenable: reactionsWidth, + builder: (context, width, __) => PangeaMessageReactions( + event, + chatController.timeline!, + chatController, + width: width != null && width > 0 ? width : null, + ), ), ), ], diff --git a/lib/pangea/toolbar/layout/practice_mode_transition_animation.dart b/lib/pangea/toolbar/layout/practice_mode_transition_animation.dart index 72dc5321e..7c8a06f5d 100644 --- a/lib/pangea/toolbar/layout/practice_mode_transition_animation.dart +++ b/lib/pangea/toolbar/layout/practice_mode_transition_animation.dart @@ -145,6 +145,7 @@ class PracticeModeTransitionAnimationState widget.controller.readingAssistanceMode, overlayKey: "overlay_transition_message_${widget.controller.widget.event.eventId}", + reactionsWidth: widget.controller.reactionNotifier, ), ); }, @@ -195,6 +196,7 @@ class CenteredMessage extends StatelessWidget { overlayKey: "overlay_center_message_${controller.widget.event.eventId}", readingAssistanceMode: controller.readingAssistanceMode, + reactionsWidth: controller.reactionNotifier, ), const SizedBox( height: AppConfig.readingAssistanceInputBarHeight + 60.0, diff --git a/lib/pangea/toolbar/word_card/word_card_switcher.dart b/lib/pangea/toolbar/word_card/word_card_switcher.dart index 9f481e0cc..014273478 100644 --- a/lib/pangea/toolbar/word_card/word_card_switcher.dart +++ b/lib/pangea/toolbar/word_card/word_card_switcher.dart @@ -24,8 +24,11 @@ class WordCardSwitcher extends StatelessWidget { overlayController: controller.widget.overlayController, ) : mode != SelectMode.emoji - ? MessageReactionPicker( - chatController: controller.widget.chatController, + ? ValueListenableBuilder( + valueListenable: controller.reactionNotifier, + builder: (context, _, __) => MessageReactionPicker( + chatController: controller.widget.chatController, + ), ) : const SizedBox.shrink(), );