From 67f9700b4a3149eab56cfaaec4e4bb0eac8b1949 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 13 Dec 2021 17:17:23 +0100 Subject: [PATCH] restoration wip --- .../lib/src/message_input/message_input.dart | 204 +++++++++++------- .../stream_attachment_picker.dart | 16 +- .../stream_message_send_button.dart | 33 ++- .../stream_message_text_field.dart | 26 +-- .../lib/src/channels_bloc.dart | 1 - .../lib/src/message_input_controller.dart | 27 +-- .../lib/src/message_list_core.dart | 1 - .../test/src/mapper/message_mapper_test.dart | 1 - .../mapper/pinned_message_mapper_test.dart | 1 - 9 files changed, 171 insertions(+), 139 deletions(-) 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 b7eb4cf2..caceba36 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 @@ -23,6 +23,9 @@ import 'package:video_compress/video_compress.dart'; export 'package:video_compress/video_compress.dart' show VideoQuality; +/// A function that returns true if the message is valid and can be sent. +typedef MessageValidator = bool Function(Message message); + /// A callback that can be passed to [MessageInput.onError]. /// /// This callback should not throw. @@ -211,6 +214,8 @@ class MessageInput extends StatefulWidget { this.attachmentsPickerBuilder, this.sendButtonBuilder, this.shouldKeepFocusAfterMessage, + this.validator = _defaultValidator, + this.restorationId, }) : super(key: key); /// List of options for showing overlays. @@ -265,8 +270,10 @@ 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 @@ -325,6 +332,15 @@ class MessageInput extends StatefulWidget { /// The default behaviour keeps focus until a command is enabled. final bool? shouldKeepFocusAfterMessage; + /// A callback function that validates the message. + final MessageValidator validator; + + /// Restoration ID to save and restore the state of the MessageInput. + final String? restorationId; + + static bool _defaultValidator(Message message) => + message.text?.isNotEmpty == true || message.attachments.isNotEmpty; + @override MessageInputState createState() => MessageInputState(); @@ -341,40 +357,77 @@ class MessageInput extends StatefulWidget { } /// State of [MessageInput] -class MessageInputState extends State { +class MessageInputState extends State + with RestorationMixin { final _imagePicker = ImagePicker(); late final _focusNode = widget.focusNode ?? FocusNode(); bool _inputEnabled = true; - bool get _commandEnabled => messageInputController.value.command != null; + bool get _commandEnabled => _effectiveController.value.command != null; bool _showCommandsOverlay = false; bool _showMentionsOverlay = false; bool _actionsShrunk = false; bool _openFilePickerSection = false; - /// The editing controller passed to the input TextField - late final MessageInputController messageInputController = - widget.messageInputController ?? MessageInputController(); - late StreamChatThemeData _streamChatTheme; late MessageInputThemeData _messageInputTheme; bool get _hasQuotedMessage => - messageInputController.value.quotedMessage != null; - - bool get _messageIsPresent => messageInputController.text.trim().isNotEmpty; + _effectiveController.value.quotedMessage != null; bool get _isEditing => - messageInputController.value.status != MessageSendingStatus.sending; + _effectiveController.value.status != MessageSendingStatus.sending; + + RestorableMessageInputController? _controller; + MessageInputController get _effectiveController => + widget.messageInputController ?? _controller!.value; + + void _createLocalController([Message? message]) { + assert(_controller == null, ''); + _controller = RestorableMessageInputController(message: message); + print('_controller?.value: ${_controller?.value}'); + } + + void _registerController() { + assert(_controller != null, ''); + registerForRestoration(_controller!, 'messageInputController'); + } @override void initState() { super.initState(); - messageInputController.textEditingController - .addListener(_onChangedDebounced); + if (widget.messageInputController == null) { + _createLocalController(); + print('_controller?.value: ${_controller?.value}'); + } + _effectiveController.textEditingController.addListener(_onChangedDebounced); _focusNode.addListener(_focusNodeListener); } + @override + void didUpdateWidget(covariant MessageInput oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.messageInputController == null && + oldWidget.messageInputController != null) { + _createLocalController(oldWidget.messageInputController!.value); + } else if (widget.messageInputController != null && + oldWidget.messageInputController == null) { + unregisterFromRestoration(_controller!); + _controller!.dispose(); + _controller = null; + } + } + + @override + void restoreState(RestorationBucket? oldBucket, bool initialRestore) { + if (_controller != null) { + _registerController(); + } + } + + @override + String? get restorationId => widget.restorationId; + void _focusNodeListener() { if (_focusNode.hasFocus) { _openFilePickerSection = false; @@ -414,7 +467,7 @@ class MessageInputState extends State { @override Widget build(BuildContext context) { Widget child = MessageValueListenableBuilder( - valueListenable: messageInputController, + valueListenable: _effectiveController, builder: (context, value, _) => DecoratedBox( decoration: BoxDecoration( color: _messageInputTheme.inputBackgroundColor, @@ -462,7 +515,7 @@ class MessageInputState extends State { padding: const EdgeInsets.symmetric(vertical: 8), child: _buildTextField(context), ), - if (messageInputController.value.parentId != null && + if (_effectiveController.value.parentId != null && !widget.hideSendAsDm) Padding( padding: const EdgeInsets.only( @@ -496,12 +549,12 @@ class MessageInputState extends State { ), OverlayOptions( visible: _focusNode.hasFocus && - messageInputController.text.isNotEmpty && - messageInputController.baseOffset > 0 && - messageInputController.text + _effectiveController.text.isNotEmpty && + _effectiveController.baseOffset > 0 && + _effectiveController.text .substring( 0, - messageInputController.baseOffset, + _effectiveController.baseOffset, ) .contains(':'), widget: _buildEmojiOverlay(), @@ -537,7 +590,7 @@ class MessageInputState extends State { height: 16, width: 16, foregroundDecoration: BoxDecoration( - border: messageInputController.showInChannel + border: _effectiveController.showInChannel ? null : Border.all( color: _streamChatTheme.colorTheme.textHighEmphasis @@ -549,18 +602,18 @@ class MessageInputState extends State { child: Center( child: Material( borderRadius: BorderRadius.circular(3), - color: messageInputController.showInChannel + color: _effectiveController.showInChannel ? _streamChatTheme.colorTheme.accentPrimary : _streamChatTheme.colorTheme.barsBg, child: InkWell( onTap: () { - messageInputController.showInChannel = - !messageInputController.showInChannel; + _effectiveController.showInChannel = + !_effectiveController.showInChannel; }, child: AnimatedCrossFade( duration: const Duration(milliseconds: 300), reverseDuration: const Duration(milliseconds: 300), - crossFadeState: messageInputController.showInChannel + crossFadeState: _effectiveController.showInChannel ? CrossFadeState.showFirst : CrossFadeState.showSecond, firstChild: StreamSvgIcon.check( @@ -591,13 +644,13 @@ class MessageInputState extends State { Widget _buildSendButton(BuildContext context) { if (widget.sendButtonBuilder != null) { - return widget.sendButtonBuilder!(context, messageInputController); + return widget.sendButtonBuilder!(context, _effectiveController); } return StreamMessageSendButton( onSendMessage: sendMessage, timeOut: _timeOut, - isIdle: !_messageIsPresent && messageInputController.attachments.isEmpty, + isIdle: !widget.validator(_effectiveController.message), isEditEnabled: _isEditing, idleSendButton: widget.idleSendButton, activeSendButton: widget.activeSendButton, @@ -696,7 +749,7 @@ class MessageInputState extends State { maxLines: null, onSubmitted: (_) => sendMessage(), keyboardType: widget.keyboardType, - controller: messageInputController, + controller: _effectiveController, focusNode: _focusNode, style: _messageInputTheme.inputTextStyle, autofocus: widget.autofocus, @@ -768,7 +821,7 @@ class MessageInputState extends State { size: 16, ), Text( - messageInputController.value.command!.toUpperCase(), + _effectiveController.value.command!.toUpperCase(), style: _streamChatTheme.textTheme.footnoteBold.copyWith( color: Colors.white, @@ -802,7 +855,7 @@ class MessageInputState extends State { height: 24, width: 24, ), - onPressed: messageInputController.clear, + onPressed: _effectiveController.clear, ), ), if (!_commandEnabled && @@ -817,14 +870,14 @@ class MessageInputState extends State { late final _onChangedDebounced = debounce( () { - var value = messageInputController.text; + var value = _effectiveController.text; if (!mounted) return; value = value.trim(); final channel = StreamChannel.of(context).channel; if (value.isNotEmpty) { channel - .keyStroke(messageInputController.value.parentId) + .keyStroke(_effectiveController.value.parentId) // ignore: no-empty-block .catchError((e) {}); } @@ -846,10 +899,10 @@ class MessageInputState extends State { ); String _getHint(BuildContext context) { - if (_commandEnabled && messageInputController.value.command == 'giphy') { + if (_commandEnabled && _effectiveController.value.command == 'giphy') { return context.translations.searchGifLabel; } - if (messageInputController.attachments.isNotEmpty) { + if (_effectiveController.attachments.isNotEmpty) { return context.translations.addACommentOrSendLabel; } if (_timeOut != 0) { @@ -861,16 +914,16 @@ class MessageInputState extends State { void _checkEmoji(String s, BuildContext context) { if (s.isNotEmpty && - messageInputController.baseOffset > 0 && - messageInputController.text + _effectiveController.baseOffset > 0 && + _effectiveController.text .substring( 0, - messageInputController.baseOffset, + _effectiveController.baseOffset, ) .contains(':')) { - final textToSelection = messageInputController.text.substring( + final textToSelection = _effectiveController.text.substring( 0, - messageInputController.selectionStart, + _effectiveController.selectionStart, ); final splits = textToSelection.split(':'); final query = splits[splits.length - 2].toLowerCase(); @@ -884,11 +937,11 @@ class MessageInputState extends State { void _checkMentions(String s, BuildContext context) { if (s.isNotEmpty && - messageInputController.baseOffset > 0 && - messageInputController.text + _effectiveController.baseOffset > 0 && + _effectiveController.text .substring( 0, - messageInputController.baseOffset, + _effectiveController.baseOffset, ) .split(' ') .last @@ -925,7 +978,7 @@ class MessageInputState extends State { } Widget _buildCommandsOverlayEntry() { - final text = messageInputController.text.trimLeft(); + final text = _effectiveController.text.trimLeft(); final renderObject = context.findRenderObject() as RenderBox?; if (renderObject == null) { @@ -941,7 +994,7 @@ class MessageInputState extends State { Widget _buildFilePickerSection() { final picker = StreamAttachmentPicker( - messageInputController: messageInputController, + messageInputController: _effectiveController, onFilePicked: pickFile, isOpen: _openFilePickerSection, pickerSize: _openFilePickerSection ? _kMinMediaPickerSize : 0, @@ -950,18 +1003,13 @@ class MessageInputState extends State { maxAttachmentSize: widget.maxAttachmentSize, compressedVideoQuality: widget.compressedVideoQuality, compressedVideoFrameRate: widget.compressedVideoFrameRate, - onChangeInputState: (val) { - setState(() { - _inputEnabled = val; - }); - }, onError: _showErrorAlert, ); if (_openFilePickerSection && widget.attachmentsPickerBuilder != null) { return widget.attachmentsPickerBuilder!( context, - messageInputController, + _effectiveController, picker, ); } @@ -971,14 +1019,14 @@ class MessageInputState extends State { Widget _buildMentionsOverlayEntry() { final channel = StreamChannel.of(context).channel; - if (messageInputController.selectionStart < 0 || channel.state == null) { + if (_effectiveController.selectionStart < 0 || channel.state == null) { return const Offstage(); } - final splits = messageInputController.text + final splits = _effectiveController.text .substring( 0, - messageInputController.selectionStart, + _effectiveController.selectionStart, ) .split('@'); final query = splits.last.toLowerCase(); @@ -1007,13 +1055,13 @@ class MessageInputState extends State { size: Size(renderObject.size.width - 16, 400), mentionsTileBuilder: tileBuilder, onMentionUserTap: (user) { - messageInputController.addMentionedUser(user); + _effectiveController.addMentionedUser(user); splits[splits.length - 1] = user.name; final rejoin = splits.join('@'); - messageInputController.text = rejoin + - messageInputController.text.substring( - messageInputController.selectionStart, + _effectiveController.text = rejoin + + _effectiveController.text.substring( + _effectiveController.selectionStart, ); _onChangedDebounced.cancel(); @@ -1023,14 +1071,14 @@ class MessageInputState extends State { } Widget _buildEmojiOverlay() { - if (messageInputController.baseOffset < 0) { + if (_effectiveController.baseOffset < 0) { return const Offstage(); } - final splits = messageInputController.text + final splits = _effectiveController.text .substring( 0, - messageInputController.baseOffset, + _effectiveController.baseOffset, ) .split(':'); @@ -1050,14 +1098,14 @@ class MessageInputState extends State { void _chooseEmoji(List splits, Emoji emoji) { final rejoin = splits.sublist(0, splits.length - 1).join(':') + emoji.char!; - messageInputController.text = rejoin + - messageInputController.text.substring( - messageInputController.selectionStart, + _effectiveController.text = rejoin + + _effectiveController.text.substring( + _effectiveController.selectionStart, ); } void _setCommand(Command c) { - messageInputController + _effectiveController ..clear() ..command = c; setState(() { @@ -1079,11 +1127,11 @@ class MessageInputState extends State { } Widget _buildAttachments() { - if (messageInputController.attachments.isEmpty) return const Offstage(); - final fileAttachments = messageInputController.attachments + if (_effectiveController.attachments.isEmpty) return const Offstage(); + final fileAttachments = _effectiveController.attachments .where((it) => it.type == 'file') .toList(growable: false); - final remainingAttachments = messageInputController.attachments + final remainingAttachments = _effectiveController.attachments .where((it) => it.type != 'file') .toList(growable: false); return Column( @@ -1168,9 +1216,8 @@ class MessageInputState extends State { focusElevation: 0, hoverElevation: 0, onPressed: () { - messageInputController.value = - messageInputController.value.copyWith( - attachments: messageInputController.attachments + _effectiveController.value = _effectiveController.value.copyWith( + attachments: _effectiveController.attachments .where((it) => it.id != attachment.id) .toList(), ); @@ -1252,7 +1299,7 @@ class MessageInputState extends State { } Widget _buildCommandButton(BuildContext context) { - final s = messageInputController.text.trim(); + final s = _effectiveController.text.trim(); final defaultButton = IconButton( icon: StreamSvgIcon.lightning( color: s.isNotEmpty @@ -1386,8 +1433,7 @@ class MessageInputState extends State { /// Adds an attachment to the [messageInputController.attachments] map void _addAttachments(Iterable attachments) { final limit = widget.attachmentLimit; - final length = - messageInputController.attachments.length + attachments.length; + final length = _effectiveController.attachments.length + attachments.length; if (length > limit) { final onAttachmentLimitExceed = widget.onAttachmentLimitExceed; if (onAttachmentLimitExceed != null) { @@ -1401,7 +1447,7 @@ class MessageInputState extends State { ); } for (final attachment in attachments) { - messageInputController.addAttachment(attachment); + _effectiveController.addAttachment(attachment); } } @@ -1518,17 +1564,13 @@ class MessageInputState extends State { /// Sends the current message Future sendMessage() async { - var message = messageInputController.value; - - if (!messageInputController.isValid) { - return; - } + var message = _effectiveController.value; var shouldKeepFocus = widget.shouldKeepFocusAfterMessage; shouldKeepFocus ??= !_commandEnabled; - messageInputController.reset(); + _effectiveController.reset(); widget.onQuotedMessageCleared?.call(); if (widget.preMessageSending != null) { @@ -1557,7 +1599,7 @@ class MessageInputState extends State { final resp = await sendingFuture; if (resp.message?.type == 'error') { - messageInputController.value = message; + _effectiveController.value = message; } _startSlowMode(); widget.onMessageSent?.call(resp.message); @@ -1638,9 +1680,9 @@ class MessageInputState extends State { @override void dispose() { - messageInputController.textEditingController + _effectiveController.textEditingController .removeListener(_onChangedDebounced); - messageInputController.dispose(); + _controller?.dispose(); _focusNode.removeListener(_focusNodeListener); _stopSlowMode(); _onChangedDebounced.cancel(); diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_attachment_picker.dart index 88fbab74..7f6a6f6e 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_attachment_picker.dart @@ -21,13 +21,23 @@ typedef CustomAttachmentIconBuilder = Widget Function( bool active, ); +/// A widget that allows to pick an attachment. class StreamAttachmentPicker extends StatefulWidget { + /// True if the picker is open. final bool isOpen; + + /// The picker size in height. final double pickerSize; + + /// The [MessageInputController] linked to this picker. final MessageInputController messageInputController; + + /// The limit of attachments that can be picked. final int attachmentLimit; + + /// The callback for when the attachment limit is exceeded. final AttachmentLimitExceedListener? onAttachmentLimitExceeded; - final ValueChanged? onChangeInputState; + final ValueChanged? onError; final FilePickerCallback onFilePicked; @@ -42,8 +52,10 @@ class StreamAttachmentPicker extends StatefulWidget { /// - Do not set it if you're using our default CDN final int maxAttachmentSize; + /// The list of attachment types that can be picked. final List allowedAttachmentTypes; + /// The list of custom attachment types that can be picked. final List customAttachmentTypes; const StreamAttachmentPicker({ @@ -57,7 +69,6 @@ class StreamAttachmentPicker extends StatefulWidget { this.maxAttachmentSize = 20971520, this.compressedVideoQuality = VideoQuality.DefaultQuality, this.compressedVideoFrameRate = 30, - this.onChangeInputState, this.onError, this.allowedAttachmentTypes = const [ DefaultAttachmentTypes.image, @@ -98,7 +109,6 @@ class StreamAttachmentPicker extends StatefulWidget { compressedVideoQuality ?? this.compressedVideoQuality, compressedVideoFrameRate: compressedVideoFrameRate ?? this.compressedVideoFrameRate, - onChangeInputState: onChangeInputState ?? this.onChangeInputState, onError: onError ?? this.onError, allowedAttachmentTypes: allowedAttachmentTypes ?? this.allowedAttachmentTypes, diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart index ec03a58c..9916f6f7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_send_button.dart @@ -1,15 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// A widget that displays a sending button. class StreamMessageSendButton extends StatelessWidget { - final int timeOut; - final bool isIdle; - final bool isCommandEnabled; - final bool isEditEnabled; - final Widget? idleSendButton; - final Widget? activeSendButton; - final VoidCallback onSendMessage; - + /// Returns a [StreamMessageSendButton] with the given [timeOut], [isIdle], + /// [isCommandEnabled], [isEditEnabled], [idleSendButton], [activeSendButton], + /// [onSendMessage]. const StreamMessageSendButton({ Key? key, this.timeOut = 0, @@ -21,6 +17,27 @@ class StreamMessageSendButton extends StatelessWidget { required this.onSendMessage, }) : super(key: key); + /// Time out related to slow mode. + final int timeOut; + + /// If true the button will be disabled. + final bool isIdle; + + /// True if a command is being sent. + final bool isCommandEnabled; + + /// True if in editing mode. + final bool isEditEnabled; + + /// The widget to display when the button is disabled. + final Widget? idleSendButton; + + /// The widget to display when the button is enabled. + final Widget? activeSendButton; + + /// The callback to call when the button is pressed. + final VoidCallback onSendMessage; + @override Widget build(BuildContext context) { final _streamChatTheme = StreamChatTheme.of(context); 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 e5ffde94..af2548a4 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 @@ -18,6 +18,7 @@ export 'package:flutter/services.dart' SmartQuotesType, SmartDashesType; +/// A widget the wraps the [TextField] and adds some StreamChat specifics. class StreamMessageTextField extends StatefulWidget { /// Creates a Material Design text field. /// @@ -47,10 +48,6 @@ class StreamMessageTextField extends StatefulWidget { /// which is evaluated after the supplied [inputFormatters], if any. /// The [maxLength] value must be either null or greater than zero. /// - /// If [maxLengthEnforced] is set to false, then more than [maxLength] - /// characters may be entered, and the error counter and divider will - /// switch to the [decoration].errorStyle when the limit is exceeded. - /// /// The text cursor is not shown if [showCursor] is false or if [showCursor] /// is null (the default) and [readOnly] is true. /// @@ -60,7 +57,7 @@ class StreamMessageTextField extends StatefulWidget { /// must not be null. /// /// The [textAlign], [autofocus], [obscureText], [readOnly], [autocorrect], - /// [maxLengthEnforced], [scrollPadding], [maxLines], [maxLength], + /// [scrollPadding], [maxLines], [maxLength], /// [selectionHeightStyle], [selectionWidthStyle], [enableSuggestions], and /// [enableIMEPersonalizedLearning] arguments must not be null. /// @@ -151,9 +148,11 @@ class StreamMessageTextField extends StatefulWidget { ), assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'), - assert(maxLength == null || - maxLength == TextField.noMaxLength || - maxLength > 0), + assert( + maxLength == null || + maxLength == TextField.noMaxLength || + maxLength > 0, + '`maxLength` needs to be null or a positive integer'), // Assert the following instead of setting it directly to avoid // surprising the user by silently changing the value they set. @@ -343,12 +342,6 @@ class StreamMessageTextField extends StatefulWidget { /// Whitespace characters (e.g. newline, space, tab) are included in the /// character count. /// - /// If [maxLengthEnforced] is set to false or [maxLengthEnforcement] is - /// [MaxLengthEnforcement.none], then more than [maxLength] - /// characters may be entered, but the error counter and divider will switch - /// to the [decoration]'s [InputDecoration.errorStyle] when the limit is - /// exceeded. - /// /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} final int? maxLength; @@ -611,10 +604,6 @@ class StreamMessageTextField extends StatefulWidget { properties.add( DiagnosticsProperty('expands', expands, defaultValue: false)); properties.add(IntProperty('maxLength', maxLength, defaultValue: null)); - properties.add(FlagProperty('maxLengthEnforced', - value: maxLengthEnforced, - defaultValue: true, - ifFalse: 'maxLength not enforced')); properties.add(EnumProperty( 'maxLengthEnforcement', maxLengthEnforcement, defaultValue: null)); @@ -742,7 +731,6 @@ class _StreamMessageTextFieldState extends State minLines: widget.minLines, expands: widget.expands, maxLength: widget.maxLength, - maxLengthEnforced: widget.maxLengthEnforced, maxLengthEnforcement: widget.maxLengthEnforcement, onEditingComplete: widget.onEditingComplete, onSubmitted: widget.onSubmitted, diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index 0e8d48bc..6514d66b 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; 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 7aad03e6..2b718939 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 @@ -9,9 +9,6 @@ import 'package:stream_chat/stream_chat.dart'; /// Pass in a [MessageInputController] as the `valueListenable`. typedef MessageValueListenableBuilder = ValueListenableBuilder; -/// A function that returns true if the message is valid and can be sent. -typedef MessageValidator = bool Function(Message message); - /// Controller for storing and mutating a [Message] value. class MessageInputController extends ValueNotifier { /// Creates a controller for an editable text field. @@ -20,37 +17,28 @@ class MessageInputController extends ValueNotifier { /// message. factory MessageInputController({ Message? message, - MessageValidator? validator, }) => MessageInputController._( initialMessage: message ?? Message(), - validator: validator ?? _defaultValidator, ); /// Creates a controller for an editable text field from an initial [text]. - factory MessageInputController.fromText( - String? text, { - MessageValidator? validator, - }) => + factory MessageInputController.fromText(String? text) => MessageInputController._( initialMessage: Message(text: text), - validator: validator ?? _defaultValidator, ); /// Creates a controller for an editable text field from initial /// [attachments]. factory MessageInputController.fromAttachments( - List attachments, { - MessageValidator? validator, - }) => + List attachments, + ) => MessageInputController._( initialMessage: Message(attachments: attachments), - validator: validator ?? _defaultValidator, ); MessageInputController._({ required Message initialMessage, - this.validator = _defaultValidator, }) : _textEditingController = TextEditingController(text: initialMessage.text), _initialMessage = initialMessage, @@ -58,15 +46,6 @@ class MessageInputController extends ValueNotifier { addListener(_textEditingSyncer); } - /// A callback function that validates the message. - final MessageValidator validator; - - /// Checks if the message is valid. - bool get isValid => validator(value); - - static bool _defaultValidator(Message message) => - message.text?.isNotEmpty == true || message.attachments.isNotEmpty; - void _textEditingSyncer() { final cleanText = value.command == null ? value.text diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 74e40d7f..a28d6d73 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/better_stream_builder.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index d2ea8b93..8a06f02d 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -157,7 +157,6 @@ void main() { (prev, curr) => prev?..update(curr.type, (value) => value + 1, ifAbsent: () => 1), ), - status: MessageSendingStatus.sending, updatedAt: DateTime.now(), extraData: const {'extra_test_data': 'extraData'}, user: user, diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 9c2378fd..9c4d99a2 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -147,7 +147,6 @@ void main() { (prev, curr) => prev?..update(curr.type, (value) => value + 1, ifAbsent: () => 1), ), - status: MessageSendingStatus.sending, updatedAt: DateTime.now(), extraData: const {'extra_test_data': 'extraData'}, user: user,