From fc06da627969d3e5f8608aa4276a84087cf9e8a4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 28 Oct 2022 13:08:49 +0200 Subject: [PATCH 1/4] feat(ui): add sendMessageKey and clearQuotedMessageKey --- .../src/platform_widget_base.dart | 4 +- .../message_input/stream_message_input.dart | 64 +++++++++++++------ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart index 9d3470d6..0487bd43 100644 --- a/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart +++ b/packages/stream_chat_flutter/lib/platform_widget_builder/src/platform_widget_base.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart' show Theme; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; /// A generic widget builder function. @@ -30,7 +30,7 @@ abstract class PlatformWidgetBase : CrossFadeState.showSecond, ), ), - // PlatformWidgetBuilder( - // mobile: (context, child) => _buildFilePickerSection(), - // ), ], ), ), @@ -753,25 +759,45 @@ class StreamMessageInputState extends State LimitedBox( maxHeight: widget.maxHeight, child: PlatformWidgetBuilder( - web: (context, child) => KeyboardShortcutRunner( - onEnterKeypress: sendMessage, - onEscapeKeypress: () { - if (_hasQuotedMessage && - _effectiveController.text.isEmpty) { - widget.onQuotedMessageCleared?.call(); - } - }, + web: (context, child) => Focus( child: child!, + onKeyEvent: (node, event) { + if (widget.sendMessageKey != null && + event.physicalKey == widget.sendMessageKey) { + sendMessage(); + return KeyEventResult.handled; + } else if (widget.clearQuotedMessageKey != null && + event.physicalKey == + widget.clearQuotedMessageKey) { + if (_hasQuotedMessage && + _effectiveController.text.isEmpty) { + widget.onQuotedMessageCleared?.call(); + } + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; + }, ), - desktop: (context, child) => KeyboardShortcutRunner( - onEnterKeypress: sendMessage, - onEscapeKeypress: () { - if (_hasQuotedMessage && - _effectiveController.text.isEmpty) { - widget.onQuotedMessageCleared?.call(); - } - }, + desktop: (context, child) => Focus( child: child!, + onKeyEvent: (node, event) { + if (widget.sendMessageKey != null && + event.physicalKey == widget.sendMessageKey) { + sendMessage(); + return KeyEventResult.handled; + } else if (widget.clearQuotedMessageKey != null && + event.physicalKey == + widget.clearQuotedMessageKey) { + if (_hasQuotedMessage && + _effectiveController.text.isEmpty) { + widget.onQuotedMessageCleared?.call(); + } + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; + }, ), mobile: (context, child) => child, child: StreamMessageTextField( From d814d5b0a3fdf5152138bb4e9b15cfb3521cf385 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 28 Oct 2022 13:09:36 +0200 Subject: [PATCH 2/4] chore(ui): update changelog --- packages/stream_chat_flutter/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 61782c02..e8047b76 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +✅ Added + +- Added `StreamMessageInput.sendMessageKey` and `StreamMessageInput.clearQuotedMessageKey` to customize the keys used to send and clear the quoted message. + ## 5.1.0 🐞 Fixed From 64e41475d9b4da457c93e6e7498aa439cb6b68f7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 31 Oct 2022 12:22:56 +0100 Subject: [PATCH 3/4] fix(ui): use a predicate instead of a key --- packages/stream_chat_flutter/CHANGELOG.md | 2 +- .../message_input/stream_message_input.dart | 80 +++++++++---------- .../lib/src/utils/typedefs.dart | 5 ++ 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index e8047b76..82e1da48 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -2,7 +2,7 @@ ✅ Added -- Added `StreamMessageInput.sendMessageKey` and `StreamMessageInput.clearQuotedMessageKey` to customize the keys used to send and clear the quoted message. +- Added `StreamMessageInput.sendMessageKeyPredicate` and `StreamMessageInput.clearQuotedMessageKeyPredicate` to customize the keys used to send and clear the quoted message. ## 5.1.0 diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 4f83d080..0a7aa4d3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -111,15 +111,16 @@ class StreamMessageInput extends StatefulWidget { this.enableMentionsOverlay = true, this.onQuotedMessageCleared, this.enableActionAnimation = true, - this.sendMessageKey = PhysicalKeyboardKey.enter, - this.clearQuotedMessageKey = PhysicalKeyboardKey.escape, + this.sendMessageKeyPredicate = _defaultSendMessageKeyPredicate, + this.clearQuotedMessageKeyPredicate = + _defaultClearQuotedMessageKeyPredicate, }); - /// The key used to send a message on web/desktop - final PhysicalKeyboardKey? sendMessageKey; + /// The predicate used to send a message on desktop/web + final RawKeyEventPredicate? sendMessageKeyPredicate; - /// The key used to send a message on web/desktop - final PhysicalKeyboardKey? clearQuotedMessageKey; + /// The predicate used to clear the quoted message on desktop/web + final RawKeyEventPredicate? clearQuotedMessageKeyPredicate; /// If true the message input will animate the actions while you type final bool enableActionAnimation; @@ -261,6 +262,18 @@ class StreamMessageInput extends StatefulWidget { static bool _defaultValidator(Message message) => message.text?.isNotEmpty == true || message.attachments.isNotEmpty; + static bool _defaultSendMessageKeyPredicate( + FocusNode node, + RawKeyEvent event, + ) => + event.logicalKey == LogicalKeyboardKey.enter; + + static bool _defaultClearQuotedMessageKeyPredicate( + FocusNode node, + RawKeyEvent event, + ) => + event.logicalKey == LogicalKeyboardKey.escape; + @override StreamMessageInputState createState() => StreamMessageInputState(); } @@ -760,44 +773,12 @@ class StreamMessageInputState extends State maxHeight: widget.maxHeight, child: PlatformWidgetBuilder( web: (context, child) => Focus( + onKey: _handleKeyPressed, child: child!, - onKeyEvent: (node, event) { - if (widget.sendMessageKey != null && - event.physicalKey == widget.sendMessageKey) { - sendMessage(); - return KeyEventResult.handled; - } else if (widget.clearQuotedMessageKey != null && - event.physicalKey == - widget.clearQuotedMessageKey) { - if (_hasQuotedMessage && - _effectiveController.text.isEmpty) { - widget.onQuotedMessageCleared?.call(); - } - return KeyEventResult.handled; - } - - return KeyEventResult.ignored; - }, ), desktop: (context, child) => Focus( + onKey: _handleKeyPressed, child: child!, - onKeyEvent: (node, event) { - if (widget.sendMessageKey != null && - event.physicalKey == widget.sendMessageKey) { - sendMessage(); - return KeyEventResult.handled; - } else if (widget.clearQuotedMessageKey != null && - event.physicalKey == - widget.clearQuotedMessageKey) { - if (_hasQuotedMessage && - _effectiveController.text.isEmpty) { - widget.onQuotedMessageCleared?.call(); - } - return KeyEventResult.handled; - } - - return KeyEventResult.ignored; - }, ), mobile: (context, child) => child, child: StreamMessageTextField( @@ -827,6 +808,25 @@ class StreamMessageInputState extends State ); } + KeyEventResult _handleKeyPressed( + FocusNode node, + RawKeyEvent event, + ) { + if (widget.sendMessageKeyPredicate != null && + widget.sendMessageKeyPredicate!(node, event)) { + sendMessage(); + return KeyEventResult.handled; + } else if (widget.clearQuotedMessageKeyPredicate != null && + widget.clearQuotedMessageKeyPredicate!(node, event)) { + if (_hasQuotedMessage && _effectiveController.text.isEmpty) { + widget.onQuotedMessageCleared?.call(); + } + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; + } + InputDecoration _getInputDecoration(BuildContext context) { final passedDecoration = _messageInputTheme.inputDecoration; return InputDecoration( diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index b6e1af43..fda46b2d 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -340,6 +340,11 @@ typedef DownloadedPathCallback = void Function(String? path); /// {@endtemplate} typedef UserTapCallback = void Function(User, Widget?); +/// {@template rawKeyEventPredicate} +/// Callback called to react to a raw key event +/// {@endtemplate} +typedef RawKeyEventPredicate = bool Function(FocusNode, RawKeyEvent); + /// {@template userItemBuilder} /// Builder used to create a custom [ListUserItem] from a [User] /// {@endtemplate} From 69ec1d8935e56377318380bbd6d38973c986a1e5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 7 Apr 2023 05:56:41 +0530 Subject: [PATCH 4/4] fix predicate checks only for keyUp events. Signed-off-by: xsahil03x --- .../message_input/stream_message_input.dart | 40 ++++++++++--------- .../lib/src/utils/typedefs.dart | 4 +- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart index 747487c8..b479b5ce 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input.dart @@ -117,10 +117,10 @@ class StreamMessageInput extends StatefulWidget { }); /// The predicate used to send a message on desktop/web - final RawKeyEventPredicate? sendMessageKeyPredicate; + final KeyEventPredicate sendMessageKeyPredicate; /// The predicate used to clear the quoted message on desktop/web - final RawKeyEventPredicate? clearQuotedMessageKeyPredicate; + final KeyEventPredicate clearQuotedMessageKeyPredicate; /// If true the message input will animate the actions while you type final bool enableActionAnimation; @@ -264,15 +264,19 @@ class StreamMessageInput extends StatefulWidget { static bool _defaultSendMessageKeyPredicate( FocusNode node, - RawKeyEvent event, - ) => - event.logicalKey == LogicalKeyboardKey.enter; + KeyEvent event, + ) { + // On desktop/web, send the message when the user presses the enter key. + return event is KeyUpEvent && event.logicalKey == LogicalKeyboardKey.enter; + } static bool _defaultClearQuotedMessageKeyPredicate( FocusNode node, - RawKeyEvent event, - ) => - event.logicalKey == LogicalKeyboardKey.escape; + KeyEvent event, + ) { + // On desktop/web, clear the quoted message when the user presses the escape key. + return event is KeyUpEvent && event.logicalKey == LogicalKeyboardKey.escape; + } @override StreamMessageInputState createState() => StreamMessageInputState(); @@ -773,11 +777,11 @@ class StreamMessageInputState extends State maxHeight: widget.maxHeight, child: PlatformWidgetBuilder( web: (context, child) => Focus( - onKey: _handleKeyPressed, + onKeyEvent: _handleKeyPressed, child: child!, ), desktop: (context, child) => Focus( - onKey: _handleKeyPressed, + onKeyEvent: _handleKeyPressed, child: child!, ), mobile: (context, child) => child, @@ -808,22 +812,22 @@ class StreamMessageInputState extends State ); } - KeyEventResult _handleKeyPressed( - FocusNode node, - RawKeyEvent event, - ) { - if (widget.sendMessageKeyPredicate != null && - widget.sendMessageKeyPredicate!(node, event)) { + KeyEventResult _handleKeyPressed(FocusNode node, KeyEvent event) { + // Check for send message key. + if (widget.sendMessageKeyPredicate(node, event)) { sendMessage(); return KeyEventResult.handled; - } else if (widget.clearQuotedMessageKeyPredicate != null && - widget.clearQuotedMessageKeyPredicate!(node, event)) { + } + + // Check for clear quoted message key. + if (widget.clearQuotedMessageKeyPredicate(node, event)) { if (_hasQuotedMessage && _effectiveController.text.isEmpty) { widget.onQuotedMessageCleared?.call(); } return KeyEventResult.handled; } + // Return ignored to allow other key events to be handled. return KeyEventResult.ignored; } diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index fda46b2d..6cc581bf 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -341,9 +341,9 @@ typedef DownloadedPathCallback = void Function(String? path); typedef UserTapCallback = void Function(User, Widget?); /// {@template rawKeyEventPredicate} -/// Callback called to react to a raw key event +/// Callback called to react to a key event /// {@endtemplate} -typedef RawKeyEventPredicate = bool Function(FocusNode, RawKeyEvent); +typedef KeyEventPredicate = bool Function(FocusNode, KeyEvent); /// {@template userItemBuilder} /// Builder used to create a custom [ListUserItem] from a [User]