From ec28d00e81f0f0d411cf4bedbff4b850404a0625 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 15 Dec 2021 10:51:28 +0100 Subject: [PATCH] fix message input quoted message --- .../lib/src/core/models/message.dart | 45 ++++++++++++++----- .../lib/src/message_input/message_input.dart | 44 +++++++++--------- .../stream_message_text_field.dart | 2 +- .../lib/src/message_input_controller.dart | 21 ++++++++- .../test/message_input_controller_test.dart | 24 ++-------- 5 files changed, 81 insertions(+), 55 deletions(-) diff --git a/packages/stream_chat/lib/src/core/models/message.dart b/packages/stream_chat/lib/src/core/models/message.dart index f1408d45..a7d21c03 100644 --- a/packages/stream_chat/lib/src/core/models/message.dart +++ b/packages/stream_chat/lib/src/core/models/message.dart @@ -8,11 +8,11 @@ import 'package:uuid/uuid.dart'; part 'message.g.dart'; -class _PinExpires { - const _PinExpires(); +class _NullConst { + const _NullConst(); } -const _pinExpires = _PinExpires(); +const _nullConst = _NullConst(); /// Enum defining the status of a sending message. enum MessageSendingStatus { @@ -273,8 +273,8 @@ class Message extends Equatable { List? latestReactions, List? ownReactions, String? parentId, - Message? quotedMessage, - String? quotedMessageId, + Object? quotedMessage = _nullConst, + Object? quotedMessageId = _nullConst, int? replyCount, List? threadParticipants, bool? showInChannel, @@ -285,7 +285,7 @@ class Message extends Equatable { User? user, bool? pinned, DateTime? pinnedAt, - Object? pinExpires = _pinExpires, + Object? pinExpires = _nullConst, User? pinnedBy, Map? extraData, MessageSendingStatus? status, @@ -294,11 +294,32 @@ class Message extends Equatable { assert(() { if (pinExpires is! DateTime && pinExpires != null && - pinExpires is! _PinExpires) { + pinExpires is! _NullConst) { throw ArgumentError('`pinExpires` can only be set as DateTime or null'); } return true; }(), 'Validate type for pinExpires'); + + assert(() { + if (quotedMessage is! Message && + quotedMessage != null && + quotedMessage is! _NullConst) { + throw ArgumentError( + '`quotedMessage` can only be set as Message or null'); + } + return true; + }(), 'Validate type for quotedMessage'); + + assert(() { + if (quotedMessageId is! String && + quotedMessageId != null && + quotedMessageId is! _NullConst) { + throw ArgumentError( + '`quotedMessage` can only be set as String or null'); + } + return true; + }(), 'Validate type for quotedMessage'); + return Message( id: id ?? this.id, text: text ?? this.text, @@ -312,8 +333,12 @@ class Message extends Equatable { latestReactions: latestReactions ?? this.latestReactions, ownReactions: ownReactions ?? this.ownReactions, parentId: parentId ?? this.parentId, - quotedMessage: quotedMessage ?? this.quotedMessage, - quotedMessageId: quotedMessageId ?? _quotedMessageId, + quotedMessage: quotedMessage == _nullConst + ? this.quotedMessage + : quotedMessage as Message?, + quotedMessageId: quotedMessageId == _nullConst + ? _quotedMessageId + : quotedMessageId as String?, replyCount: replyCount ?? this.replyCount, threadParticipants: threadParticipants ?? this.threadParticipants, showInChannel: showInChannel ?? this.showInChannel, @@ -325,7 +350,7 @@ class Message extends Equatable { pinned: pinned ?? this.pinned, pinnedAt: pinnedAt ?? this.pinnedAt, pinExpires: - pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?, + pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?, pinnedBy: pinnedBy ?? this.pinnedBy, extraData: extraData ?? this.extraData, status: status ?? this.status, diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index 9787b39e..2ba5c038 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -190,8 +190,6 @@ class MessageInput extends StatefulWidget { this.actionsLocation = ActionsLocation.left, this.attachmentThumbnailBuilders, this.focusNode, - this.quotedMessage, - this.onQuotedMessageCleared, this.sendButtonLocation = SendButtonLocation.outside, this.autofocus = false, this.hideSendAsDm = false, @@ -270,12 +268,6 @@ class MessageInput extends StatefulWidget { /// The focus node associated to the TextField. final FocusNode? focusNode; - /// The message that is being quoted. - final Message? quotedMessage; - - /// Callback invoked when the quoted message is cleared. - final VoidCallback? onQuotedMessageCleared; - /// The location of the send button final SendButtonLocation sendButtonLocation; @@ -385,12 +377,19 @@ class MessageInputState extends State void _createLocalController([Message? message]) { assert(_controller == null, ''); _controller = RestorableMessageInputController(message: message); - _registerController(); } void _registerController() { assert(_controller != null, ''); - registerForRestoration(_controller!, 'messageInputController'); + + registerForRestoration( + _controller!, + widget.restorationId ?? 'messageInputController', + ); + _effectiveController.textEditingController + .removeListener(_onChangedDebounced); + _effectiveController.textEditingController.addListener(_onChangedDebounced); + if (!_isEditing && _timeOut <= 0) _startSlowMode(); } @override @@ -398,8 +397,13 @@ class MessageInputState extends State super.initState(); if (widget.messageInputController == null) { _createLocalController(); + } else { + _effectiveController.textEditingController + .removeListener(_onChangedDebounced); + _effectiveController.textEditingController + .addListener(_onChangedDebounced); + if (!_isEditing && _timeOut <= 0) _startSlowMode(); } - _effectiveController.textEditingController.addListener(_onChangedDebounced); _focusNode.addListener(_focusNodeListener); } @@ -506,7 +510,10 @@ class MessageInputState extends State IconButton( visualDensity: VisualDensity.compact, icon: StreamSvgIcon.closeSmall(), - onPressed: widget.onQuotedMessageCleared, + onPressed: () { + _effectiveController.clearQuotedMessage(); + _focusNode.unfocus(); + }, ), ], ), @@ -646,9 +653,6 @@ class MessageInputState extends State return widget.sendButtonBuilder!(context, _effectiveController); } - print( - 'widget.validator(_effectiveController.message): ${widget.validator(_effectiveController.message)}'); - return StreamMessageSendButton( onSendMessage: sendMessage, timeOut: _timeOut, @@ -714,7 +718,6 @@ class MessageInputState extends State } Expanded _buildTextInput(BuildContext context) { - print('build text input'); final margin = (widget.sendButtonLocation == SendButtonLocation.inside ? const EdgeInsets.only(right: 8) : EdgeInsets.zero) + @@ -873,7 +876,6 @@ class MessageInputState extends State late final _onChangedDebounced = debounce( () { - print('onchangeddebounce'); var value = _effectiveController.text; if (!mounted) return; value = value.trim(); @@ -893,7 +895,6 @@ class MessageInputState extends State setState(() { _actionsShrunk = value.isNotEmpty && actionsLength > 1; }); - print('CHECK COMMANDS 00'); _checkCommands(value, context); _checkMentions(value, context); @@ -965,7 +966,6 @@ class MessageInputState extends State void _checkCommands(String s, BuildContext context) { if (s.startsWith('/')) { - print('CHECK COMMANDS'); final allCommands = StreamChannel.of(context).channel.config?.commands; final command = allCommands?.firstWhereOrNull((it) => it.name == s.substring(1)); @@ -1121,12 +1121,12 @@ class MessageInputState extends State Widget _buildReplyToMessage() { if (!_hasQuotedMessage) return const Offstage(); - final containsUrl = widget.quotedMessage!.attachments + final containsUrl = _effectiveController.value.quotedMessage!.attachments .any((element) => element.titleLink != null); return QuotedMessageWidget( reverse: true, showBorder: !containsUrl, - message: widget.quotedMessage!, + message: _effectiveController.value.quotedMessage!, messageTheme: _streamChatTheme.otherMessageTheme, padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), ); @@ -1577,7 +1577,6 @@ class MessageInputState extends State shouldKeepFocus ??= !_commandEnabled; _effectiveController.reset(); - widget.onQuotedMessageCleared?.call(); if (widget.preMessageSending != null) { message = await widget.preMessageSending!(message); @@ -1699,7 +1698,6 @@ class MessageInputState extends State void didChangeDependencies() { _streamChatTheme = StreamChatTheme.of(context); _messageInputTheme = MessageInputTheme.of(context); - if (!_isEditing && _timeOut <= 0) _startSlowMode(); super.didChangeDependencies(); } diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart index af2548a4..e7d765d9 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart @@ -697,7 +697,7 @@ class _StreamMessageTextFieldState extends State void _registerController() { assert(_controller != null, ''); - registerForRestoration(_controller!, 'controller'); + registerForRestoration(_controller!, restorationId ?? 'controller'); } @override diff --git a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart index 2b718939..29bd2db2 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart @@ -40,7 +40,10 @@ class MessageInputController extends ValueNotifier { MessageInputController._({ required Message initialMessage, }) : _textEditingController = - TextEditingController(text: initialMessage.text), + TextEditingController.fromValue(TextEditingValue( + text: initialMessage.text ?? '', + composing: TextRange.collapsed(initialMessage.text?.length ?? 0), + )), _initialMessage = initialMessage, super(initialMessage) { addListener(_textEditingSyncer); @@ -83,6 +86,22 @@ class MessageInputController extends ValueNotifier { value = message; } + /// Sets the message that's being quoted. + set quotedMessage(Message message) { + value = value.copyWith( + quotedMessage: message, + quotedMessageId: message.id, + ); + } + + /// Clears the quoted message. + void clearQuotedMessage() { + value = value.copyWith( + quotedMessageId: null, + quotedMessage: null, + ); + } + /// Sets a command for the message. set command(Command command) { value = value.copyWith( diff --git a/packages/stream_chat_flutter_core/test/message_input_controller_test.dart b/packages/stream_chat_flutter_core/test/message_input_controller_test.dart index 842a58bb..5e7934bd 100644 --- a/packages/stream_chat_flutter_core/test/message_input_controller_test.dart +++ b/packages/stream_chat_flutter_core/test/message_input_controller_test.dart @@ -3,28 +3,12 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; void main() { testWidgets( - 'should instantiate a new MessageInputController with default validator' - ' and empty message', + 'should instantiate a new MessageInputController with empty message', (tester) async { - final controller = MessageInputController(); + final controller = MessageInputController()..text = 'test'; - expect(controller.isValid, false); - controller.text = 'test'; - expect(controller.isValid, true); - }, - ); - - testWidgets( - 'should instantiate a new MessageInputController with default validator' - ' and specified message', - (tester) async { - final message = Message(text: 'test'); - final controller = MessageInputController( - message: message, - ); - - expect(controller.message, message); - expect(controller.isValid, true); + expect(controller.text, 'test'); + expect(controller.message.text, 'test'); }, ); }