diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 7dc2bb4625..adde7fd8b9 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -216,7 +216,7 @@ class ChatController extends State final Set unfolded = {}; - Event? replyEvent; + final replyEventNotifier = ValueNotifier(null); Event? editEvent; @@ -504,7 +504,7 @@ class ChatController extends State // ignore: unawaited_futures room!.sendTextEvent( sendController.text.trim(), - inReplyTo: replyEvent, + inReplyTo: replyEventNotifier.value, editEventId: editEvent?.eventId, parseCommands: parseCommands, ); @@ -514,8 +514,8 @@ class ChatController extends State ); setReadMarker(); inputText.value = pendingText; + _updateReplyEvent(); setState(() { - replyEvent = null; editEvent = null; pendingText = ''; }); @@ -614,9 +614,7 @@ class ChatController extends State // ); // return null; // }); - setState(() { - replyEvent = null; - }); + _updateReplyEvent(); } void onEmojiAction() { @@ -625,7 +623,7 @@ class ChatController extends State if (PlatformInfos.isMobile) { hideKeyboardChatScreen(); } else { - inputFocus.requestFocus(); + _requestInputFocus(); } } @@ -646,9 +644,7 @@ class ChatController extends State await event.copyTextEvent(context, timeline!); showEmojiPickerNotifier.value = false; - setState(() { - selectedEvents.clear(); - }); + _clearSelectEvent(); } void reportEventAction() async { @@ -694,9 +690,7 @@ class ChatController extends State ); if (result.error != null) return; showEmojiPickerNotifier.value = false; - setState(() { - selectedEvents.clear(); - }); + _clearSelectEvent(); TwakeSnackBar.show(context, L10n.of(context)!.contentHasBeenReported); } @@ -734,9 +728,7 @@ class ChatController extends State ); } showEmojiPickerNotifier.value = false; - setState(() { - selectedEvents.clear(); - }); + _clearSelectEvent(); } List get currentRoomBundle { @@ -780,7 +772,7 @@ class ChatController extends State "forwardEventsAction():: shareContentList: ${Matrix.of(context).shareContentList}", ); } - setState(() => selectedEvents.clear()); + _clearSelectEvent(); context.go( '/rooms/forward', extra: ForwardArgument( @@ -800,15 +792,17 @@ class ChatController extends State for (final e in allEditEvents) { e.sendAgain(); } - setState(() => selectedEvents.clear()); + _clearSelectEvent(); } - void replyAction({Event? replyTo}) { - setState(() { - replyEvent = replyTo ?? selectedEvents.first; - selectedEvents.clear(); - }); - inputFocus.requestFocus(); + void replyAction({ + Event? replyTo, + }) { + _updateReplyEvent( + event: replyTo ?? selectedEvents.first, + ); + _clearSelectEvent(); + _requestInputFocus(); } Future scrollToEventIdAndHighlight(String eventId) async { @@ -997,7 +991,7 @@ class ChatController extends State ); if (PlatformInfos.isWeb) { - inputFocus.requestFocus(); + _requestInputFocus(); } break; } @@ -1010,8 +1004,8 @@ class ChatController extends State } void sendEmojiAction(String? emoji) async { - final events = List.from(selectedEvents); - setState(() => selectedEvents.clear()); + final events = selectedEvents; + _clearSelectEvent(); for (final event in events) { await room!.sendReaction( event.eventId, @@ -1023,9 +1017,7 @@ class ChatController extends State void clearSelectedEvents() { showEmojiPickerNotifier.value = false; - setState(() { - selectedEvents.clear(); - }); + _clearSelectEvent(); } void clearSingleSelectedEvent() { @@ -1046,15 +1038,16 @@ class ChatController extends State setState(() { pendingText = sendController.text; editEvent = selectedEvents.first; - inputText.value = sendController.text = - editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback( - MatrixLocals(L10n.of(context)!), - withSenderNamePrefix: false, - hideReply: true, - ); - selectedEvents.clear(); }); - inputFocus.requestFocus(); + inputText.value = sendController.text = + editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + withSenderNamePrefix: false, + hideReply: true, + ); + _clearSelectEvent(); + + _requestInputFocus(); } void goToNewRoomAction() async { @@ -1091,13 +1084,9 @@ class ChatController extends State void onSelectMessage(Event event) { if (!event.redacted) { if (selectedEvents.contains(event)) { - setState( - () => selectedEvents.remove(event), - ); + _removeSelectEvent(event); } else { - setState( - () => selectedEvents.add(event), - ); + _addSelectEvent([event]); } selectedEvents.sort( (a, b) => a.originServerTs.compareTo(b.originServerTs), @@ -1275,7 +1264,7 @@ class ChatController extends State inputText.value = sendController.text = pendingText; pendingText = ''; } - replyEvent = null; + _updateReplyEvent(); editEvent = null; }); @@ -1472,7 +1461,7 @@ class ChatController extends State void onKeyboardAction() { showEmojiPickerNotifier.toggle(); - inputFocus.requestFocus(); + _requestInputFocus(); } void onPushDetails() async { @@ -1680,6 +1669,42 @@ class ChatController extends State } } + void _requestInputFocus() { + if (!inputFocus.hasPrimaryFocus) { + inputFocus.requestFocus(); + } + } + + void _updateReplyEvent({Event? event}) { + try { + replyEventNotifier.value = event; + } on FlutterError catch (e) { + Logs().e( + 'Chat::_updateReplyEvent():: FlutterError: $e', + e.stackTrace, + ); + } + } + + void _addSelectEvent(List events) { + setState(() { + selectedEvents.addAll(events); + }); + } + + void _removeSelectEvent(Event events) { + setState(() { + selectedEvents.remove(events); + }); + } + + void _clearSelectEvent() { + if (selectedEvents.isEmpty) return; + setState(() { + selectedEvents.clear(); + }); + } + @override void initState() { _initializePinnedEvents(); @@ -1733,6 +1758,7 @@ class ChatController extends State suggestionsController.dispose(); stickyTimestampNotifier.dispose(); openingChatViewStateNotifier.dispose(); + replyEventNotifier.dispose(); super.dispose(); } diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index a787441bcf..9b99e955a6 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -193,7 +193,7 @@ class ChatView extends StatelessWidget with MessageContentMixin { builder: (context, showScrollDownButton, _) { if (showScrollDownButton && controller.selectedEvents.isEmpty && - controller.replyEvent == null) { + controller.replyEventNotifier.value == null) { return Padding( padding: const EdgeInsets.only(bottom: 56.0), child: FloatingActionButton( @@ -224,7 +224,12 @@ class ChatView extends StatelessWidget with MessageContentMixin { return ChatInvitationBody(controller); } - return ChatViewBody(controller); + return ValueListenableBuilder( + valueListenable: controller.replyEventNotifier, + builder: (context, _, __) { + return ChatViewBody(controller); + }, + ); } Widget _buildBackButton(BuildContext context) => Padding( diff --git a/lib/pages/chat/events/reply_content.dart b/lib/pages/chat/events/reply_content.dart index 3c45ee431a..2c859bee9f 100644 --- a/lib/pages/chat/events/reply_content.dart +++ b/lib/pages/chat/events/reply_content.dart @@ -131,7 +131,7 @@ class ReplyContent extends StatelessWidget { ); }, ), - replyBody, + Expanded(child: replyBody), ], ), ), diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index a406814f3a..6892a63d5f 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -17,7 +17,7 @@ class ReactionsPicker extends StatelessWidget { Widget build(BuildContext context) { if (controller.showEmojiPickerNotifier.value) return Container(); final display = controller.editEvent == null && - controller.replyEvent == null && + controller.replyEventNotifier.value == null && controller.room!.canSendDefaultMessages && controller.selectedEvents.isNotEmpty; return AnimatedContainer( diff --git a/lib/pages/chat/reply_display.dart b/lib/pages/chat/reply_display.dart index 19bfd5f14d..17f585e5d8 100644 --- a/lib/pages/chat/reply_display.dart +++ b/lib/pages/chat/reply_display.dart @@ -15,13 +15,15 @@ class ReplyDisplay extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: controller.editEvent != null || controller.replyEvent != null + padding: controller.editEvent != null || + controller.replyEventNotifier.value != null ? ReplyDisplayStyle.replyDisplayPadding : EdgeInsets.zero, child: AnimatedContainer( duration: TwakeThemes.animationDuration, curve: TwakeThemes.animationCurve, - height: controller.editEvent != null || controller.replyEvent != null + height: controller.editEvent != null || + controller.replyEventNotifier.value != null ? ReplyDisplayStyle.replyContainerHeight : 0, clipBehavior: Clip.hardEdge, @@ -30,9 +32,9 @@ class ReplyDisplay extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: controller.replyEvent != null + child: controller.replyEventNotifier.value != null ? ReplyContent( - controller.replyEvent!, + controller.replyEventNotifier.value!, timeline: controller.timeline!, ) : _EditContent(