diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index c5cc4660..49b324d6 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -7,6 +7,26 @@ - [[#1621]](https://github.com/GetStream/stream-chat-flutter/issues/1621) Fixed `createdAtStyle` null check error in `SendingIndicatorBuilder`. +✅ Added + +- Added support for customizing attachments in `StreamMessageInput`. Use various properties mentioned + below. [#1511](https://github.com/GetStream/stream-chat-flutter/issues/1511) + * `StreamMessageInput.attachmentListBuilder` to customize the attachment list. + * `StreamMessageInput.fileAttachmentListBuilder` to customize the file attachment list. + * `StreamMessageInput.mediaAttachmentListBuilder` to customize the media attachment list. Includes images, videos + and gifs. + * `StreamMessageInput.fileAttachmentBuilder` to customize the file attachment item shown in `FileAttachmentList`. + * `StreamMessageInput.mediaAttachmentBuilder` to customize the media attachment item shown in + `MediaAttachmentList`. + + +- Added `StreamMessageInput.quotedMessageAttachmentThumbnailBuilders` to customize the thumbnail builders for quoted + message attachments. + +🔄 Changed + +- Deprecated `StreamMessageInput.attachmentThumbnailBuilders` in favor of `StreamMessageInput.mediaAttachmentBuilder`. + ## 6.4.0 🐞 Fixed diff --git a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart index 10efca0b..0d5d4ecd 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/video_attachment.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; -import 'package:stream_chat_flutter/src/video/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamVideoAttachment} diff --git a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart index 98ae7df2..e7384fd9 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart @@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; -import 'package:stream_chat_flutter/src/video/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamGalleryFooter} diff --git a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart index a61ced63..ab90710a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/quoted_message_widget.dart @@ -2,7 +2,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/message_input/clear_input_item_button.dart'; -import 'package:stream_chat_flutter/src/video/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; 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 d4581ea0..97391d6a 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 @@ -3,14 +3,10 @@ import 'dart:async'; import 'dart:math'; -import 'package:cached_network_image/cached_network_image.dart' - hide ErrorListener; import 'package:desktop_drop/desktop_drop.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/message_input/attachment_button.dart'; import 'package:stream_chat_flutter/src/message_input/command_button.dart'; @@ -19,7 +15,6 @@ import 'package:stream_chat_flutter/src/message_input/quoted_message_widget.dart import 'package:stream_chat_flutter/src/message_input/quoting_message_top_area.dart'; import 'package:stream_chat_flutter/src/message_input/simple_safe_area.dart'; import 'package:stream_chat_flutter/src/message_input/tld.dart'; -import 'package:stream_chat_flutter/src/video/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; const _kCommandTrigger = '/'; @@ -110,6 +105,12 @@ class StreamMessageInput extends StatefulWidget { this.messageInputController, this.actions = const [], this.actionsLocation = ActionsLocation.left, + this.attachmentListBuilder, + this.fileAttachmentListBuilder, + this.mediaAttachmentListBuilder, + this.fileAttachmentBuilder, + this.mediaAttachmentBuilder, + @Deprecated('Use `mediaAttachmentBuilder` instead.') this.attachmentThumbnailBuilders, this.focusNode, this.sendButtonLocation = SendButtonLocation.outside, @@ -130,6 +131,7 @@ class StreamMessageInput extends StatefulWidget { this.mentionAllAppUsers = false, this.sendButtonBuilder, this.quotedMessageBuilder, + this.quotedMessageAttachmentThumbnailBuilders, this.shouldKeepFocusAfterMessage, this.validator = _defaultValidator, this.restorationId, @@ -209,9 +211,41 @@ class StreamMessageInput extends StatefulWidget { /// The location of the custom actions. final ActionsLocation actionsLocation; + /// Builder used to build the attachment list present in the message input. + /// + /// In case you want to customize only sub-parts of the attachment list, + /// consider using [fileAttachmentListBuilder], [mediaAttachmentListBuilder]. + final AttachmentListBuilder? attachmentListBuilder; + + /// Builder used to build the file type attachment list. + /// + /// In case you want to customize the attachment item, consider using + /// [fileAttachmentBuilder]. + final AttachmentListBuilder? fileAttachmentListBuilder; + + /// Builder used to build the media type attachment list. + /// + /// In case you want to customize the attachment item, consider using + /// [mediaAttachmentBuilder]. + final AttachmentListBuilder? mediaAttachmentListBuilder; + + /// Builder used to build the file attachment item. + final AttachmentItemBuilder? fileAttachmentBuilder; + + /// Builder used to build the media attachment item. + final AttachmentItemBuilder? mediaAttachmentBuilder; + /// Map that defines a thumbnail builder for an attachment type. + @Deprecated('Use `mediaAttachmentBuilder` instead.') final Map? attachmentThumbnailBuilders; + /// Map that defines a thumbnail builder for an attachment type. + /// + /// This is used to build the thumbnail for the attachment in the quoted + /// message. + final Map? + quotedMessageAttachmentThumbnailBuilders; + /// The focus node associated to the TextField. final FocusNode? focusNode; @@ -1147,193 +1181,93 @@ class StreamMessageInputState extends State messageTheme: _streamChatTheme.otherMessageTheme, padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), onQuotedMessageClear: widget.onQuotedMessageCleared, - attachmentThumbnailBuilders: widget.attachmentThumbnailBuilders, + attachmentThumbnailBuilders: + widget.quotedMessageAttachmentThumbnailBuilders, ); } Widget _buildAttachments() { - final nonOGAttachments = _effectiveController.attachments.where( - (it) => it.titleLink == null, - ); + final attachments = _effectiveController.attachments; + final nonOGAttachments = attachments.where((it) { + return it.titleLink == null; + }).toList(growable: false); + + // If there are no attachments, return an empty widget if (nonOGAttachments.isEmpty) return const Offstage(); - final fileAttachments = nonOGAttachments - .where((it) => it.type == 'file') - .toList(growable: false); - final remainingAttachments = nonOGAttachments - .where((it) => it.type != 'file') - .toList(growable: false); - return Column( - children: [ - if (fileAttachments.isNotEmpty) - Padding( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - child: LimitedBox( - maxHeight: 136, - child: ListView( - reverse: true, - shrinkWrap: true, - children: fileAttachments.reversed - .map( - (e) => ClipRRect( - borderRadius: BorderRadius.circular(10), - child: StreamFileAttachment( - message: Message(), // dummy message - attachment: e, - constraints: BoxConstraints.loose(Size( - MediaQuery.of(context).size.width * 0.65, - 56, - )), - trailing: Padding( - padding: const EdgeInsets.all(8), - child: _buildRemoveButton(e), - ), - ), - ), - ) - .insertBetween(const SizedBox(height: 8)), - ), - ), - ), - if (remainingAttachments.isNotEmpty) - Padding( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), - child: LimitedBox( - maxHeight: 104, - child: ListView( - scrollDirection: Axis.horizontal, - children: remainingAttachments - .map( - (attachment) => ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Stack( - children: [ - AspectRatio( - aspectRatio: 1, - child: SizedBox( - height: 104, - width: 104, - child: _buildAttachment(attachment), - ), - ), - Positioned( - top: 8, - right: 8, - child: _buildRemoveButton(attachment), - ), - ], - ), - ), - ) - .insertBetween(const SizedBox(width: 8)), - ), - ), - ), - ], - ); - } - Widget _buildRemoveButton(Attachment attachment) { - return SizedBox( - height: 24, - width: 24, - child: RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - highlightElevation: 0, - focusElevation: 0, - hoverElevation: 0, - onPressed: () async { - final file = attachment.file; - final uploadState = attachment.uploadState; + // Default callback for removing an attachment. + Future onAttachmentRemovePressed(Attachment attachment) async { + final file = attachment.file; + final uploadState = attachment.uploadState; - if (file != null && !uploadState.isSuccess && !isWeb) { - await StreamAttachmentHandler.instance.deleteAttachmentFile( - attachmentFile: file, - ); - } + if (file != null && !uploadState.isSuccess && !isWeb) { + await StreamAttachmentHandler.instance.deleteAttachmentFile( + attachmentFile: file, + ); + } - _effectiveController.removeAttachmentById(attachment.id); - }, - fillColor: - _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), - child: Center( - child: StreamSvgIcon.close( - size: 24, - color: _streamChatTheme.colorTheme.barsBg, - ), - ), - ), - ); - } + _effectiveController.removeAttachmentById(attachment.id); + } - Widget _buildAttachment(Attachment attachment) { - if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) == - true) { - return widget.attachmentThumbnailBuilders![attachment.type!]!( + // If the user has provided a custom attachment list builder, use that. + final attachmentListBuilder = widget.attachmentListBuilder; + if (attachmentListBuilder != null) { + return attachmentListBuilder( context, - attachment, + nonOGAttachments, + onAttachmentRemovePressed, ); } - switch (attachment.type) { - case 'image': - case 'giphy': - return attachment.file != null - ? Image.memory( - attachment.file!.bytes!, - fit: BoxFit.cover, - errorBuilder: (context, _, __) => Image.asset( - 'images/placeholder.png', - package: 'stream_chat_flutter', - ), - ) - : CachedNetworkImage( - imageUrl: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl!, - fit: BoxFit.cover, - errorWidget: (_, obj, trace) => - getFileTypeImage(attachment.extraData['other'] as String?), - placeholder: (context, _) => Shimmer.fromColors( - baseColor: _streamChatTheme.colorTheme.disabled, - highlightColor: _streamChatTheme.colorTheme.inputBg, - child: Image.asset( - 'images/placeholder.png', - fit: BoxFit.cover, - package: 'stream_chat_flutter', - ), + // Otherwise, use the default attachment list builder. + return LimitedBox( + maxHeight: 240, + child: StreamMessageInputAttachmentList( + attachments: nonOGAttachments, + onRemovePressed: onAttachmentRemovePressed, + fileAttachmentListBuilder: widget.fileAttachmentListBuilder, + mediaAttachmentListBuilder: widget.mediaAttachmentListBuilder, + fileAttachmentBuilder: widget.fileAttachmentBuilder, + mediaAttachmentBuilder: widget.mediaAttachmentBuilder ?? + // For backward compatibility. + // TODO: Remove in the next major release. + (context, attachment, onRemovePressed) { + final Widget mediaAttachmentThumbnail; + + final builder = + widget.attachmentThumbnailBuilders?[attachment.type]; + if (builder != null) { + mediaAttachmentThumbnail = builder(context, attachment); + } else { + mediaAttachmentThumbnail = MessageInputMediaAttachmentThumbnail( + attachment: attachment, + ); + } + + return ClipRRect( + key: Key(attachment.id), + borderRadius: BorderRadius.circular(10), + child: Stack( + children: [ + AspectRatio( + aspectRatio: 1, + child: mediaAttachmentThumbnail, + ), + Positioned( + top: 8, + right: 8, + child: RemoveAttachmentButton( + onPressed: onRemovePressed != null + ? () => onRemovePressed(attachment) + : null, + ), + ), + ], ), ); - case 'video': - return Stack( - children: [ - StreamVideoThumbnailImage( - constraints: BoxConstraints.loose( - const Size( - 104, - 104, - ), - ), - video: attachment.file?.path ?? attachment.assetUrl, - ), - Positioned( - left: 8, - bottom: 10, - child: SvgPicture.asset( - 'svgs/video_call_icon.svg', - package: 'stream_chat_flutter', - ), - ), - ], - ); - default: - return const ColoredBox( - color: Colors.black26, - child: Icon(Icons.insert_drive_file), - ); - } + }, + ), + ); } Widget _buildCommandButton(BuildContext context) { diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart new file mode 100644 index 00000000..a849bffc --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_input_attachment_list.dart @@ -0,0 +1,388 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment.dart'; +import 'package:stream_chat_flutter/src/misc/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/utils/utils.dart'; +import 'package:stream_chat_flutter/src/video/video_thumbnail_image.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// WidgetBuilder used to build the message input attachment list. +/// +/// see more: +/// - [StreamMessageInputAttachmentList] +typedef AttachmentListBuilder = Widget Function( + BuildContext context, + List attachments, + ValueSetter? onRemovePressed, +); + +/// WidgetBuilder used to build the message input attachment item. +/// +/// see more: +/// - [StreamMessageInputAttachmentList] +typedef AttachmentItemBuilder = Widget Function( + BuildContext context, + Attachment attachment, + ValueSetter? onRemovePressed, +); + +/// {@template stream_message_input_attachment_list} +/// Widget used to display the list of attachments added to the message input. +/// +/// By default, it displays the list of file attachments and media attachments +/// separately. +/// +/// You can customize the list of file attachments and media attachments using +/// [fileAttachmentListBuilder] and [mediaAttachmentListBuilder] respectively. +/// +/// You can also customize the attachment item using [fileAttachmentBuilder] and +/// [mediaAttachmentBuilder] respectively. +/// +/// You can override the default action of removing an attachment by providing +/// [onRemovePressed]. +/// {@endtemplate} +class StreamMessageInputAttachmentList extends StatefulWidget { + /// {@macro stream_message_input_attachment_list} + const StreamMessageInputAttachmentList({ + super.key, + required this.attachments, + this.onRemovePressed, + this.fileAttachmentBuilder, + this.mediaAttachmentBuilder, + this.fileAttachmentListBuilder, + this.mediaAttachmentListBuilder, + }); + + /// List of attachments to display thumbnails for. + /// + /// Open graph should be filtered out. + final Iterable attachments; + + /// Builder used to build the file attachment item. + final AttachmentItemBuilder? fileAttachmentBuilder; + + /// Builder used to build the media attachment item. + final AttachmentItemBuilder? mediaAttachmentBuilder; + + /// Builder used to build the file attachment list. + final AttachmentListBuilder? fileAttachmentListBuilder; + + /// Builder used to build the media attachment list. + final AttachmentListBuilder? mediaAttachmentListBuilder; + + /// Callback called when the remove button is pressed. + final ValueSetter? onRemovePressed; + + @override + State createState() => + _StreamMessageInputAttachmentListState(); +} + +class _StreamMessageInputAttachmentListState + extends State { + List fileAttachments = []; + List mediaAttachments = []; + + void _updateAttachments() { + // Clear the lists. + fileAttachments.clear(); + mediaAttachments.clear(); + + // Split the attachments into file and media attachments. + for (final attachment in widget.attachments) { + if (attachment.type == 'file') { + fileAttachments.add(attachment); + } else { + mediaAttachments.add(attachment); + } + } + } + + @override + void initState() { + super.initState(); + _updateAttachments(); + } + + @override + void didUpdateWidget(covariant StreamMessageInputAttachmentList oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.attachments != widget.attachments) { + _updateAttachments(); + } + } + + @override + Widget build(BuildContext context) { + // If there are no attachments, return an empty box. + if (fileAttachments.isEmpty && mediaAttachments.isEmpty) { + return const SizedBox(); + } + + return SingleChildScrollView( + padding: const EdgeInsets.only(top: 8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (mediaAttachments.isNotEmpty) + Flexible( + child: widget.mediaAttachmentListBuilder?.call( + context, + mediaAttachments, + widget.onRemovePressed, + ) ?? + MessageInputMediaAttachments( + attachments: mediaAttachments, + attachmentBuilder: widget.mediaAttachmentBuilder, + onRemovePressed: widget.onRemovePressed, + ), + ), + if (fileAttachments.isNotEmpty) + Flexible( + child: widget.fileAttachmentListBuilder?.call( + context, + fileAttachments, + widget.onRemovePressed, + ) ?? + MessageInputFileAttachments( + attachments: fileAttachments, + attachmentBuilder: widget.fileAttachmentBuilder, + onRemovePressed: widget.onRemovePressed, + ), + ), + ].insertBetween( + Divider( + height: 16, + indent: 16, + endIndent: 16, + thickness: 1, + color: StreamChatTheme.of(context).colorTheme.disabled, + ), + ), + ), + ); + } +} + +/// Widget used to display the list of file type attachments added to the +/// message input. +class MessageInputFileAttachments extends StatelessWidget { + /// Creates a new FileAttachments widget. + const MessageInputFileAttachments({ + super.key, + required this.attachments, + this.attachmentBuilder, + this.onRemovePressed, + }); + + /// List of file type attachments to display thumbnails for. + final List attachments; + + /// Builder used to build the file type attachment item. + final AttachmentItemBuilder? attachmentBuilder; + + /// Callback called when the remove button is pressed. + final ValueSetter? onRemovePressed; + + @override + Widget build(BuildContext context) { + return ListView( + reverse: true, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 8), + children: attachments.reversed.map( + (attachment) { + // If a custom builder is provided, use it. + final builder = attachmentBuilder; + if (builder != null) { + return builder(context, attachment, onRemovePressed); + } + + // Otherwise, use the default builder. + return ClipRRect( + key: Key(attachment.id), + borderRadius: BorderRadius.circular(10), + child: StreamFileAttachment( + message: Message(), // dummy message + attachment: attachment, + constraints: BoxConstraints.loose(Size( + MediaQuery.sizeOf(context).width * 0.65, + 56, + )), + trailing: Padding( + padding: const EdgeInsets.all(8), + child: RemoveAttachmentButton( + onPressed: onRemovePressed != null + ? () => onRemovePressed!(attachment) + : null, + ), + ), + ), + ); + }, + ).insertBetween(const SizedBox(height: 8)), + ); + } +} + +/// Widget used to display the list of media type attachments added to the +/// message input. +class MessageInputMediaAttachments extends StatelessWidget { + /// Creates a new MediaAttachments widget. + const MessageInputMediaAttachments({ + super.key, + required this.attachments, + this.attachmentBuilder, + this.onRemovePressed, + }); + + /// List of media type attachments to display thumbnails for. + /// + /// Only attachments of type `image`, `video` and `giphy` are supported. Shows + /// a placeholder for other types of attachments. + final List attachments; + + /// Builder used to build the media type attachment item. + final AttachmentItemBuilder? attachmentBuilder; + + /// Callback called when the remove button is pressed. + final ValueSetter? onRemovePressed; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 104, + child: ListView( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 8), + children: attachments.map( + (attachment) { + // If a custom builder is provided, use it. + final builder = attachmentBuilder; + if (builder != null) { + return builder(context, attachment, onRemovePressed); + } + + return ClipRRect( + key: Key(attachment.id), + borderRadius: BorderRadius.circular(10), + child: Stack( + children: [ + AspectRatio( + aspectRatio: 1, + child: MessageInputMediaAttachmentThumbnail( + attachment: attachment, + ), + ), + Positioned( + top: 8, + right: 8, + child: RemoveAttachmentButton( + onPressed: onRemovePressed != null + ? () => onRemovePressed!(attachment) + : null, + ), + ), + ], + ), + ); + }, + ).insertBetween(const SizedBox(width: 8)), + ), + ); + } +} + +/// A widget that displays a thumbnail for a media attachment. +class MessageInputMediaAttachmentThumbnail extends StatelessWidget { + /// Creates a new media attachment widget. + const MessageInputMediaAttachmentThumbnail({ + super.key, + required this.attachment, + }); + + /// The attachment to display. + final Attachment attachment; + + @override + Widget build(BuildContext context) { + switch (attachment.type) { + case 'image': + case 'giphy': + return attachment.file != null + ? Image.memory( + attachment.file!.bytes!, + fit: BoxFit.cover, + errorBuilder: (context, _, __) => Image.asset( + 'images/placeholder.png', + package: 'stream_chat_flutter', + ), + ) + : CachedNetworkImage( + imageUrl: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl!, + fit: BoxFit.cover, + errorWidget: (_, obj, trace) => Image.asset( + 'images/placeholder.png', + package: 'stream_chat_flutter', + ), + ); + case 'video': + return Stack( + children: [ + StreamVideoThumbnailImage( + video: attachment.file?.path ?? attachment.assetUrl, + ), + Positioned( + left: 8, + bottom: 10, + child: StreamSvgIcon.videoCall(), + ), + ], + ); + default: + return const ColoredBox( + color: Colors.black26, + child: Icon(Icons.insert_drive_file), + ); + } + } +} + +/// Material Button used for removing attachments. +class RemoveAttachmentButton extends StatelessWidget { + /// Creates a new remove attachment button. + const RemoveAttachmentButton({super.key, this.onPressed}); + + /// Callback when the remove attachment button is pressed. + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + final theme = StreamChatTheme.of(context); + final colorTheme = theme.colorTheme; + + return SizedBox( + width: 24, + height: 24, + child: RawMaterialButton( + elevation: 0, + focusElevation: 0, + hoverElevation: 0, + highlightElevation: 0, + onPressed: onPressed, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + fillColor: colorTheme.textHighEmphasis.withOpacity(0.5), + child: StreamSvgIcon.close( + size: 24, + color: colorTheme.barsBg, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index c2a0bc4d..e5ca5a1e 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -48,6 +48,7 @@ export 'src/message_input/attachment_picker/stream_attachment_picker_bottom_shee export 'src/message_input/countdown_button.dart'; export 'src/message_input/enums.dart'; export 'src/message_input/stream_message_input.dart'; +export 'src/message_input/stream_message_input_attachment_list.dart'; export 'src/message_input/stream_message_send_button.dart'; export 'src/message_input/stream_message_text_field.dart'; export 'src/message_list_view/message_details.dart'; @@ -96,3 +97,4 @@ export 'src/utils/device_segmentation.dart'; export 'src/utils/extensions.dart'; export 'src/utils/helpers.dart'; export 'src/utils/typedefs.dart'; +export 'src/video/video_thumbnail_image.dart';