diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index d4a755a5..c500cb5e 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -12,10 +12,6 @@ - Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls. -✅ Added - -- Added `MessageInput.customOverlays` property to add custom overlays to the `MessageInput`. - ## 2.2.1 ⚠️ Deprecated diff --git a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart deleted file mode 100644 index 9a7f45ee..00000000 --- a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - -/// Options for building an [Overlay] -class OverlayOption { - /// Constructor for creating an [OverlayOption]. - OverlayOption({ - required this.showOverlay, - required this.overlayBuilder, - }); - - /// Show or hide overlay - final bool showOverlay; - - /// Builder for creating overlay widget - final Widget Function(BuildContext, Offset anchor) overlayBuilder; -} - -/// Class to create an overlay anchored to a child widget -class AnchoredOverlay extends StatelessWidget { - /// Constructor for creating an [AnchoredOverlay] - const AnchoredOverlay({ - Key? key, - required this.overlayOptions, - required this.child, - }) : super(key: key); - - /// List of overlay options - /// The first one which showOverlay is true will be used - final List overlayOptions; - - /// Child to which overlay is anchored - final Widget child; - - @override - Widget build(BuildContext context) { - final overlay = - overlayOptions.firstWhereOrNull((option) => option.showOverlay); - return SizedBox( - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) => - OverlayBuilder( - showOverlay: overlay != null, - overlayBuilder: (BuildContext overlayContext) { - final box = context.findRenderObject() as RenderBox?; - if (box != null) { - final center = - box.size.center(box.localToGlobal(const Offset(0, 0))); - return overlay?.overlayBuilder(overlayContext, center) ?? - const Offstage(); - } else { - return const Offstage(); - } - }, - child: child, - ), - ), - ); - } -} - -/// Class for creating a declarative-ish overlay -class OverlayBuilder extends StatefulWidget { - /// Constructor for creating an [OverlayBuilder] - const OverlayBuilder({ - Key? key, - this.showOverlay = false, - required this.overlayBuilder, - required this.child, - }) : super(key: key); - - /// Show or hide overlay - final bool showOverlay; - - /// Builder for creating overlay widget - final Widget Function(BuildContext) overlayBuilder; - - /// Child to which overlay is anchored - final Widget child; - - @override - _OverlayBuilderState createState() => _OverlayBuilderState(); -} - -class _OverlayBuilderState extends State - with WidgetsBindingObserver { - OverlayEntry? overlayEntry; - - @override - void initState() { - super.initState(); - - WidgetsBinding.instance!.addObserver(this); - - if (widget.showOverlay) { - WidgetsBinding.instance?.addPostFrameCallback((_) => showOverlay()); - } - } - - EdgeInsets? _previousInsets; - - @override - void didChangeMetrics() { - super.didChangeMetrics(); - - final newInsets = MediaQuery.of(context).viewPadding; - - if (newInsets.bottom != _previousInsets?.bottom) { - WidgetsBinding.instance!.addPostFrameCallback((_) { - hideOverlay(); - syncWidgetAndOverlay(); - }); - - _previousInsets = newInsets; - } - } - - @override - void didUpdateWidget(OverlayBuilder oldWidget) { - super.didUpdateWidget(oldWidget); - WidgetsBinding.instance - ?.addPostFrameCallback((_) => syncWidgetAndOverlay()); - } - - @override - void reassemble() { - super.reassemble(); - WidgetsBinding.instance - ?.addPostFrameCallback((_) => syncWidgetAndOverlay()); - } - - @override - void dispose() { - WidgetsBinding.instance!.removeObserver(this); - if (isShowingOverlay()) { - hideOverlay(); - } - - super.dispose(); - } - - bool isShowingOverlay() => overlayEntry != null; - - void showOverlay() { - overlayEntry = OverlayEntry( - builder: widget.overlayBuilder, - ); - if (overlayEntry != null) { - addToOverlay(overlayEntry!); - } - } - - void addToOverlay(OverlayEntry entry) async { - Overlay.of(context)?.insert(entry); - } - - void hideOverlay() { - overlayEntry?.remove(); - overlayEntry = null; - } - - void syncWidgetAndOverlay() { - if (isShowingOverlay() && !widget.showOverlay) { - hideOverlay(); - } else if (!isShowingOverlay() && widget.showOverlay) { - showOverlay(); - } - } - - @override - Widget build(BuildContext context) => widget.child; -} - -/// Class to position Overlay -class CenterAbout extends StatelessWidget { - /// Constructor for creating an [CenterAbout] - const CenterAbout({ - Key? key, - required this.position, - required this.child, - }) : super(key: key); - - /// Position of child - final Offset position; - - /// Child widget to which overlay is attached - final Widget child; - - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - - return Positioned( - bottom: size.height - position.dy, - left: position.dx, - child: FractionalTranslation( - translation: const Offset(-0.5, 0), - child: Padding( - padding: const EdgeInsets.only(bottom: 32), - child: child, - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart index b51f8261..41b25d83 100644 --- a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart @@ -83,7 +83,7 @@ class MentionsOverlay extends StatelessWidget { }, child: mentionsTileBuilder != null ? mentionsTileBuilder!(context, m) - : MentionTile(m), + : MentionTile(m, key: ValueKey(m.user?.id)), ), ), ) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 837e88c4..f8c83d02 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -6,11 +6,11 @@ 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'; import 'package:shimmer/shimmer.dart'; -import 'package:stream_chat_flutter/src/anchored_overlay.dart'; import 'package:stream_chat_flutter/src/commands_overlay.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart'; @@ -192,7 +192,6 @@ class MessageInput extends StatefulWidget { this.onAttachmentLimitExceed, this.attachmentButtonBuilder, this.commandButtonBuilder, - this.customOverlays = const [], }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", @@ -223,9 +222,6 @@ class MessageInput extends StatefulWidget { /// Use this to transform the message final FutureOr Function(Message)? preMessageSending; - /// List of custom overlays - final List customOverlays; - /// Parent message in case of a thread final Message? parentMessage; @@ -466,40 +462,32 @@ class MessageInputState extends State { ); } - return AnchoredOverlay( - overlayOptions: [ - OverlayOption( - overlayBuilder: (context, offset) => CenterAbout( - position: offset, - child: _buildMentionsOverlayEntry(), - ), - showOverlay: _showMentionsOverlay, + return PortalEntry( + portal: _buildCommandsOverlayEntry(), + visible: _showCommandsOverlay, + 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, ), - OverlayOption( - showOverlay: _showCommandsOverlay, - overlayBuilder: (context, offset) => CenterAbout( - position: offset, - child: _buildCommandsOverlayEntry(), - ), - ), - OverlayOption( - showOverlay: _focusNode.hasFocus && - textEditingController.text.isNotEmpty && - textEditingController.selection.baseOffset > 0 && - textEditingController.text - .substring( - 0, - textEditingController.selection.baseOffset, - ) - .contains(':'), - overlayBuilder: (context, offset) => CenterAbout( - position: offset, - child: _buildEmojiOverlay(), - ), - ), - ...widget.customOverlays, - ], - child: child, + ), ); } @@ -946,8 +934,10 @@ class MessageInputState extends State { Widget _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); - // ignore: cast_nullable_to_non_nullable - final renderObject = context.findRenderObject() as RenderBox; + final renderObject = context.findRenderObject() as RenderBox?; + if (renderObject == null) { + return const Offstage(); + } return CommandsOverlay( channel: StreamChannel.of(context).channel, size: Size(renderObject.size.width - 16, 400), diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 694293a1..42188fa0 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -1,7 +1,6 @@ export 'package:jiffy/jiffy.dart'; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -export 'src/anchored_overlay.dart'; export 'src/attachment/attachment.dart'; export 'src/back_button.dart'; export 'src/channel_avatar.dart';