From 152adfc8b0cbfa0346700ccadc221cea172af929 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Sep 2021 16:44:03 +0200 Subject: [PATCH] refactor(ui): added `MultiPortal` and `customPortalOptions` --- packages/stream_chat_flutter/CHANGELOG.md | 4 ++ .../lib/src/message_input.dart | 53 +++++++++------- .../stream_chat_flutter/lib/src/overlays.dart | 63 +++++++++++++++++++ 3 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/overlays.dart diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index c500cb5e..e7bbf104 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -12,6 +12,10 @@ - Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls. +✅ Added + +- Added `MessageInput.customPortalOptions` property to add custom overlays to the `MessageInput`. + ## 2.2.1 ⚠️ Deprecated diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index f8c83d02..1c8f8f37 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -6,7 +6,6 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -18,6 +17,7 @@ import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/mentions_overlay.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; +import 'package:stream_chat_flutter/src/overlays.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; @@ -192,12 +192,16 @@ class MessageInput extends StatefulWidget { this.onAttachmentLimitExceed, this.attachmentButtonBuilder, this.commandButtonBuilder, + this.customPortalOptions = const [], }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", ), super(key: key); + /// List of portal options for showing overlays + final List customPortalOptions; + /// Message to edit final Message? editMessage; @@ -462,32 +466,33 @@ class MessageInputState extends State { ); } - return PortalEntry( - portal: _buildCommandsOverlayEntry(), - visible: _showCommandsOverlay, + return MultiPortal( childAnchor: Alignment.topCenter, portalAnchor: Alignment.bottomCenter, - child: PortalEntry( - portal: _buildEmojiOverlay(), - visible: _focusNode.hasFocus && - textEditingController.text.isNotEmpty && - textEditingController.selection.baseOffset > 0 && - textEditingController.text - .substring( - 0, - textEditingController.selection.baseOffset, - ) - .contains(':'), - childAnchor: Alignment.topCenter, - portalAnchor: Alignment.bottomCenter, - child: PortalEntry( - portal: _buildMentionsOverlayEntry(), - visible: _showMentionsOverlay, - childAnchor: Alignment.topCenter, - portalAnchor: Alignment.bottomCenter, - child: child, + portalOptions: [ + PortalOptions( + visible: _showCommandsOverlay, + widget: _buildCommandsOverlayEntry(), ), - ), + PortalOptions( + visible: _focusNode.hasFocus && + textEditingController.text.isNotEmpty && + textEditingController.selection.baseOffset > 0 && + textEditingController.text + .substring( + 0, + textEditingController.selection.baseOffset, + ) + .contains(':'), + widget: _buildEmojiOverlay(), + ), + PortalOptions( + visible: _showMentionsOverlay, + widget: _buildMentionsOverlayEntry(), + ), + ...widget.customPortalOptions, + ], + child: child, ); } diff --git a/packages/stream_chat_flutter/lib/src/overlays.dart b/packages/stream_chat_flutter/lib/src/overlays.dart new file mode 100644 index 00000000..59328429 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/overlays.dart @@ -0,0 +1,63 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_portal/flutter_portal.dart'; + +/// Class that contains the parameters for building a portal entry +class PortalOptions { + /// Constructs a new portal options object + /// [visible] - the visibility of the portal + /// [widget] - the widget to be displayed + PortalOptions({ + required this.visible, + required this.widget, + }); + + /// the visibility of the portal + final bool visible; + + /// the widget to be displayed + final Widget widget; +} + +/// Widget that renders a single portal widget from a list of [portalOptions] +/// It shows the first one that is visible +class MultiPortal extends StatelessWidget { + /// Constructs a new MultiPortal widget + /// [portalOptions] - the list of portal options + /// [portalAnchor] - the anchor relative to the portal + /// [childAnchor] - the anchor relative to the child + /// [child] - the child widget + const MultiPortal({ + Key? key, + required this.portalOptions, + required this.child, + required this.portalAnchor, + required this.childAnchor, + }) : super(key: key); + + /// The list of portal options + final List portalOptions; + + /// The child widget + final Widget child; + + /// The anchor relative to the portal + final Alignment? portalAnchor; + + /// The anchor relative to the child + final Alignment? childAnchor; + + @override + Widget build(BuildContext context) { + final visibleOverlay = + portalOptions.firstWhereOrNull((element) => element.visible); + + return PortalEntry( + childAnchor: childAnchor, + portalAnchor: portalAnchor, + visible: visibleOverlay != null, + portal: visibleOverlay?.widget, + child: child, + ); + } +}