From 23c01018ff78a83a219d414b0f1092cea3802539 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Mon, 7 Mar 2022 17:43:00 +0530 Subject: [PATCH 1/5] Added a flag to control autocorrect in messageinput and a field to provide a custom scroll to bottom builder --- .../lib/src/message_input.dart | 4 ++ .../lib/src/message_list_view.dart | 58 ++++++++++++------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index d1a45786..9af64b38 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -200,6 +200,7 @@ class MessageInput extends StatefulWidget { this.customOverlays = const [], this.mentionAllAppUsers = false, this.shouldKeepFocusAfterMessage, + this.autocorrectEnabled, }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", @@ -322,6 +323,8 @@ class MessageInput extends StatefulWidget { /// The default behaviour keeps focus until a command is enabled. final bool? shouldKeepFocusAfterMessage; + final bool? autocorrectEnabled; + @override MessageInputState createState() => MessageInputState(); @@ -707,6 +710,7 @@ class MessageInputState extends State { textAlignVertical: TextAlignVertical.center, decoration: _getInputDecoration(context), textCapitalization: TextCapitalization.sentences, + autocorrect: widget.autocorrectEnabled ?? true, ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index c6e269b9..edd76327 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -170,6 +170,7 @@ class MessageListView extends StatefulWidget { const MessageListView({ Key? key, this.showScrollToBottom = true, + this.scrollToBottomBuilder, this.messageBuilder, this.parentMessageBuilder, this.parentMessage, @@ -242,6 +243,11 @@ class MessageListView extends StatefulWidget { /// messages and the scroll offset is not zero final bool showScrollToBottom; + final Widget Function( + int unreadCount, + Future Function(int) scrollToBottomDefaultTapAction, + )? scrollToBottomBuilder; + /// Parent message in case of a thread final Message? parentMessage; @@ -846,6 +852,29 @@ class _MessageListViewState extends State { .index; } + Future scrollToBottomDefaultTapAction(int unreadCount) async { + if (unreadCount > 0) { + streamChannel!.channel.markRead(); + } + if (!_upToDate) { + _bottomPaginationActive = false; + initialAlignment = 0; + initialIndex = 0; + await streamChannel!.reloadChannel(); + + WidgetsBinding.instance?.addPostFrameCallback((_) { + _scrollController!.jumpTo(index: 0); + }); + } else { + _showScrollToBottom.value = false; + _scrollController!.scrollTo( + index: 0, + duration: const Duration(seconds: 1), + curve: Curves.easeInOut, + ); + } + } + Widget _buildScrollToBottom() => StreamBuilder( stream: streamChannel!.channel.state!.unreadCountStream, builder: (_, snapshot) { @@ -855,6 +884,12 @@ class _MessageListViewState extends State { return const Offstage(); } final unreadCount = snapshot.data!; + if (widget.scrollToBottomBuilder != null) { + return widget.scrollToBottomBuilder!( + unreadCount, + scrollToBottomDefaultTapAction, + ); + } final showUnreadCount = unreadCount > 0 && streamChannel!.channel.state!.members.any((e) => e.userId == @@ -869,28 +904,7 @@ class _MessageListViewState extends State { children: [ FloatingActionButton( backgroundColor: _streamTheme.colorTheme.barsBg, - onPressed: () async { - if (unreadCount > 0) { - streamChannel!.channel.markRead(); - } - if (!_upToDate) { - _bottomPaginationActive = false; - initialAlignment = 0; - initialIndex = 0; - await streamChannel!.reloadChannel(); - - WidgetsBinding.instance?.addPostFrameCallback((_) { - _scrollController!.jumpTo(index: 0); - }); - } else { - _showScrollToBottom.value = false; - _scrollController!.scrollTo( - index: 0, - duration: const Duration(seconds: 1), - curve: Curves.easeInOut, - ); - } - }, + onPressed: () => scrollToBottomDefaultTapAction(unreadCount), child: widget.reverse ? StreamSvgIcon.down( color: _streamTheme.colorTheme.textHighEmphasis, From c2f5b2b03836b70f4346f9ff4e94fab202bedde1 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Mon, 21 Mar 2022 14:08:24 +0530 Subject: [PATCH 2/5] Moved autoCorrect flag to v4 message input and added doc comments for scrollToBottomBuilder --- .../stream_chat_flutter/lib/src/message_input.dart | 4 ---- .../lib/src/message_input/message_input.dart | 8 ++++++++ .../lib/src/message_list_view.dart | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 19a6592d..240d318a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -195,7 +195,6 @@ class MessageInput extends StatefulWidget { this.customOverlays = const [], this.mentionAllAppUsers = false, this.shouldKeepFocusAfterMessage, - this.autocorrectEnabled, }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", @@ -312,8 +311,6 @@ class MessageInput extends StatefulWidget { /// The default behaviour keeps focus until a command is enabled. final bool? shouldKeepFocusAfterMessage; - final bool? autocorrectEnabled; - @override MessageInputState createState() => MessageInputState(); @@ -701,7 +698,6 @@ class MessageInputState extends State { textAlignVertical: TextAlignVertical.center, decoration: _getInputDecoration(context), textCapitalization: TextCapitalization.sentences, - autocorrect: widget.autocorrectEnabled ?? true, ), ), ], 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 0ffa472c..7116e435 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 @@ -213,6 +213,7 @@ class StreamMessageInput extends StatefulWidget { this.enableSafeArea, this.elevation, this.shadow, + this.autoCorrect, }) : super(key: key); /// List of options for showing overlays. @@ -332,6 +333,10 @@ class StreamMessageInput extends StatefulWidget { /// Shadow for the [StreamMessageInput] widget final BoxShadow? shadow; + /// Disable autoCorrect by passing false + /// autoCorrect is enabled by default + final bool? autoCorrect; + static bool _defaultValidator(Message message) => message.text?.isNotEmpty == true || message.attachments.isNotEmpty; @@ -363,6 +368,8 @@ class StreamMessageInputState extends State bool get _isEditing => _effectiveController.value.status != MessageSendingStatus.sending; + bool get _autoCorrect => widget.autoCorrect ?? true; + RestorableMessageInputController? _controller; MessageInputController get _effectiveController => @@ -805,6 +812,7 @@ class StreamMessageInputState extends State textAlignVertical: TextAlignVertical.center, decoration: _getInputDecoration(context), textCapitalization: TextCapitalization.sentences, + autocorrect: _autoCorrect, ), ), ], diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index e7b2d215..1439dfb3 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -248,6 +248,20 @@ class StreamMessageListView extends StatefulWidget { /// messages and the scroll offset is not zero final bool showScrollToBottom; + /// Function used to build a custom scroll to bottom widget + /// + /// Provides the current unread messages count and a reference + /// to the function that is executed on tap of this widget by default + /// + /// As an example: + /// MessageListView( + /// scrollToBottomBuilder: (unreadCount, defaultTapAction) { + /// return InkWell( + /// onTap: () => defaultTapAction(unreadCount), + /// child: Text('Scroll To Bottom'), + /// ); + /// }, + /// ), final Widget Function( int unreadCount, Future Function(int) scrollToBottomDefaultTapAction, From d59a755f7ceb8f4fd00eb328882a8695b20a5627 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Mon, 21 Mar 2022 14:34:21 +0530 Subject: [PATCH 3/5] Add flag to control if default emoji suggestions overlay is visible --- .../lib/src/message_input/message_input.dart | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 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 7116e435..8cd525c1 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 @@ -214,6 +214,7 @@ class StreamMessageInput extends StatefulWidget { this.elevation, this.shadow, this.autoCorrect, + this.disableEmojiSuggestionsOverlay, }) : super(key: key); /// List of options for showing overlays. @@ -337,6 +338,10 @@ class StreamMessageInput extends StatefulWidget { /// autoCorrect is enabled by default final bool? autoCorrect; + /// Disable the default emoji suggestions + /// Enabled by default + final bool? disableEmojiSuggestionsOverlay; + static bool _defaultValidator(Message message) => message.text?.isNotEmpty == true || message.attachments.isNotEmpty; @@ -370,6 +375,9 @@ class StreamMessageInputState extends State bool get _autoCorrect => widget.autoCorrect ?? true; + bool get _disableEmojiSuggestionsOverlay => + widget.disableEmojiSuggestionsOverlay ?? false; + RestorableMessageInputController? _controller; MessageInputController get _effectiveController => @@ -599,18 +607,19 @@ class StreamMessageInputState extends State visible: _showCommandsOverlay, widget: _buildCommandsOverlayEntry(), ), - OverlayOptions( - visible: _focusNode.hasFocus && - _effectiveController.text.isNotEmpty && - _effectiveController.baseOffset > 0 && - _effectiveController.text - .substring( - 0, - _effectiveController.baseOffset, - ) - .contains(':'), - widget: _buildEmojiOverlay(), - ), + if (!_disableEmojiSuggestionsOverlay) + OverlayOptions( + visible: _focusNode.hasFocus && + _effectiveController.text.isNotEmpty && + _effectiveController.baseOffset > 0 && + _effectiveController.text + .substring( + 0, + _effectiveController.baseOffset, + ) + .contains(':'), + widget: _buildEmojiOverlay(), + ), OverlayOptions( visible: _showMentionsOverlay, widget: _buildMentionsOverlayEntry(), From 67c094b12bb5cb172da4e923371f902f86c49bdb Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Tue, 5 Apr 2022 13:46:57 +0530 Subject: [PATCH 4/5] Added an option to pass InlineSpan builders for full control of message input --- .../lib/src/message_input_controller.dart | 16 +++++----- .../src/message_text_field_controller.dart | 29 +++++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) 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 65c6d8d8..f7ddc1e1 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 @@ -19,37 +19,37 @@ class MessageInputController extends ValueNotifier { /// message. factory MessageInputController({ Message? message, - Map? textPatternStyle, + Map? textPatternSpan, }) => MessageInputController._( initialMessage: message ?? Message(), - textPatternStyle: textPatternStyle, + textPatternSpan: textPatternSpan, ); /// Creates a controller for an editable text field from an initial [text]. factory MessageInputController.fromText( String? text, { - Map? textPatternStyle, + Map? textPatternSpan, }) => MessageInputController._( initialMessage: Message(text: text), - textPatternStyle: textPatternStyle, + textPatternSpan: textPatternSpan, ); /// Creates a controller for an editable text field from initial /// [attachments]. factory MessageInputController.fromAttachments( List attachments, { - Map? textPatternStyle, + Map? textPatternSpan, }) => MessageInputController._( initialMessage: Message(attachments: attachments), - textPatternStyle: textPatternStyle, + textPatternSpan: textPatternSpan, ); MessageInputController._({ required Message initialMessage, - Map? textPatternStyle, + Map? textPatternSpan, }) : _textEditingController = MessageTextFieldController.fromValue( initialMessage.text == null ? const TextEditingValue() @@ -57,7 +57,7 @@ class MessageInputController extends ValueNotifier { text: initialMessage.text!, composing: TextRange.collapsed(initialMessage.text!.length), ), - textPatternStyle: textPatternStyle, + textPatternSpan: textPatternSpan, ), _initialMessage = initialMessage, super(initialMessage) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart index 0f9f75c6..cba47cf2 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -/// A function that takes a [BuildContext] and returns a [TextStyle]. -typedef TextStyleBuilder = TextStyle? Function( +/// A function that takes a [BuildContext] and returns an [InlineSpan]. +typedef InlineSpanBuilder = InlineSpan Function( BuildContext context, String text, ); @@ -11,17 +11,17 @@ class MessageTextFieldController extends TextEditingController { /// Returns a new MessageTextFieldController MessageTextFieldController({ String? text, - this.textPatternStyle, + this.textPatternSpan, }) : super(text: text); /// Returns a new MessageTextFieldController with the given text [value]. MessageTextFieldController.fromValue( TextEditingValue? value, { - this.textPatternStyle, + this.textPatternSpan, }) : super.fromValue(value); /// A map of style to apply to the text matching the RegExp patterns. - final Map? textPatternStyle; + final Map? textPatternSpan; /// Builds a [TextSpan] from the current text, /// highlighting the matches for [textPatternStyle]. @@ -31,7 +31,7 @@ class MessageTextFieldController extends TextEditingController { TextStyle? style, required bool withComposing, }) { - final pattern = textPatternStyle; + final pattern = textPatternSpan; if (pattern == null || pattern.isEmpty) { return super.buildTextSpan( context: context, @@ -45,13 +45,10 @@ class MessageTextFieldController extends TextEditingController { onMatch: (match) { final text = match[0]!; final key = pattern.keys.firstWhere((it) => it.hasMatch(text)); - return TextSpan( - text: text, - style: pattern[key]?.call( - context, - text, - ), - ); + return pattern[key]?.call(context, text) ?? + TextSpan( + text: text, + ); }, ); } @@ -60,10 +57,10 @@ class MessageTextFieldController extends TextEditingController { extension _TextSpanX on TextSpan { TextSpan splitMapJoin( Pattern pattern, { - TextSpan Function(Match)? onMatch, - TextSpan Function(TextSpan)? onNonMatch, + InlineSpan Function(Match)? onMatch, + InlineSpan Function(InlineSpan)? onNonMatch, }) { - final children = []; + final children = []; toPlainText().splitMapJoin( pattern, From 92b8711ccf1ff5d78092532183e81fd0a489cce1 Mon Sep 17 00:00:00 2001 From: Ayush Shekhar Date: Wed, 6 Apr 2022 18:26:09 +0530 Subject: [PATCH 5/5] Removed the inline span builder code from messageinput --- .../lib/src/message_input_controller.dart | 16 +++++----- .../src/message_text_field_controller.dart | 29 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) 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 f7ddc1e1..65c6d8d8 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 @@ -19,37 +19,37 @@ class MessageInputController extends ValueNotifier { /// message. factory MessageInputController({ Message? message, - Map? textPatternSpan, + Map? textPatternStyle, }) => MessageInputController._( initialMessage: message ?? Message(), - textPatternSpan: textPatternSpan, + textPatternStyle: textPatternStyle, ); /// Creates a controller for an editable text field from an initial [text]. factory MessageInputController.fromText( String? text, { - Map? textPatternSpan, + Map? textPatternStyle, }) => MessageInputController._( initialMessage: Message(text: text), - textPatternSpan: textPatternSpan, + textPatternStyle: textPatternStyle, ); /// Creates a controller for an editable text field from initial /// [attachments]. factory MessageInputController.fromAttachments( List attachments, { - Map? textPatternSpan, + Map? textPatternStyle, }) => MessageInputController._( initialMessage: Message(attachments: attachments), - textPatternSpan: textPatternSpan, + textPatternStyle: textPatternStyle, ); MessageInputController._({ required Message initialMessage, - Map? textPatternSpan, + Map? textPatternStyle, }) : _textEditingController = MessageTextFieldController.fromValue( initialMessage.text == null ? const TextEditingValue() @@ -57,7 +57,7 @@ class MessageInputController extends ValueNotifier { text: initialMessage.text!, composing: TextRange.collapsed(initialMessage.text!.length), ), - textPatternSpan: textPatternSpan, + textPatternStyle: textPatternStyle, ), _initialMessage = initialMessage, super(initialMessage) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart index cba47cf2..0f9f75c6 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -/// A function that takes a [BuildContext] and returns an [InlineSpan]. -typedef InlineSpanBuilder = InlineSpan Function( +/// A function that takes a [BuildContext] and returns a [TextStyle]. +typedef TextStyleBuilder = TextStyle? Function( BuildContext context, String text, ); @@ -11,17 +11,17 @@ class MessageTextFieldController extends TextEditingController { /// Returns a new MessageTextFieldController MessageTextFieldController({ String? text, - this.textPatternSpan, + this.textPatternStyle, }) : super(text: text); /// Returns a new MessageTextFieldController with the given text [value]. MessageTextFieldController.fromValue( TextEditingValue? value, { - this.textPatternSpan, + this.textPatternStyle, }) : super.fromValue(value); /// A map of style to apply to the text matching the RegExp patterns. - final Map? textPatternSpan; + final Map? textPatternStyle; /// Builds a [TextSpan] from the current text, /// highlighting the matches for [textPatternStyle]. @@ -31,7 +31,7 @@ class MessageTextFieldController extends TextEditingController { TextStyle? style, required bool withComposing, }) { - final pattern = textPatternSpan; + final pattern = textPatternStyle; if (pattern == null || pattern.isEmpty) { return super.buildTextSpan( context: context, @@ -45,10 +45,13 @@ class MessageTextFieldController extends TextEditingController { onMatch: (match) { final text = match[0]!; final key = pattern.keys.firstWhere((it) => it.hasMatch(text)); - return pattern[key]?.call(context, text) ?? - TextSpan( - text: text, - ); + return TextSpan( + text: text, + style: pattern[key]?.call( + context, + text, + ), + ); }, ); } @@ -57,10 +60,10 @@ class MessageTextFieldController extends TextEditingController { extension _TextSpanX on TextSpan { TextSpan splitMapJoin( Pattern pattern, { - InlineSpan Function(Match)? onMatch, - InlineSpan Function(InlineSpan)? onNonMatch, + TextSpan Function(Match)? onMatch, + TextSpan Function(TextSpan)? onNonMatch, }) { - final children = []; + final children = []; toPlainText().splitMapJoin( pattern,