diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 838f267c..3d568c3e 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -499,7 +499,7 @@ class Channel { ]); } - final isImage = it.type == 'image'; + final isImage = it.type == AttachmentType.image; final cancelToken = CancelToken(); Future future; if (isImage) { diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index b53e73b1..86d3efed 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -16,6 +16,7 @@ mixin AttachmentType { static const file = 'file'; static const giphy = 'giphy'; static const video = 'video'; + static const audio = 'audio'; /// Application custom types. static const urlPreview = 'url_preview'; @@ -53,19 +54,15 @@ class Attachment extends Equatable { }) : id = id ?? const Uuid().v4(), _type = type, title = title ?? file?.name, + _uploadState = uploadState, localUri = file?.path != null ? Uri.parse(file!.path!) : null, // For backwards compatibility, // set 'file_size', 'mime_type' in [extraData]. extraData = { ...extraData, if (file?.size != null) 'file_size': file?.size, - if (file?.mimeType != null) 'mime_type': file?.mimeType?.mimeType, - } { - this.uploadState = uploadState ?? - ((assetUrl != null || imageUrl != null || thumbUrl != null) - ? const UploadState.success() - : const UploadState.preparing()); - } + if (file?.mediaType != null) 'mime_type': file?.mediaType?.mimeType, + }; /// Create a new instance from a json factory Attachment.fromJson(Map json) => @@ -82,7 +79,8 @@ class Attachment extends Equatable { factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) => Attachment( - type: ogAttachment.type, + // If the type is not specified, we default to urlPreview. + type: ogAttachment.type ?? AttachmentType.urlPreview, title: ogAttachment.title, titleLink: ogAttachment.titleLink, text: ogAttachment.text, @@ -98,7 +96,9 @@ class Attachment extends Equatable { ///The attachment type based on the URL resource. This can be: audio, ///image or video String? get type { - if (_type == AttachmentType.image && titleLink != null) { + // If the attachment contains titleLink but is not of type giphy, we + // consider it as a urlPreview. + if (_type != AttachmentType.giphy && titleLink != null) { return AttachmentType.urlPreview; } @@ -107,6 +107,9 @@ class Attachment extends Equatable { final String? _type; + /// The raw attachment type. + String? get rawType => _type; + ///The link to which the attachment message points to. final String? titleLink; @@ -159,7 +162,15 @@ class Attachment extends Equatable { final AttachmentFile? file; /// The current upload state of the attachment - late final UploadState uploadState; + UploadState get uploadState { + if (_uploadState case final state?) return state; + + return ((assetUrl != null || imageUrl != null || thumbUrl != null) + ? const UploadState.success() + : const UploadState.preparing()); + } + + final UploadState? _uploadState; /// Map of custom channel extraData final Map extraData; diff --git a/packages/stream_chat/lib/src/core/models/attachment_file.dart b/packages/stream_chat/lib/src/core/models/attachment_file.dart index ee6690ca..261d7db3 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.dart @@ -62,7 +62,7 @@ class AttachmentFile { String? get extension => name?.split('.').last; /// The mime type of this file. - MediaType? get mimeType => name?.mimeType; + MediaType? get mediaType => name?.mediaType; /// Serialize to json Map toJson() => _$AttachmentFileToJson(this); @@ -75,13 +75,13 @@ class AttachmentFile { multiPartFile = MultipartFile.fromBytes( bytes!, filename: name, - contentType: mimeType, + contentType: mediaType, ); } else { multiPartFile = await MultipartFile.fromFile( path!, filename: name, - contentType: mimeType, + contentType: mediaType, ); } return multiPartFile; diff --git a/packages/stream_chat/lib/src/core/util/extension.dart b/packages/stream_chat/lib/src/core/util/extension.dart index a28dc12b..132413a6 100644 --- a/packages/stream_chat/lib/src/core/util/extension.dart +++ b/packages/stream_chat/lib/src/core/util/extension.dart @@ -20,8 +20,8 @@ extension MapX on Map { /// Useful extension functions for [String] extension StringX on String { - /// returns the mime type from the passed file name. - MediaType? get mimeType { + /// returns the media type from the passed file name. + MediaType? get mediaType { if (toLowerCase().endsWith('heic')) { return MediaType.parse('image/heic'); } else { diff --git a/packages/stream_chat/test/src/core/util/extension_test.dart b/packages/stream_chat/test/src/core/util/extension_test.dart index ae3c08d8..ad624c05 100644 --- a/packages/stream_chat/test/src/core/util/extension_test.dart +++ b/packages/stream_chat/test/src/core/util/extension_test.dart @@ -25,13 +25,13 @@ void main() { group('mimeType', () { test('should return null if `String` is not a filename', () { const fileName = 'not-a-file-name'; - final mimeType = fileName.mimeType; + final mimeType = fileName.mediaType; expect(mimeType, isNull); }); test('should return mimeType if string is a filename', () { const fileName = 'dummyFileName.jpeg'; - final mimeType = fileName.mimeType; + final mimeType = fileName.mediaType; expect(mimeType, isNotNull); expect(mimeType!.type, 'image'); expect(mimeType.subtype, 'jpeg'); @@ -39,7 +39,7 @@ void main() { test('should return `image/heic` if ends with `heic`', () { const fileName = 'dummyFileName.heic'; - final mimeType = fileName.mimeType; + final mimeType = fileName.mediaType; expect(mimeType, isNotNull); expect(mimeType!.type, 'image'); expect(mimeType.subtype, 'heic'); diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart index 976fa32f..5e51c733 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_widget_catalog.dart @@ -57,6 +57,8 @@ class AttachmentWidgetCatalog { extension on List { /// Groups the attachments by their type. Map> get grouped { - return groupBy(this, (attachment) => attachment.type!); + return groupBy(where((it) { + return it.type != null; + }), (attachment) => attachment.type!); } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart index 0abf4750..f8ef052f 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/attachment_widget_builder.dart @@ -1,20 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/attachment/file_attachment.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment.dart'; import 'package:stream_chat_flutter/src/attachment/gallery_attachment.dart'; -import 'package:stream_chat_flutter/src/attachment/giphy_attachment.dart'; -import 'package:stream_chat_flutter/src/attachment/image_attachment.dart'; -import 'package:stream_chat_flutter/src/attachment/thumbnail/giphy_attachment_thumbnail.dart'; -import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart'; -import 'package:stream_chat_flutter/src/attachment/thumbnail/video_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/attachment/url_attachment.dart'; -import 'package:stream_chat_flutter/src/attachment/video_attachment.dart'; import 'package:stream_chat_flutter/src/stream_chat.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_core/stream_chat_flutter_core.dart'; -import '../attachment_upload_state_builder.dart'; - part 'fallback_attachment_builder.dart'; part 'file_attachment_builder.dart'; @@ -82,41 +75,58 @@ abstract class StreamAttachmentWidgetBuilder { /// widget. static List defaultBuilders({ required Message message, + ShapeBorder? shape, + EdgeInsetsGeometry padding = const EdgeInsets.all(4), StreamAttachmentWidgetTapCallback? onAttachmentTap, }) { return [ - // Handles a mix of image, gif, video, and file attachments. + // Handles a mix of image, gif, video, url and file attachments. MixedAttachmentBuilder( + padding: padding, onAttachmentTap: onAttachmentTap, ), // Handles a mix of image, gif, and video attachments. GalleryAttachmentBuilder( + shape: shape, + padding: padding, + runSpacing: padding.vertical / 2, + spacing: padding.horizontal / 2, onAttachmentTap: onAttachmentTap, ), // Handles file attachments. FileAttachmentBuilder( + shape: shape, + padding: padding, onAttachmentTap: onAttachmentTap, ), // Handles giphy attachments. GiphyAttachmentBuilder( + shape: shape, + padding: padding, onAttachmentTap: onAttachmentTap, ), // Handles image attachments. ImageAttachmentBuilder( + shape: shape, + padding: padding, onAttachmentTap: onAttachmentTap, ), // Handles video attachments. VideoAttachmentBuilder( + shape: shape, + padding: padding, onAttachmentTap: onAttachmentTap, ), // We don't handle URL attachments if the message is a reply. if (message.quotedMessage == null) UrlAttachmentBuilder( + shape: shape, + padding: padding, onAttachmentTap: onAttachmentTap, ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/gallery_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/gallery_attachment_builder.dart index 9b86749d..cd2bf22e 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/gallery_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/gallery_attachment_builder.dart @@ -93,16 +93,6 @@ class GalleryAttachmentBuilder extends StreamAttachmentWidgetBuilder { attachments: galleryAttachments, itemBuilder: (context, index) { final attachment = galleryAttachments[index]; - final attachmentType = attachment.type; - - final isImage = attachmentType == AttachmentType.image; - final isVideo = attachmentType == AttachmentType.video; - final isGiphy = attachmentType == AttachmentType.giphy; - - assert( - isImage || isVideo || isGiphy, - 'Attachment type should be image, video or giphy', - ); VoidCallback? onTap; if (onAttachmentTap != null) { @@ -112,29 +102,13 @@ class GalleryAttachmentBuilder extends StreamAttachmentWidgetBuilder { return InkWell( onTap: onTap, child: Stack( - alignment: Alignment.center, children: [ - if (isImage) - StreamImageAttachmentThumbnail( - image: attachment, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - ) - else if (isVideo) - StreamVideoAttachmentThumbnail( - video: attachment, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - ) - else if (isGiphy) - StreamGiphyAttachmentThumbnail( - giphy: attachment, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - ), + StreamMediaAttachmentThumbnail( + media: attachment, + width: constraints.maxWidth, + height: constraints.maxHeight, + fit: BoxFit.cover, + ), Padding( padding: const EdgeInsets.all(8), child: StreamAttachmentUploadStateBuilder( diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/image_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/image_attachment_builder.dart index 145649db..14aa7c68 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/image_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/image_attachment_builder.dart @@ -62,6 +62,7 @@ class ImageAttachmentBuilder extends StreamAttachmentWidgetBuilder { child: InkWell( onTap: onTap, child: StreamImageAttachment( + shape: shape, message: message, constraints: constraints, image: image, diff --git a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart index b0eb7844..3d0eb466 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/builder/mixed_attachment_builder.dart @@ -3,67 +3,71 @@ part of 'attachment_widget_builder.dart'; /// {@template mixedAttachmentBuilder} /// A widget builder for Mixed attachment type. /// -/// This builder is used when a message contains both image/video/giphy and file -/// attachments. +/// This builder is used when a message contains a mix of media type and file +/// or url preview attachments. /// -/// This builder will render first image/video/giphy attachment and then render -/// the file attachments. +/// This builder will render first the url preview or file attachment and then +/// the media attachments. /// {@endtemplate} class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { /// {@macro mixedAttachmentBuilder} MixedAttachmentBuilder({ - this.shape, this.padding = const EdgeInsets.all(4), - this.onAttachmentTap, + StreamAttachmentWidgetTapCallback? onAttachmentTap, }) : _imageAttachmentBuilder = ImageAttachmentBuilder( + padding: EdgeInsets.zero, onAttachmentTap: onAttachmentTap, - padding: EdgeInsets.symmetric(horizontal: padding.horizontal), ), _videoAttachmentBuilder = VideoAttachmentBuilder( + padding: EdgeInsets.zero, onAttachmentTap: onAttachmentTap, - padding: EdgeInsets.symmetric(horizontal: padding.horizontal), ), _giphyAttachmentBuilder = GiphyAttachmentBuilder( + padding: EdgeInsets.zero, onAttachmentTap: onAttachmentTap, - padding: EdgeInsets.symmetric(horizontal: padding.horizontal), ), _galleryAttachmentBuilder = GalleryAttachmentBuilder( + padding: EdgeInsets.zero, onAttachmentTap: onAttachmentTap, - padding: EdgeInsets.symmetric(horizontal: padding.horizontal), ), _fileAttachmentBuilder = FileAttachmentBuilder( + padding: EdgeInsets.zero, + onAttachmentTap: onAttachmentTap, + ), + _urlAttachmentBuilder = UrlAttachmentBuilder( + padding: EdgeInsets.zero, onAttachmentTap: onAttachmentTap, - padding: EdgeInsets.symmetric(horizontal: padding.horizontal), ); - /// The shape of the gallery attachment. - final ShapeBorder? shape; - - /// The padding to apply to the gallery attachment widget. + /// The padding to apply to the mixed attachment widget. final EdgeInsetsGeometry padding; - /// The callback to call when the attachment is tapped. - final StreamAttachmentWidgetTapCallback? onAttachmentTap; - late final StreamAttachmentWidgetBuilder _imageAttachmentBuilder; late final StreamAttachmentWidgetBuilder _videoAttachmentBuilder; late final StreamAttachmentWidgetBuilder _giphyAttachmentBuilder; late final StreamAttachmentWidgetBuilder _galleryAttachmentBuilder; late final StreamAttachmentWidgetBuilder _fileAttachmentBuilder; + late final StreamAttachmentWidgetBuilder _urlAttachmentBuilder; @override bool canHandle( Message message, Map> attachments, ) { - final containsImage = attachments.keys.contains(AttachmentType.image); - final containsVideo = attachments.keys.contains(AttachmentType.video); - final containsGiphy = attachments.keys.contains(AttachmentType.giphy); - final containsFile = attachments.keys.contains(AttachmentType.file); + final types = attachments.keys; + + final containsImage = types.contains(AttachmentType.image); + final containsVideo = types.contains(AttachmentType.video); + final containsGiphy = types.contains(AttachmentType.giphy); + final containsFile = types.contains(AttachmentType.file); + final containsUrlPreview = types.contains(AttachmentType.urlPreview); final containsMedia = containsImage || containsVideo || containsGiphy; - return containsMedia && containsFile; + return containsMedia && containsFile || + containsMedia && containsUrlPreview || + containsFile && containsUrlPreview || + containsMedia && containsFile && containsUrlPreview; } @override @@ -74,6 +78,7 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { ) { assert(debugAssertCanHandle(message, attachments), ''); + final urls = attachments[AttachmentType.urlPreview]; final files = attachments[AttachmentType.file]; final images = attachments[AttachmentType.image]; final videos = attachments[AttachmentType.video]; @@ -86,11 +91,14 @@ class MixedAttachmentBuilder extends StreamAttachmentWidgetBuilder { child: Column( mainAxisSize: MainAxisSize.min, children: [ + if (urls != null) + _urlAttachmentBuilder.build(context, message, { + AttachmentType.urlPreview: urls, + }), if (files != null) - for (final file in files) - _fileAttachmentBuilder.build(context, message, { - AttachmentType.file: [file], - }), + _fileAttachmentBuilder.build(context, message, { + AttachmentType.file: files, + }), if (shouldBuildGallery) _galleryAttachmentBuilder.build(context, message, { if (images != null) AttachmentType.image: images, diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 964b3034..3790b416 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -128,19 +128,21 @@ class _FileTypeImage extends StatelessWidget { file: file, width: double.infinity, height: double.infinity, - // fit: BoxFit.cover, ); - final mimeType = file.title?.mimeType?.type; - final isImage = mimeType == 'image'; - final isVideo = mimeType == 'video'; + final mediaType = file.title?.mediaType; + final isImage = mediaType?.type == AttachmentType.image; + final isVideo = mediaType?.type == AttachmentType.video; if (isImage || isVideo) { final colorTheme = StreamChatTheme.of(context).colorTheme; child = Container( clipBehavior: Clip.hardEdge, decoration: ShapeDecoration( shape: RoundedRectangleBorder( - side: BorderSide(color: colorTheme.borders), + side: BorderSide( + color: colorTheme.borders, + strokeAlign: BorderSide.strokeAlignOutside, + ), borderRadius: BorderRadius.circular(8), ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart index 76106eb3..8acb39ba 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/gallery_attachment.dart @@ -127,6 +127,8 @@ class StreamGalleryAttachment extends StatelessWidget { [1], [1], ], + spacing: spacing, + runSpacing: runSpacing, children: [ itemBuilder(context, 0), itemBuilder(context, 1), @@ -145,6 +147,8 @@ class StreamGalleryAttachment extends StatelessWidget { pattern: const [ [1, 1], ], + spacing: spacing, + runSpacing: runSpacing, children: [ itemBuilder(context, 0), itemBuilder(context, 1), @@ -170,6 +174,8 @@ class StreamGalleryAttachment extends StatelessWidget { pattern: [ if (isLandscape1) [2, 1] else [1, 2], ], + spacing: spacing, + runSpacing: runSpacing, children: [ itemBuilder(context, 0), itemBuilder(context, 1), @@ -204,6 +210,8 @@ class StreamGalleryAttachment extends StatelessWidget { [1], [1, 1], ], + spacing: spacing, + runSpacing: runSpacing, reverse: !isLandscape1, children: [ itemBuilder(context, 0), @@ -238,6 +246,8 @@ class StreamGalleryAttachment extends StatelessWidget { return FlexGrid( pattern: pattern, maxChildren: 4, + spacing: spacing, + runSpacing: runSpacing, children: children, overlayBuilder: (context, remaining) { return IgnorePointer( diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index f8ba4e04..fc995c08 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -12,7 +12,7 @@ class StreamGiphyAttachment extends StatelessWidget { super.key, required this.message, required this.giphy, - this.type = GiphyInfoType.fixedHeightDownsampled, + this.type = GiphyInfoType.original, this.shape, this.constraints = const BoxConstraints(), }); diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart index 96d5e1ed..8c58fe2d 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart @@ -50,18 +50,18 @@ Future downloadAttachmentData( String? downloadUrl; String? fileName; /* ---IMAGES/GIFS--- */ - if (type == 'image') { + if (type == AttachmentType.image) { downloadUrl = attachment.imageUrl ?? attachment.assetUrl; fileName = attachment.title; fileName ??= 'attachment.${attachment.mimeType ?? 'png'}'; } /* ---GIPHY's--- */ - else if (type == 'giphy') { + else if (type == AttachmentType.giphy) { downloadUrl = attachment.thumbUrl; fileName = '${attachment.title}.gif'; } /* ---FILES AND VIDEOS--- */ - else if (type == 'file' || type == 'video') { + else if (type == AttachmentType.file || type == AttachmentType.video) { downloadUrl = attachment.assetUrl; fileName = attachment.title; } diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart index 996d3c4d..89374932 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/file_attachment_thumbnail.dart @@ -50,9 +50,9 @@ class StreamFileAttachmentThumbnail extends StatelessWidget { @override Widget build(BuildContext context) { - final mimeType = file.title?.mimeType?.type; + final mediaType = file.title?.mediaType; - final isImage = mimeType == 'image'; + final isImage = mediaType?.type == AttachmentType.image; if (isImage) { return StreamImageAttachmentThumbnail( image: file, @@ -62,7 +62,7 @@ class StreamFileAttachmentThumbnail extends StatelessWidget { ); } - final isVideo = mimeType == 'video'; + final isVideo = mediaType?.type == AttachmentType.video; if (isVideo) { return StreamVideoAttachmentThumbnail( video: file, @@ -73,6 +73,6 @@ class StreamFileAttachmentThumbnail extends StatelessWidget { } // Return a generic file type icon. - return getFileTypeImage(mimeType); + return getFileTypeImage(mediaType?.mimeType); } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/giphy_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/giphy_attachment_thumbnail.dart index 98b8e026..6b3a5e8d 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/giphy_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/giphy_attachment_thumbnail.dart @@ -50,6 +50,9 @@ class StreamGiphyAttachmentThumbnail extends StatelessWidget { return ThumbnailError( error: error, stackTrace: stackTrace, + height: double.infinity, + width: double.infinity, + fit: BoxFit.cover, ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart index 2d04c0fa..590fe4e7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/image_attachment_thumbnail.dart @@ -2,49 +2,12 @@ import 'dart:io' show File; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:image_size_getter/file_input.dart'; // For compatibility with flutter web. -import 'package:image_size_getter/image_size_getter.dart' hide Size; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_error.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_core/stream_chat_flutter_core.dart'; -extension AspectRatioX on Attachment { - /// Returns the size of the attachment if it is an image or giffy. - /// Otherwise, returns null. - Size? get originalSize { - // Return null if the attachment is not an image or giffy. - if (type != 'image' && type != 'giphy') return null; - - // Calculate size locally if the attachment is not uploaded yet. - final file = this.file; - if (file != null) { - ImageInput? input; - if (file.bytes != null) { - input = MemoryInput(file.bytes!); - } else if (file.path != null) { - input = FileInput(File(file.path!)); - } - - // Return null if the file does not contain enough information. - if (input == null) return null; - - final size = ImageSizeGetter.getSize(input); - if (size.needRotate) { - return Size(size.height.toDouble(), size.width.toDouble()); - } - return Size(size.width.toDouble(), size.height.toDouble()); - } - - // Otherwise, use the size provided by the server. - final width = originalWidth; - final height = originalHeight; - if (width == null || height == null) return null; - return Size(width.toDouble(), height.toDouble()); - } -} - /// {@template imageAttachmentThumbnail} /// Widget for building image attachment thumbnail. /// @@ -101,6 +64,9 @@ class StreamImageAttachmentThumbnail extends StatelessWidget { return ThumbnailError( error: error, stackTrace: stackTrace, + height: double.infinity, + width: double.infinity, + fit: BoxFit.cover, ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart new file mode 100644 index 00000000..ab8c60c5 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/media_attachment_thumbnail.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/giphy_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/thumbnail_error.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/video_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// {@template mediaAttachmentThumbnail} +/// Widget for building media attachment thumbnail. +/// +/// This widget is used when the [Attachment.type] is [AttachmentType.image], +/// [AttachmentType.video] or [AttachmentType.giphy]. +/// +/// see also: +/// * [StreamImageAttachmentThumbnail] +/// * [StreamVideoAttachmentThumbnail] +/// * [StreamGiphyAttachmentThumbnail] +/// {@endtemplate} +class StreamMediaAttachmentThumbnail extends StatelessWidget { + /// {@macro mediaAttachmentThumbnail} + const StreamMediaAttachmentThumbnail({ + super.key, + required this.media, + this.width, + this.height, + this.fit, + this.thumbnailSize, + this.thumbnailResizeType = 'clip', + this.thumbnailCropType = 'center', + this.gifInfoType = GiphyInfoType.original, + this.errorBuilder = _defaultErrorBuilder, + }); + + /// The giphy attachment to build the thumbnail for. + final Attachment media; + + /// The width of the thumbnail. + final double? width; + + /// The height of the thumbnail. + final double? height; + + /// How to inscribe the thumbnail into the space allocated during layout. + final BoxFit? fit; + + /// Builder used when the thumbnail fails to load. + final ThumbnailErrorBuilder errorBuilder; + + /// Size of the attachment image thumbnail. + /// + /// Ignored if the [Attachment.type] is not [AttachmentType.image]. + final Size? thumbnailSize; + + /// Resize type of the image attachment thumbnail. + /// + /// Defaults to [crop] + /// + /// Ignored if the [Attachment.type] is not [AttachmentType.image]. + final String /*clip|crop|scale|fill*/ thumbnailResizeType; + + /// Crop type of the image attachment thumbnail. + /// + /// Defaults to [center] + /// + /// Ignored if the [Attachment.type] is not [AttachmentType.image]. + final String /*center|top|bottom|left|right*/ thumbnailCropType; + + /// The type of giphy thumbnail to build. + /// + /// Ignored if the [Attachment.type] is not [AttachmentType.giphy]. + final GiphyInfoType gifInfoType; + + // Default error builder for image attachment thumbnail. + static Widget _defaultErrorBuilder( + BuildContext context, + Object error, + StackTrace? stackTrace, + ) { + return ThumbnailError( + error: error, + stackTrace: stackTrace, + height: double.infinity, + width: double.infinity, + fit: BoxFit.cover, + ); + } + + @override + Widget build(BuildContext context) { + final type = media.type; + if (type == AttachmentType.image) { + return StreamImageAttachmentThumbnail( + image: media, + width: width, + height: height, + fit: fit, + thumbnailSize: thumbnailSize, + thumbnailResizeType: thumbnailResizeType, + thumbnailCropType: thumbnailCropType, + errorBuilder: errorBuilder, + ); + } + + if (type == AttachmentType.giphy) { + return StreamGiphyAttachmentThumbnail( + giphy: media, + width: width, + height: height, + fit: fit, + type: gifInfoType, + errorBuilder: errorBuilder, + ); + } + + if (type == AttachmentType.video) { + return StreamVideoAttachmentThumbnail( + video: media, + width: width, + height: height, + fit: fit, + errorBuilder: errorBuilder, + ); + } + + return errorBuilder( + context, + 'Unsupported attachment type: $type', + StackTrace.current, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart index e1387403..37b288fc 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/thumbnail_error.dart @@ -21,8 +21,20 @@ class ThumbnailError extends StatelessWidget { super.key, required this.error, this.stackTrace, + this.width, + this.height, + this.fit, }); + /// The width of the thumbnail. + final double? width; + + /// The height of the thumbnail. + final double? height; + + /// How to inscribe the thumbnail into the space allocated during layout. + final BoxFit? fit; + /// The error that triggered this error widget. final Object error; @@ -33,7 +45,9 @@ class ThumbnailError extends StatelessWidget { Widget build(BuildContext context) { return Image.asset( 'images/placeholder.png', - fit: BoxFit.cover, + width: width, + height: height, + fit: fit, package: 'stream_chat_flutter', ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/video_attachment_thumbnail.dart b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/video_attachment_thumbnail.dart index ac4da7fa..0be758d5 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/thumbnail/video_attachment_thumbnail.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/thumbnail/video_attachment_thumbnail.dart @@ -46,6 +46,9 @@ class StreamVideoAttachmentThumbnail extends StatelessWidget { return ThumbnailError( error: error, stackTrace: stackTrace, + height: double.infinity, + width: double.infinity, + fit: BoxFit.cover, ); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart index b47f573f..9b44e259 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/url_attachment.dart @@ -47,7 +47,7 @@ class StreamUrlAttachment extends StatelessWidget { color: colorTheme.borders, strokeAlign: BorderSide.strokeAlignOutside, ), - borderRadius: BorderRadius.circular(14), + borderRadius: BorderRadius.circular(8), ); final backgroundColor = messageTheme.urlAttachmentBackgroundColor; @@ -62,44 +62,43 @@ class StreamUrlAttachment extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (urlAttachment.imageUrl != null) - Stack( - children: [ - AspectRatio( - // Default aspect ratio for Open Graph images. - // https://www.kapwing.com/resources/what-is-an-og-image-make-and-format-og-images-for-your-blog-or-webpage - aspectRatio: 1.91 / 1, - child: StreamImageAttachmentThumbnail( - image: urlAttachment, - fit: BoxFit.cover, - ), + Stack( + children: [ + AspectRatio( + // Default aspect ratio for Open Graph images. + // https://www.kapwing.com/resources/what-is-an-og-image-make-and-format-og-images-for-your-blog-or-webpage + aspectRatio: 1.91 / 1, + child: StreamImageAttachmentThumbnail( + image: urlAttachment, + fit: BoxFit.cover, ), - Positioned( - left: 0, - bottom: 0, - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(16), - ), - color: backgroundColor, + ), + Positioned( + left: 0, + bottom: 0, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(16), ), - child: Padding( - padding: const EdgeInsets.only( - top: 8, - left: 8, - right: 12, - bottom: 4, - ), - child: Text( - hostDisplayName, - style: messageTheme.urlAttachmentHostStyle, - ), + color: backgroundColor, + ), + child: Padding( + padding: const EdgeInsets.only( + top: 8, + left: 8, + right: 12, + bottom: 4, + ), + child: Text( + hostDisplayName, + style: messageTheme.urlAttachmentHostStyle, ), ), ), - ], - ), + ), + ], + ), Padding( padding: const EdgeInsets.all(8), child: Column( diff --git a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart index 2125c71b..aafab865 100644 --- a/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/attachment_actions_modal/attachment_actions_modal.dart @@ -129,7 +129,7 @@ class AttachmentActionsModal extends StatelessWidget { if (showSave) _buildButton( context, - attachment.type == 'video' + attachment.type == AttachmentType.video ? context.translations.saveVideoLabel : context.translations.saveImageLabel, StreamSvgIcon.iconSave( diff --git a/packages/stream_chat_flutter/lib/src/channel/stream_message_preview_text.dart b/packages/stream_chat_flutter/lib/src/channel/stream_message_preview_text.dart index 91e65d37..e6dd6a88 100644 --- a/packages/stream_chat_flutter/lib/src/channel/stream_message_preview_text.dart +++ b/packages/stream_chat_flutter/lib/src/channel/stream_message_preview_text.dart @@ -36,11 +36,11 @@ class StreamMessagePreviewText extends StatelessWidget { final messageTextParts = [ ...messageAttachments.map((it) { - if (it.type == 'image') { + if (it.type == AttachmentType.image) { return '📷'; - } else if (it.type == 'video') { + } else if (it.type == AttachmentType.video) { return '🎬'; - } else if (it.type == 'giphy') { + } else if (it.type == AttachmentType.giphy) { return '[GIF]'; } return it == message.attachments.last diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart index 1b0d82b7..80ee4328 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media.dart @@ -7,7 +7,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; -import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/context_menu_items/download_menu_item.dart'; import 'package:stream_chat_flutter/src/fullscreen_media/full_screen_media_widget.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -272,7 +272,7 @@ class _FullScreenMediaState extends State { } } if (widget.autoplayVideos && - currentAttachment.type == 'video') { + currentAttachment.type == AttachmentType.video) { final controller = videoPackages[currentAttachment.id]!; controller._chewieController?.play(); } @@ -294,47 +294,19 @@ class _FullScreenMediaState extends State { child: ContextMenuArea( verticalPadding: 0, builder: (_) => [ - DownloadMenuItem( - attachment: attachment, - ), + DownloadMenuItem(attachment: attachment), ], child: PhotoView.customChild( - heroAttributes: PhotoViewHeroAttributes( - tag: attachment.id, - ), - // imageProvider: (imageUrl == null && - // attachment.localUri != null && - // attachment.file?.bytes != null) - // ? Image.memory(attachment.file!.bytes!).image - // : CachedNetworkImageProvider(imageUrl!), - // errorBuilder: (_, __, ___) => const AttachmentError(), - // loadingBuilder: (context, _) { - // final image = Image.asset( - // 'images/placeholder.png', - // fit: BoxFit.cover, - // package: 'stream_chat_flutter', - // ); - // final colorTheme = - // StreamChatTheme.of(context).colorTheme; - // return Shimmer.fromColors( - // baseColor: colorTheme.disabled, - // highlightColor: colorTheme.inputBg, - // child: image, - // ); - // }, - child: StreamImageAttachmentThumbnail( - image: attachment, - width: double.infinity, - height: double.infinity, - ), maxScale: PhotoViewComputedScale.covered, minScale: PhotoViewComputedScale.contained, - // heroAttributes: PhotoViewHeroAttributes( - // tag: widget.mediaAttachmentPackages, - // ), backgroundDecoration: const BoxDecoration( color: Colors.transparent, ), + child: StreamMediaAttachmentThumbnail( + media: attachment, + width: double.infinity, + height: double.infinity, + ), ), ), ), @@ -353,9 +325,7 @@ class _FullScreenMediaState extends State { child: ContextMenuArea( verticalPadding: 0, builder: (_) => [ - DownloadMenuItem( - attachment: attachment, - ), + DownloadMenuItem(attachment: attachment), ], child: Chewie( controller: controller.chewieController!, diff --git a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart index 13185a79..bdc6d9cf 100644 --- a/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart +++ b/packages/stream_chat_flutter/lib/src/fullscreen_media/full_screen_media_desktop.dart @@ -94,7 +94,7 @@ class _FullScreenMediaDesktopState extends State { _pageController = PageController(initialPage: widget.startIndex); for (var i = 0; i < widget.mediaAttachmentPackages.length; i++) { final attachment = widget.mediaAttachmentPackages[i].attachment; - if (attachment.type != 'video') continue; + if (attachment.type != AttachmentType.video) continue; final package = DesktopVideoPackage(attachment); videoPackages[attachment.id] = package; } @@ -298,7 +298,8 @@ class _FullScreenMediaDesktopState extends State { p.player.pause(); } } - if (widget.autoplayVideos && currentAttachment.type == 'video') { + if (widget.autoplayVideos && + currentAttachment.type == AttachmentType.video) { final package = videoPackages[currentAttachment.id]!; package.player.play(); } @@ -307,7 +308,8 @@ class _FullScreenMediaDesktopState extends State { final currentAttachmentPackage = widget.mediaAttachmentPackages[index]; final attachment = currentAttachmentPackage.attachment; - if (attachment.type == 'image' || attachment.type == 'giphy') { + if (attachment.type == AttachmentType.image || + attachment.type == AttachmentType.giphy) { final imageUrl = attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl; @@ -359,7 +361,7 @@ class _FullScreenMediaDesktopState extends State { ), ), ); - } else if (attachment.type == 'video') { + } else if (attachment.type == AttachmentType.video) { final package = videoPackages[attachment.id]!; package.player.open( Playlist( 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 f63a7fb7..04928469 100644 --- a/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery/gallery_footer.dart @@ -95,7 +95,7 @@ class _StreamGalleryFooterState extends State { final url = attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl!; - final type = attachment.type == 'image' + final type = attachment.type == AttachmentType.image ? 'jpg' : url.split('?').first.split('.').last; final request = await HttpClient().getUrl(Uri.parse(url)); @@ -218,7 +218,7 @@ class _StreamGalleryFooterState extends State { widget.mediaAttachmentPackages[index]; final attachment = attachmentPackage.attachment; final message = attachmentPackage.message; - if (attachment.type == 'video') { + if (attachment.type == AttachmentType.video) { media = MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart index c9ff5595..2fb83715 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart @@ -114,119 +114,121 @@ class _MessageActionsModalState extends State { final child = Center( child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (widget.showReactionPicker && hasReactionPermission) - LayoutBuilder( - builder: (context, constraints) { - return Align( - alignment: Alignment( - calculateReactionsHorizontalAlignment( - user, - widget.message, - constraints, - fontSize, - orientation, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (widget.showReactionPicker && hasReactionPermission) + LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment( + calculateReactionsHorizontalAlignment( + user, + widget.message, + constraints, + fontSize, + orientation, + ), + 0, ), - 0, - ), - child: StreamReactionPicker( - message: widget.message, - ), - ); - }, + child: StreamReactionPicker( + message: widget.message, + ), + ); + }, + ), + const SizedBox(height: 10), + IgnorePointer( + child: widget.messageWidget, ), - const SizedBox(height: 10), - IgnorePointer( - child: widget.messageWidget, - ), - const SizedBox(height: 8), - Padding( - padding: EdgeInsets.only( - left: widget.reverse ? 0 : 40, - ), - child: SizedBox( - width: mediaQueryData.size.width * 0.75, - child: Material( - color: streamChatThemeData.colorTheme.appBg, - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (widget.showReplyMessage && - widget.message.state.isCompleted) - ReplyButton( - onTap: () { - Navigator.of(context).pop(); - if (widget.onReplyTap != null) { - widget.onReplyTap?.call(widget.message); - } - }, + const SizedBox(height: 8), + Padding( + padding: EdgeInsets.only( + left: widget.reverse ? 0 : 40, + ), + child: SizedBox( + width: mediaQueryData.size.width * 0.75, + child: Material( + color: streamChatThemeData.colorTheme.appBg, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (widget.showReplyMessage && + widget.message.state.isCompleted) + ReplyButton( + onTap: () { + Navigator.of(context).pop(); + if (widget.onReplyTap != null) { + widget.onReplyTap?.call(widget.message); + } + }, + ), + if (widget.showThreadReplyMessage && + (widget.message.state.isCompleted) && + widget.message.parentId == null) + ThreadReplyButton( + message: widget.message, + onThreadReplyTap: widget.onThreadReplyTap, + ), + if (widget.showResendMessage) + ResendMessageButton( + message: widget.message, + channel: channel, + ), + if (widget.showEditMessage) + EditMessageButton( + onTap: () { + Navigator.of(context).pop(); + _showEditBottomSheet(context); + }, + ), + if (widget.showCopyMessage) + CopyMessageButton( + onTap: () { + widget.onCopyTap?.call(widget.message); + Navigator.of(context).pop(); + }, + ), + if (widget.showFlagButton) + FlagMessageButton( + onTap: _showFlagDialog, + ), + if (widget.showPinButton) + PinMessageButton( + onTap: _togglePin, + pinned: widget.message.pinned, + ), + if (widget.showDeleteMessage) + DeleteMessageButton( + isDeleteFailed: + widget.message.state.isDeletingFailed, + onTap: _showDeleteBottomSheet, + ), + ...widget.customActions + .map((action) => _buildCustomAction( + context, + action, + )), + ].insertBetween( + Container( + height: 1, + color: streamChatThemeData.colorTheme.borders, ), - if (widget.showThreadReplyMessage && - (widget.message.state.isCompleted) && - widget.message.parentId == null) - ThreadReplyButton( - message: widget.message, - onThreadReplyTap: widget.onThreadReplyTap, - ), - if (widget.showResendMessage) - ResendMessageButton( - message: widget.message, - channel: channel, - ), - if (widget.showEditMessage) - EditMessageButton( - onTap: () { - Navigator.of(context).pop(); - _showEditBottomSheet(context); - }, - ), - if (widget.showCopyMessage) - CopyMessageButton( - onTap: () { - widget.onCopyTap?.call(widget.message); - Navigator.of(context).pop(); - }, - ), - if (widget.showFlagButton) - FlagMessageButton( - onTap: _showFlagDialog, - ), - if (widget.showPinButton) - PinMessageButton( - onTap: _togglePin, - pinned: widget.message.pinned, - ), - if (widget.showDeleteMessage) - DeleteMessageButton( - isDeleteFailed: - widget.message.state.isDeletingFailed, - onTap: _showDeleteBottomSheet, - ), - ...widget.customActions - .map((action) => _buildCustomAction( - context, - action, - )), - ].insertBetween( - Container( - height: 1, - color: streamChatThemeData.colorTheme.borders, ), ), ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart index a361f1ad..e37cd379 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/options/stream_gallery_picker.dart @@ -217,7 +217,7 @@ extension StreamImagePickerX on StreamAttachmentPickerController { final extraDataMap = {}; - final mimeType = file.mimeType?.mimeType; + final mimeType = file.mediaType?.mimeType; if (mimeType != null) { extraDataMap['mime_type'] = mimeType; diff --git a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart index 6c8f1f59..ada845c0 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart @@ -240,10 +240,10 @@ class WebOrDesktopAttachmentPickerOption extends AttachmentPickerOption { extension AttachmentPickerOptionTypeX on StreamAttachmentPickerController { /// Returns the list of available attachment picker options. Set get currentAttachmentPickerTypes { - final containsImage = value.any((it) => it.type == 'image'); - final containsVideo = value.any((it) => it.type == 'video'); - final containsAudio = value.any((it) => it.type == 'audio'); - final containsFile = value.any((it) => it.type == 'file'); + final containsImage = value.any((it) => it.type == AttachmentType.image); + final containsVideo = value.any((it) => it.type == AttachmentType.video); + final containsAudio = value.any((it) => it.type == AttachmentType.audio); + final containsFile = value.any((it) => it.type == AttachmentType.file); return { if (containsImage) AttachmentPickerType.images, 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 60238301..054d9cc4 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 @@ -1,10 +1,11 @@ -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/attachment/thumbnail/video_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/file_attachment_thumbnail.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/src/message_input/clear_input_item_button.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:video_player/video_player.dart'; + +typedef _Builders = Map; /// {@template streamQuotedMessage} /// Widget for the quoted message. @@ -40,8 +41,7 @@ class StreamQuotedMessageWidget extends StatelessWidget { final int textLimit; /// Map that defines a thumbnail builder for an attachment type - final Map? - attachmentThumbnailBuilders; + final _Builders? attachmentThumbnailBuilders; /// Padding around the widget final EdgeInsetsGeometry padding; @@ -109,19 +109,17 @@ class _QuotedMessage extends StatelessWidget { final bool reverse; final Widget Function(BuildContext, Message)? textBuilder; - /// Map that defines a thumbnail builder for an attachment type - final Map? - attachmentThumbnailBuilders; + final _Builders? attachmentThumbnailBuilders; bool get _hasAttachments => message.attachments.isNotEmpty; bool get _containsText => message.text?.isNotEmpty == true; bool get _containsLinkAttachment => - message.attachments.any((element) => element.titleLink != null); + message.attachments.any((it) => it.type == AttachmentType.urlPreview); - bool get _isGiphy => - message.attachments.any((element) => element.type == 'giphy'); + bool get _isGiphy => message.attachments + .any((element) => element.type == AttachmentType.giphy); bool get _isDeleted => message.isDeleted || message.deletedAt != null; @@ -150,14 +148,6 @@ class _QuotedMessage extends StatelessWidget { } else { // Show quoted message children = [ - if (onQuotedMessageClear != null) - PlatformWidgetBuilder( - web: (context, child) => child, - desktop: (context, child) => child, - child: ClearInputItemButton( - onTap: onQuotedMessageClear, - ), - ), if (_hasAttachments) _ParseAttachments( message: message, @@ -184,9 +174,26 @@ class _QuotedMessage extends StatelessWidget { ), ), ), - ].insertBetween(const SizedBox(width: 8)); + ]; } + // Add clear button if needed. + if (onQuotedMessageClear != null) { + children.insert( + 0, + PlatformWidgetBuilder( + web: (context, child) => child, + desktop: (context, child) => child, + child: ClearInputItemButton( + onTap: onQuotedMessageClear, + ), + ), + ); + } + + // Add some spacing between the children. + children = children.insertBetween(const SizedBox(width: 8)); + return Container( decoration: BoxDecoration( color: _getBackgroundColor(context), @@ -229,193 +236,106 @@ class _ParseAttachments extends StatelessWidget { final Message message; final StreamMessageThemeData messageTheme; - final Map? - attachmentThumbnailBuilders; - - bool get _containsLinkAttachment => - message.attachments.any((element) => element.titleLink != null); + final _Builders? attachmentThumbnailBuilders; @override Widget build(BuildContext context) { - Widget child; - Attachment attachment; - if (_containsLinkAttachment) { - attachment = message.attachments.firstWhere( - (element) => element.ogScrapeUrl != null || element.titleLink != null, + final attachment = message.attachments.first; + + var attachmentBuilders = attachmentThumbnailBuilders; + attachmentBuilders ??= _createDefaultAttachmentBuilders(); + + // Build the attachment widget using the builder for the attachment type. + final attachmentWidget = attachmentBuilders[attachment.type]?.call( + context, + attachment, + ); + + // Return empty container if no attachment widget is returned. + if (attachmentWidget == null) return const SizedBox.shrink(); + + final colorTheme = StreamChatTheme.of(context).colorTheme; + + var clipBehavior = Clip.none; + ShapeDecoration? decoration; + if (attachment.type != AttachmentType.file) { + clipBehavior = Clip.hardEdge; + decoration = ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide( + color: colorTheme.borders, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: BorderRadius.circular(8), + ), ); - child = _UrlAttachment(attachment: attachment); - } else { - QuotedMessageAttachmentThumbnailBuilder? attachmentBuilder; - attachment = message.attachments.last; - if (attachmentThumbnailBuilders?.containsKey(attachment.type) == true) { - attachmentBuilder = attachmentThumbnailBuilders![attachment.type]; - } - attachmentBuilder = _defaultAttachmentBuilder[attachment.type]; - if (attachmentBuilder == null) { - child = const Offstage(); - } else { - child = attachmentBuilder(context, attachment); - } } - final isImageFile = attachment.title?.mimeType?.type == 'image'; - final isVideoFile = attachment.title?.mimeType?.type == 'video'; + return Container( + key: Key(attachment.id), + clipBehavior: clipBehavior, + decoration: decoration, + constraints: const BoxConstraints.tightFor(width: 36, height: 36), + child: AbsorbPointer(child: attachmentWidget), + ); + } - return Material( - clipBehavior: Clip.hardEdge, - type: MaterialType.transparency, - shape: attachment.type == 'file' && (!isImageFile && !isVideoFile) - ? null - : RoundedRectangleBorder( - side: const BorderSide(width: 0, color: Colors.transparent), + _Builders _createDefaultAttachmentBuilders() { + Widget _createMediaThumbnail(BuildContext context, Attachment media) { + return StreamImageAttachmentThumbnail( + image: media, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ); + } + + Widget _createUrlThumbnail(BuildContext context, Attachment media) { + return StreamImageAttachmentThumbnail( + image: media, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ); + } + + Widget _createFileThumbnail(BuildContext context, Attachment file) { + Widget thumbnail = StreamFileAttachmentThumbnail( + file: file, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ); + + final mediaType = file.title?.mediaType; + final isImage = mediaType?.type == AttachmentType.image; + final isVideo = mediaType?.type == AttachmentType.video; + if (isImage || isVideo) { + final colorTheme = StreamChatTheme.of(context).colorTheme; + thumbnail = Container( + clipBehavior: Clip.hardEdge, + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide( + color: colorTheme.borders, + strokeAlign: BorderSide.strokeAlignOutside, + ), borderRadius: BorderRadius.circular(8), ), - child: AbsorbPointer(child: child), - ); - } - - Map - get _defaultAttachmentBuilder { - final builders = { - 'image': (_, attachment) { - return StreamImageAttachment( - message: message, - image: attachment, - constraints: BoxConstraints.loose(const Size(32, 32)), - ); - }, - 'video': (_, attachment) { - return StreamVideoAttachmentThumbnail( - key: ValueKey(attachment.assetUrl), - video: attachment, - width: 32, - height: 32, - // constraints: BoxConstraints.loose(const Size(32, 32)), - // errorBuilder: (_, __) => AttachmentError( - // constraints: BoxConstraints.loose(const Size(32, 32)), - // ), - ); - }, - 'giphy': (_, attachment) { - const size = Size(32, 32); - return CachedNetworkImage( - height: size.height, - width: size.width, - placeholder: (_, __) { - return SizedBox( - width: size.width, - height: size.height, - child: const Center( - child: CircularProgressIndicator.adaptive(), - ), - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl!, - errorWidget: (context, url, error) => - AttachmentError(constraints: BoxConstraints.loose(size)), - fit: BoxFit.cover, - ); - }, - }; - - builders['file'] = (_, attachment) { - return SizedBox( - height: 32, - width: 32, - child: Builder( - builder: (context) { - final isImageFile = attachment.title?.mimeType?.type == 'image'; - if (isImageFile) { - return builders['image']!(context, attachment); - } - - final isVideoFile = attachment.title?.mimeType?.type == 'video'; - if (isVideoFile) { - return builders['video']!(context, attachment); - } - - return getFileTypeImage( - attachment.extraData['mime_type'] as String?, - ); - }, - ), - ); - }; - - return builders; - } -} - -class _UrlAttachment extends StatelessWidget { - const _UrlAttachment({ - required this.attachment, - }); - - final Attachment attachment; - - @override - Widget build(BuildContext context) { - const size = Size(32, 32); - if (attachment.thumbUrl != null) { - return Container( - height: size.height, - width: size.width, - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: CachedNetworkImageProvider( - attachment.thumbUrl!, - ), ), - ), - ); + child: thumbnail, + ); + } + + return thumbnail; } - return AttachmentError(constraints: BoxConstraints.loose(size)); - } -} -class _VideoAttachmentThumbnail extends StatefulWidget { - const _VideoAttachmentThumbnail({ - required this.attachment, - }); - - final Attachment attachment; - - @override - _VideoAttachmentThumbnailState createState() => - _VideoAttachmentThumbnailState(); -} - -class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { - late VideoPlayerController _controller; - - @override - void initState() { - super.initState(); - _controller = VideoPlayerController.networkUrl( - Uri.parse(widget.attachment.assetUrl!), - )..initialize().then((_) { - // ignore: no-empty-block - setState(() {}); //when your thumbnail will show. - }); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return SizedBox( - height: 32, - width: 32, - child: _controller.value.isInitialized - ? VideoPlayer(_controller) - : const CircularProgressIndicator.adaptive(), - ); + return { + AttachmentType.image: _createMediaThumbnail, + AttachmentType.giphy: _createMediaThumbnail, + AttachmentType.video: _createMediaThumbnail, + AttachmentType.urlPreview: _createUrlThumbnail, + AttachmentType.file: _createFileThumbnail, + }; } } 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 8bdbfd99..8ed8e2bb 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 @@ -1169,7 +1169,7 @@ class StreamMessageInputState extends State } final containsUrl = quotedMessage.attachments.any((it) { - return it.titleLink != null; + return it.type == AttachmentType.urlPreview; }); return StreamQuotedMessageWidget( @@ -1316,8 +1316,6 @@ class StreamMessageInputState extends State message = message.copyWith(text: '/${message.command} ${message.text}'); } - final skipEnrichUrl = _effectiveController.ogAttachment == null; - var shouldKeepFocus = widget.shouldKeepFocusAfterMessage; shouldKeepFocus ??= !_commandEnabled; @@ -1341,10 +1339,7 @@ class StreamMessageInputState extends State await WidgetsBinding.instance.endOfFrame; } - await _sendOrUpdateMessage( - message: message, - skipEnrichUrl: skipEnrichUrl, - ); + await _sendOrUpdateMessage(message: message); if (mounted) { if (shouldKeepFocus) { @@ -1357,36 +1352,29 @@ class StreamMessageInputState extends State Future _sendOrUpdateMessage({ required Message message, - bool skipEnrichUrl = false, }) async { final channel = StreamChannel.of(context).channel; try { Future sendingFuture; if (_isEditing) { - sendingFuture = channel.updateMessage( - message, - skipEnrichUrl: skipEnrichUrl, - ); + sendingFuture = channel.updateMessage(message); } else { - sendingFuture = channel.sendMessage( - message, - skipEnrichUrl: skipEnrichUrl, - ); + sendingFuture = channel.sendMessage(message); } final resp = await sendingFuture; - if (resp.message?.type == 'error') { + if (resp.message?.isError ?? false) { _effectiveController.message = message; } _startSlowMode(); widget.onMessageSent?.call(resp.message); } catch (e, stk) { if (widget.onError != null) { - widget.onError?.call(e, stk); - } else { - rethrow; + return widget.onError?.call(e, stk); } + + rethrow; } } 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 index cfb9e25c..56dabbfe 100644 --- 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 @@ -1,14 +1,11 @@ -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/attachment/file_attachment.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/media_attachment_thumbnail.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'; -import '../attachment/thumbnail/video_attachment_thumbnail.dart'; - /// WidgetBuilder used to build the message input attachment list. /// /// see more: @@ -93,7 +90,7 @@ class _StreamMessageInputAttachmentListState // Split the attachments into file and media attachments. for (final attachment in widget.attachments) { - if (attachment.type == 'file') { + if (attachment.type == AttachmentType.file) { fileAttachments.add(attachment); } else { mediaAttachments.add(attachment); @@ -123,7 +120,7 @@ class _StreamMessageInputAttachmentListState } return SingleChildScrollView( - padding: const EdgeInsets.only(top: 8), + padding: const EdgeInsets.only(top: 6), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -203,23 +200,19 @@ class MessageInputFileAttachments extends StatelessWidget { } // Otherwise, use the default builder. - return ClipRRect( - key: Key(attachment.id), - borderRadius: BorderRadius.circular(10), - child: StreamFileAttachment( - message: Message(), // dummy message - file: attachment, - constraints: BoxConstraints.loose(Size( - MediaQuery.of(context).size.width * 0.65, - 56, - )), - trailing: Padding( - padding: const EdgeInsets.all(8), - child: RemoveAttachmentButton( - onPressed: onRemovePressed != null - ? () => onRemovePressed!(attachment) - : null, - ), + return StreamFileAttachment( + message: Message(), // Dummy message + file: attachment, + constraints: BoxConstraints.loose(Size( + MediaQuery.of(context).size.width * 0.65, + 56, + )), + trailing: Padding( + padding: const EdgeInsets.all(8), + child: RemoveAttachmentButton( + onPressed: onRemovePressed != null + ? () => onRemovePressed!(attachment) + : null, ), ), ); @@ -258,7 +251,8 @@ class MessageInputMediaAttachments extends StatelessWidget { height: 104, child: ListView( scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 8), + cacheExtent: 104 * 10, // Cache 10 items ahead. children: attachments.map( (attachment) { // If a custom builder is provided, use it. @@ -267,27 +261,47 @@ class MessageInputMediaAttachments extends StatelessWidget { return builder(context, attachment, onRemovePressed); } - return ClipRRect( + final colorTheme = StreamChatTheme.of(context).colorTheme; + final shape = RoundedRectangleBorder( + side: BorderSide( + color: colorTheme.borders, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: BorderRadius.circular(14), + ); + + return Container( key: Key(attachment.id), - borderRadius: BorderRadius.circular(10), - child: Stack( - children: [ - AspectRatio( - aspectRatio: 1, - child: MessageInputMediaAttachmentThumbnail( - attachment: attachment, + clipBehavior: Clip.hardEdge, + decoration: ShapeDecoration(shape: shape), + child: AspectRatio( + aspectRatio: 1, + child: Stack( + alignment: Alignment.center, + children: [ + StreamMediaAttachmentThumbnail( + media: attachment, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, ), - ), - Positioned( - top: 8, - right: 8, - child: RemoveAttachmentButton( - onPressed: onRemovePressed != null - ? () => onRemovePressed!(attachment) - : null, + if (attachment.type == AttachmentType.video) + Positioned( + left: 8, + bottom: 8, + child: StreamSvgIcon.videoCall(), + ), + Positioned( + top: 8, + right: 8, + child: RemoveAttachmentButton( + onPressed: onRemovePressed != null + ? () => onRemovePressed!(attachment) + : null, + ), ), - ), - ], + ], + ), ), ); }, @@ -297,61 +311,6 @@ class MessageInputMediaAttachments extends StatelessWidget { } } -/// 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: [ - StreamVideoAttachmentThumbnail(video: attachment), - 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. diff --git a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart index ee85693a..995ec161 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart @@ -104,6 +104,7 @@ class StreamMessageListView extends StatefulWidget { this.loadingBuilder, this.emptyBuilder, this.systemMessageBuilder, + this.ephemeralMessageBuilder, this.messageListBuilder, this.errorBuilder, this.messageFilter, @@ -149,6 +150,9 @@ class StreamMessageListView extends StatefulWidget { /// {@macro systemMessageBuilder} final SystemMessageBuilder? systemMessageBuilder; + /// {@macro ephemeralMessageBuilder} + final EphemeralMessageBuilder? ephemeralMessageBuilder; + /// {@macro parentMessageBuilder} final ParentMessageBuilder? parentMessageBuilder; @@ -915,13 +919,19 @@ class _StreamMessageListViewState extends State { final currentUserMember = members.firstWhereOrNull((e) => e.user!.id == currentUser!.id); + final hasFileAttachment = + message.attachments.any((it) => it.type == AttachmentType.file); + final hasUrlAttachment = - message.attachments.any((it) => it.ogScrapeUrl != null); + message.attachments.any((it) => it.type == AttachmentType.urlPreview); - final isEphemeral = message.isEphemeral; + final attachmentBorderRadius = hasUrlAttachment + ? 8.0 + : hasFileAttachment + ? 12.0 + : 14.0; - final borderSide = - isOnlyEmoji || hasUrlAttachment || isEphemeral ? BorderSide.none : null; + final borderSide = isOnlyEmoji ? BorderSide.none : null; final defaultMessageWidget = StreamMessageWidget( showReplyMessage: false, @@ -935,13 +945,34 @@ class _StreamMessageListViewState extends State { showUsername: !isMyMessage, padding: const EdgeInsets.all(8), showSendingIndicator: false, + attachmentPadding: EdgeInsets.all( + hasUrlAttachment + ? 8 + : hasFileAttachment + ? 4 + : 2, + ), + attachmentShape: RoundedRectangleBorder( + side: BorderSide( + color: _streamTheme.colorTheme.borders, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(attachmentBorderRadius), + bottomLeft: isMyMessage + ? Radius.circular(attachmentBorderRadius) + : Radius.zero, + topRight: Radius.circular(attachmentBorderRadius), + bottomRight: isMyMessage + ? Radius.zero + : Radius.circular(attachmentBorderRadius), + ), + ), borderRadiusGeometry: BorderRadius.only( topLeft: const Radius.circular(16), - bottomLeft: - isMyMessage ? const Radius.circular(16) : const Radius.circular(2), + bottomLeft: isMyMessage ? const Radius.circular(16) : Radius.zero, topRight: const Radius.circular(16), - bottomRight: - isMyMessage ? const Radius.circular(2) : const Radius.circular(16), + bottomRight: isMyMessage ? Radius.zero : const Radius.circular(16), ), textPadding: EdgeInsets.symmetric( vertical: 8, @@ -1061,14 +1092,8 @@ class _StreamMessageListViewState extends State { } if (message.isEphemeral) { - // return widget.ephemeralMessageBuilder?.call(context, message) ?? - return StreamEphemeralMessage( - message: message, - // onMessageTap: (message) { - // widget.onEphemeralMessageTap?.call(message); - // FocusScope.of(context).unfocus(); - // }, - ); + return widget.ephemeralMessageBuilder?.call(context, message) ?? + StreamEphemeralMessage(message: message); } final userId = StreamChat.of(context).currentUser!.id; @@ -1088,14 +1113,21 @@ class _StreamMessageListViewState extends State { } final hasFileAttachment = - message.attachments.any((it) => it.type == 'file'); + message.attachments.any((it) => it.type == AttachmentType.file); + + final hasUrlAttachment = + message.attachments.any((it) => it.type == AttachmentType.urlPreview); final isThreadMessage = message.parentId != null && message.showInChannel == true; final hasReplies = message.replyCount! > 0; - final attachmentBorderRadius = hasFileAttachment ? 12.0 : 14.0; + final attachmentBorderRadius = hasUrlAttachment + ? 8.0 + : hasFileAttachment + ? 12.0 + : 14.0; final showTimeStamp = (!isThreadMessage || _isThreadConversation) && !hasReplies && @@ -1119,13 +1151,7 @@ class _StreamMessageListViewState extends State { final showThreadReplyIndicator = !_isThreadConversation && hasReplies; final isOnlyEmoji = message.text?.isOnlyEmoji ?? false; - final isEphemeral = message.isEphemeral; - - final hasUrlAttachment = - message.attachments.any((it) => it.ogScrapeUrl != null); - - final borderSide = - isOnlyEmoji || hasUrlAttachment || isEphemeral ? BorderSide.none : null; + final borderSide = isOnlyEmoji ? BorderSide.none : null; final currentUser = StreamChat.of(context).currentUser; final members = StreamChannel.of(context).channel.state?.members ?? []; @@ -1170,27 +1196,39 @@ class _StreamMessageListViewState extends State { showFlagButton: !isMyMessage, borderSide: borderSide, onThreadTap: _onThreadTap, - attachmentBorderRadiusGeometry: BorderRadius.only( - topLeft: Radius.circular(attachmentBorderRadius), - bottomLeft: isMyMessage - ? Radius.circular(attachmentBorderRadius) - : Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage || hasFileAttachment) - ? 0 - : attachmentBorderRadius, - ), - topRight: Radius.circular(attachmentBorderRadius), - bottomRight: isMyMessage - ? Radius.circular( - (hasTimeDiff || !isNextUserSame) && - !(hasReplies || isThreadMessage || hasFileAttachment) - ? 0 - : attachmentBorderRadius, - ) - : Radius.circular(attachmentBorderRadius), + attachmentShape: RoundedRectangleBorder( + side: BorderSide( + color: _streamTheme.colorTheme.borders, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(attachmentBorderRadius), + bottomLeft: isMyMessage + ? Radius.circular(attachmentBorderRadius) + : Radius.circular( + (hasTimeDiff || !isNextUserSame) && + !(hasReplies || isThreadMessage || hasFileAttachment) + ? 0 + : attachmentBorderRadius, + ), + topRight: Radius.circular(attachmentBorderRadius), + bottomRight: isMyMessage + ? Radius.circular( + (hasTimeDiff || !isNextUserSame) && + !(hasReplies || isThreadMessage || hasFileAttachment) + ? 0 + : attachmentBorderRadius, + ) + : Radius.circular(attachmentBorderRadius), + ), + ), + attachmentPadding: EdgeInsets.all( + hasUrlAttachment + ? 8 + : hasFileAttachment + ? 4 + : 2, ), - attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2), borderRadiusGeometry: BorderRadius.only( topLeft: const Radius.circular(16), bottomLeft: isMyMessage diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart index 24da1ac3..455da220 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_card.dart @@ -22,6 +22,7 @@ class MessageCard extends StatefulWidget { required this.isGiphy, required this.attachmentBuilders, required this.attachmentPadding, + required this.attachmentShape, required this.onAttachmentTap, required this.onShowMessage, required this.onReplyTap, @@ -80,6 +81,9 @@ class MessageCard extends StatefulWidget { /// {@macro attachmentPadding} final EdgeInsetsGeometry attachmentPadding; + /// {@macro attachmentShape} + final ShapeBorder? attachmentShape; + /// {@macro onAttachmentTap} final StreamAttachmentWidgetTapCallback? onAttachmentTap; @@ -200,6 +204,7 @@ class _MessageCardState extends State { message: widget.message, attachmentBuilders: widget.attachmentBuilders, attachmentPadding: widget.attachmentPadding, + attachmentShape: widget.attachmentShape, onAttachmentTap: widget.onAttachmentTap, onShowMessage: widget.onShowMessage, onReplyTap: widget.onReplyTap, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 74835bd6..16a1980e 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -49,11 +49,9 @@ class StreamMessageWidget extends StatefulWidget { this.reverse = false, this.translateUserAvatar = true, this.shape, - this.attachmentShape, this.borderSide, - this.attachmentBorderSide, this.borderRadiusGeometry, - this.attachmentBorderRadiusGeometry, + this.attachmentShape, this.onMentionTap, this.onMessageTap, this.showReactionPicker = true, @@ -334,21 +332,11 @@ class StreamMessageWidget extends StatefulWidget { /// {@endtemplate} final BorderSide? borderSide; - /// {@template attachmentBorderSide} - /// The borderSide of an attachment - /// {@endtemplate} - final BorderSide? attachmentBorderSide; - /// {@template borderRadiusGeometry} /// The border radius of the message text /// {@endtemplate} final BorderRadiusGeometry? borderRadiusGeometry; - /// {@template attachmentBorderRadiusGeometry} - /// The border radius of an attachment - /// {@endtemplate} - final BorderRadiusGeometry? attachmentBorderRadiusGeometry; - /// {@template padding} /// The padding of the widget /// {@endtemplate} @@ -542,9 +530,7 @@ class StreamMessageWidget extends StatefulWidget { ShapeBorder? shape, ShapeBorder? attachmentShape, BorderSide? borderSide, - BorderSide? attachmentBorderSide, BorderRadiusGeometry? borderRadiusGeometry, - BorderRadiusGeometry? attachmentBorderRadiusGeometry, EdgeInsetsGeometry? padding, EdgeInsets? textPadding, EdgeInsetsGeometry? attachmentPadding, @@ -603,10 +589,7 @@ class StreamMessageWidget extends StatefulWidget { shape: shape ?? this.shape, attachmentShape: attachmentShape ?? this.attachmentShape, borderSide: borderSide ?? this.borderSide, - attachmentBorderSide: attachmentBorderSide ?? this.attachmentBorderSide, borderRadiusGeometry: borderRadiusGeometry ?? this.borderRadiusGeometry, - attachmentBorderRadiusGeometry: - attachmentBorderRadiusGeometry ?? this.attachmentBorderRadiusGeometry, padding: padding ?? this.padding, textPadding: textPadding ?? this.textPadding, attachmentPadding: attachmentPadding ?? this.attachmentPadding, @@ -691,8 +674,8 @@ class _StreamMessageWidgetState extends State /// {@template isGiphy} /// `true` if any of the [message]'s attachments are a giphy. /// {@endtemplate} - bool get isGiphy => - widget.message.attachments.any((element) => element.type == 'giphy'); + bool get isGiphy => widget.message.attachments + .any((element) => element.type == AttachmentType.giphy); /// {@template isOnlyEmoji} /// `true` if [message.text] contains only emoji. @@ -749,7 +732,8 @@ class _StreamMessageWidgetState extends State bool get shouldShowEditAction => widget.showEditMessage && !isDeleteFailed && - !widget.message.attachments.any((element) => element.type == 'giphy'); + !widget.message.attachments + .any((element) => element.type == AttachmentType.giphy); bool get shouldShowResendAction => widget.showResendMessage && (isSendFailed || isUpdateFailed); @@ -762,7 +746,8 @@ class _StreamMessageWidgetState extends State bool get shouldShowEditMessage => widget.showEditMessage && !isDeleteFailed && - !widget.message.attachments.any((element) => element.type == 'giphy'); + !widget.message.attachments + .any((element) => element.type == AttachmentType.giphy); bool get shouldShowThreadReplyAction => widget.showThreadReplyMessage && @@ -853,6 +838,7 @@ class _StreamMessageWidgetState extends State textPadding: widget.textPadding, attachmentBuilders: widget.attachmentBuilders, attachmentPadding: widget.attachmentPadding, + attachmentShape: widget.attachmentShape, onAttachmentTap: widget.onAttachmentTap, onReplyTap: widget.onReplyTap, onShowMessage: widget.onShowMessage, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index e84f91f1..59c99732 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -48,6 +48,7 @@ class MessageWidgetContent extends StatelessWidget { required this.isGiphy, required this.attachmentBuilders, required this.attachmentPadding, + required this.attachmentShape, required this.onAttachmentTap, required this.onShowMessage, required this.onReplyTap, @@ -150,6 +151,9 @@ class MessageWidgetContent extends StatelessWidget { /// {@macro attachmentPadding} final EdgeInsetsGeometry attachmentPadding; + /// {@macro attachmentShape} + final ShapeBorder? attachmentShape; + /// {@macro onAttachmentTap} final StreamAttachmentWidgetTapCallback? onAttachmentTap; @@ -341,6 +345,7 @@ class MessageWidgetContent extends StatelessWidget { isGiphy: isGiphy, attachmentBuilders: attachmentBuilders, attachmentPadding: attachmentPadding, + attachmentShape: attachmentShape, onAttachmentTap: onAttachmentTap, onReplyTap: onReplyTap, onShowMessage: onShowMessage, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart index 3477a751..57b849ed 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/parse_attachments.dart @@ -16,6 +16,7 @@ class ParseAttachments extends StatelessWidget { required this.message, required this.attachmentBuilders, required this.attachmentPadding, + this.attachmentShape, this.onAttachmentTap, this.onShowMessage, this.onReplyTap, @@ -31,6 +32,9 @@ class ParseAttachments extends StatelessWidget { /// {@macro attachmentPadding} final EdgeInsetsGeometry attachmentPadding; + /// {@macro attachmentShape} + final ShapeBorder? attachmentShape; + /// {@macro onAttachmentTap} final StreamAttachmentWidgetTapCallback? onAttachmentTap; @@ -104,6 +108,8 @@ class ParseAttachments extends StatelessWidget { var builders = attachmentBuilders; builders ??= StreamAttachmentWidgetBuilder.defaultBuilders( message: message, + shape: attachmentShape, + padding: attachmentPadding, onAttachmentTap: onAttachmentTap, ); diff --git a/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart index 8dadc47d..072ee5be 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart @@ -50,45 +50,47 @@ class StreamMessageReactionsModal extends StatelessWidget { final child = Center( child: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (showReactionPicker && hasReactionPermission) - LayoutBuilder( - builder: (context, constraints) { - return Align( - alignment: Alignment( - calculateReactionsHorizontalAlignment( - user, - message, - constraints, - fontSize, - orientation, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (showReactionPicker && hasReactionPermission) + LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment( + calculateReactionsHorizontalAlignment( + user, + message, + constraints, + fontSize, + orientation, + ), + 0, ), - 0, - ), - child: StreamReactionPicker( - message: message, - ), - ); - }, - ), - const SizedBox(height: 10), - IgnorePointer( - child: messageWidget, - ), - if (message.latestReactions?.isNotEmpty == true) ...[ - const SizedBox(height: 8), - ReactionsCard( - currentUser: user!, - message: message, - messageTheme: messageTheme, + child: StreamReactionPicker( + message: message, + ), + ); + }, + ), + const SizedBox(height: 10), + IgnorePointer( + child: messageWidget, ), + if (message.latestReactions?.isNotEmpty == true) ...[ + const SizedBox(height: 8), + ReactionsCard( + currentUser: user!, + message: message, + messageTheme: messageTheme, + ), + ], ], - ], + ), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart b/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart index 2fa017ed..af5260a2 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/text_bubble.dart @@ -51,7 +51,7 @@ class TextBubble extends StatelessWidget { @override Widget build(BuildContext context) { - if (message.text?.trim().isEmpty ?? false) return const Offstage(); + if (message.text?.trim().isEmpty ?? true) return const Offstage(); return Padding( padding: isOnlyEmoji ? EdgeInsets.zero : textPadding, child: textBuilder != null diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart index b1b165ba..36b676f4 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/channel_scroll_view/stream_channel_list_tile.dart @@ -228,8 +228,7 @@ class StreamChannelListTile extends StatelessWidget { } final hasNonUrlAttachments = lastMessage.attachments - .where((it) => it.titleLink == null || it.type == 'giphy') - .isNotEmpty; + .any((it) => it.type != AttachmentType.urlPreview); return Padding( padding: const EdgeInsets.only(right: 4), diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index e7449343..c24948b3 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -201,6 +201,7 @@ class StreamChatThemeData { urlAttachmentTitleStyle: textTheme.footnoteBold, urlAttachmentTextStyle: textTheme.footnote, urlAttachmentTitleMaxLine: 1, + urlAttachmentTextMaxLine: 3, ), otherMessageTheme: StreamMessageThemeData( reactionsBackgroundColor: colorTheme.borders, @@ -227,6 +228,7 @@ class StreamChatThemeData { urlAttachmentTitleStyle: textTheme.footnoteBold, urlAttachmentTextStyle: textTheme.footnote, urlAttachmentTitleMaxLine: 1, + urlAttachmentTextMaxLine: 3, ), messageInputTheme: StreamMessageInputThemeData( borderRadius: BorderRadius.circular(20), diff --git a/packages/stream_chat_flutter/lib/src/utils/extensions.dart b/packages/stream_chat_flutter/lib/src/utils/extensions.dart index e8358306..b1ef9a2c 100644 --- a/packages/stream_chat_flutter/lib/src/utils/extensions.dart +++ b/packages/stream_chat_flutter/lib/src/utils/extensions.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:math'; import 'package:diacritic/diacritic.dart'; @@ -5,6 +6,8 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:image_size_getter/file_input.dart'; // For compatibility with flutter web. +import 'package:image_size_getter/image_size_getter.dart' hide Size; import 'package:stream_chat_flutter/src/localization/translations.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -114,7 +117,7 @@ extension PlatformFileX on PlatformFile { final file = toAttachmentFile; final extraDataMap = {}; - final mimeType = file.mimeType?.mimeType; + final mimeType = file.mediaType?.mimeType; if (mimeType != null) { extraDataMap['mime_type'] = mimeType; @@ -151,7 +154,7 @@ extension XFileX on XFile { final extraDataMap = {}; - final mimeType = this.mimeType ?? file.mimeType?.mimeType; + final mimeType = this.mimeType ?? file.mediaType?.mimeType; if (mimeType != null) { extraDataMap['mime_type'] = mimeType; @@ -367,7 +370,7 @@ extension MessageX on Message { /// Returns an approximation of message size double roughMessageSize(double? fontSize) { - var messageTextLength = min(text!.biggestLine().length, 65); + var messageTextLength = min(text?.biggestLine().length ?? 0, 65); if (quotedMessage != null) { var quotedMessageLength = @@ -488,3 +491,46 @@ extension ConstraintsX on BoxConstraints { ); } } + +/// Useful extensions on [Attachment]. +extension OriginalSizeX on Attachment { + /// Returns the size of the attachment if it is an image or giffy. + /// Otherwise, returns null. + Size? get originalSize { + // Return null if the attachment is not an image or giffy. + if (type != AttachmentType.image && type != AttachmentType.giphy) { + return null; + } + + // Calculate size locally if the attachment is not uploaded yet. + final file = this.file; + if (file != null) { + ImageInput? input; + if (file.bytes != null) { + input = MemoryInput(file.bytes!); + } else if (file.path != null) { + input = FileInput(File(file.path!)); + } + + // Return null if the file does not contain enough information. + if (input == null) return null; + + try { + final size = ImageSizeGetter.getSize(input); + if (size.needRotate) { + return Size(size.height.toDouble(), size.width.toDouble()); + } + return Size(size.width.toDouble(), size.height.toDouble()); + } catch (e, stk) { + debugPrint('Error getting image size: $e\n$stk'); + return null; + } + } + + // Otherwise, use the size provided by the server. + final width = originalWidth; + final height = originalHeight; + if (width == null || height == null) return null; + return Size(width.toDouble(), height.toDouble()); + } +} diff --git a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart index 2a504b2d..12e25468 100644 --- a/packages/stream_chat_flutter/lib/src/utils/typedefs.dart +++ b/packages/stream_chat_flutter/lib/src/utils/typedefs.dart @@ -259,6 +259,14 @@ typedef SystemMessageBuilder = Widget Function( Message, ); +/// {@template ephemeralMessageBuilder} +/// A widget builder for creating custom ephemeral messages. +/// {@endtemplate} +typedef EphemeralMessageBuilder = Widget Function( + BuildContext, + Message, +); + /// {@template threadBuilder} /// A widget builder for creating custom thread UI. /// {@endtemplate} diff --git a/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart b/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart index 11ff945e..69327452 100644 --- a/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart +++ b/packages/stream_chat_flutter/lib/src/video/video_thumbnail_image.dart @@ -142,6 +142,8 @@ class StreamVideoThumbnailImage int get hashCode => Object.hash(video, scale); @override - String toString() => - '${objectRuntimeType(this, 'StreamVideoThumbnailImage')}($video, scale: $scale)'; + String toString() { + final runtimeType = objectRuntimeType(this, 'StreamVideoThumbnailImage'); + return '$runtimeType($video, scale: $scale)'; + } } diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 3b793463..6b3937be 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -7,9 +7,9 @@ export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; export 'src/attachment/attachment.dart'; export 'src/attachment/attachment_title.dart'; +export 'src/attachment/gallery_attachment.dart'; export 'src/attachment/handler/stream_attachment_handler.dart'; export 'src/attachment/image_attachment.dart'; -export 'src/attachment/gallery_attachment.dart'; export 'src/attachment/stream_attachment_package.dart'; export 'src/attachment/url_attachment.dart'; export 'src/attachment/video_attachment.dart'; diff --git a/packages/stream_chat_flutter/test/src/attachment/image_group_test.dart b/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart similarity index 52% rename from packages/stream_chat_flutter/test/src/attachment/image_group_test.dart rename to packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart index c8c8d264..5a1270c0 100644 --- a/packages/stream_chat_flutter/test/src/attachment/image_group_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/gallery_attachment_test.dart @@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat_flutter/src/attachment/thumbnail/image_attachment_thumbnail.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../mocks.dart'; @@ -18,6 +19,27 @@ void main() { final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); + final attachments = [ + Attachment( + type: 'image', + title: 'example.png', + imageUrl: + 'https://logowik.com/content/uploads/images/flutter5786.jpg', + extraData: const { + 'mime_type': 'png', + }, + ), + Attachment( + type: 'image', + title: 'example.png', + imageUrl: + 'https://logowik.com/content/uploads/images/flutter5786.jpg', + extraData: const { + 'mime_type': 'png', + }, + ), + ]; + await tester.pumpWidget( MaterialApp( home: StreamChatTheme( @@ -31,26 +53,17 @@ void main() { 300, )), message: Message(), - attachments: [ - Attachment( - type: 'image', - title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', - extraData: const { - 'mime_type': 'png', - }, - ), - Attachment( - type: 'image', - title: 'example.png', - imageUrl: - 'https://logowik.com/content/uploads/images/flutter5786.jpg', - extraData: const { - 'mime_type': 'png', - }, - ), - ], + attachments: attachments, + itemBuilder: (context, index) { + final attachment = attachments[index]; + + return StreamImageAttachmentThumbnail( + image: attachment, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + ); + }, ), ), ), diff --git a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart index 2203de7c..755686c7 100644 --- a/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/giphy_attachment_test.dart @@ -30,7 +30,7 @@ void main() { 300, )), message: Message(), - file: Attachment( + giphy: Attachment( type: 'giphy', title: 'example.gif', imageUrl: diff --git a/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart index 37a5c56f..03ec5fa8 100644 --- a/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/image_attachment_test.dart @@ -31,7 +31,7 @@ void main() { 300, )), message: Message(), - file: Attachment( + image: Attachment( type: 'image', title: 'example.png', imageUrl: diff --git a/packages/stream_chat_flutter/test/src/attachment/url_attachment_test.dart b/packages/stream_chat_flutter/test/src/attachment/url_attachment_test.dart index b1636fd4..cf12e07e 100644 --- a/packages/stream_chat_flutter/test/src/attachment/url_attachment_test.dart +++ b/packages/stream_chat_flutter/test/src/attachment/url_attachment_test.dart @@ -26,6 +26,7 @@ void main() { child: SizedBox( child: StreamUrlAttachment( messageTheme: streamTheme.ownMessageTheme, + message: Message(), hostDisplayName: 'Test', urlAttachment: Attachment( title: 'Flutter', diff --git a/packages/stream_chat_flutter/test/src/goldens/attachment_button_0.png b/packages/stream_chat_flutter/test/src/goldens/attachment_button_0.png index 2294ea84..096c345d 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/attachment_button_0.png and b/packages/stream_chat_flutter/test/src/goldens/attachment_button_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/attachment_modal_sheet_0.png b/packages/stream_chat_flutter/test/src/goldens/attachment_modal_sheet_0.png index 8c4b011c..c4704cde 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/attachment_modal_sheet_0.png and b/packages/stream_chat_flutter/test/src/goldens/attachment_modal_sheet_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/clear_input_item_0.png b/packages/stream_chat_flutter/test/src/goldens/clear_input_item_0.png index a6503af8..53e98a8b 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/clear_input_item_0.png and b/packages/stream_chat_flutter/test/src/goldens/clear_input_item_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/command_button_0.png b/packages/stream_chat_flutter/test/src/goldens/command_button_0.png index c294b279..0674402d 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/command_button_0.png and b/packages/stream_chat_flutter/test/src/goldens/command_button_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/confirmation_dialog_0.png b/packages/stream_chat_flutter/test/src/goldens/confirmation_dialog_0.png index 9a2676ad..55b0c8c9 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/confirmation_dialog_0.png and b/packages/stream_chat_flutter/test/src/goldens/confirmation_dialog_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/countdown_button_0.png b/packages/stream_chat_flutter/test/src/goldens/countdown_button_0.png index e18ed82d..8623c9c3 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/countdown_button_0.png and b/packages/stream_chat_flutter/test/src/goldens/countdown_button_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/delete_message_dialog_0.png b/packages/stream_chat_flutter/test/src/goldens/delete_message_dialog_0.png index 9e7f6a73..e144913d 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/delete_message_dialog_0.png and b/packages/stream_chat_flutter/test/src/goldens/delete_message_dialog_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png b/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png index 36f80c62..5bb1cc01 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png and b/packages/stream_chat_flutter/test/src/goldens/deleted_message_custom.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_0.png b/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_0.png index 39535158..8e84f2e9 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_0.png and b/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_1.png b/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_1.png index f07b2a9d..51f7c436 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_1.png and b/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_1.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_2.png b/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_2.png index a5b1eda7..5d411f66 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_2.png and b/packages/stream_chat_flutter/test/src/goldens/dm_checkbox_2.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/download_menu_item_0.png b/packages/stream_chat_flutter/test/src/goldens/download_menu_item_0.png index 2b593918..b1fbcad8 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/download_menu_item_0.png and b/packages/stream_chat_flutter/test/src/goldens/download_menu_item_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/edit_message_sheet_0.png b/packages/stream_chat_flutter/test/src/goldens/edit_message_sheet_0.png index babe1ef3..2cdcf5e6 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/edit_message_sheet_0.png and b/packages/stream_chat_flutter/test/src/goldens/edit_message_sheet_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/error_alert_sheet_0.png b/packages/stream_chat_flutter/test/src/goldens/error_alert_sheet_0.png index f484ee2a..4c163773 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/error_alert_sheet_0.png and b/packages/stream_chat_flutter/test/src/goldens/error_alert_sheet_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/gallery_footer_0.png b/packages/stream_chat_flutter/test/src/goldens/gallery_footer_0.png index 8f21a6ca..203d7842 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/gallery_footer_0.png and b/packages/stream_chat_flutter/test/src/goldens/gallery_footer_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/gallery_header_0.png b/packages/stream_chat_flutter/test/src/goldens/gallery_header_0.png index 3c8e020f..eaac9b11 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/gallery_header_0.png and b/packages/stream_chat_flutter/test/src/goldens/gallery_header_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png index bf15f6af..5b153d86 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png index bd466f79..4d3ab3b6 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_1.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_2.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_2.png index d7d671c4..30fe9dc9 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_2.png and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_2.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_3.png b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_3.png index 5381c0f0..6b3f5cdb 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_3.png and b/packages/stream_chat_flutter/test/src/goldens/gradient_avatar_3.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/group_avatar_0.png b/packages/stream_chat_flutter/test/src/goldens/group_avatar_0.png index bb14fc09..5eb45d37 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/group_avatar_0.png and b/packages/stream_chat_flutter/test/src/goldens/group_avatar_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/message_dialog_0.png b/packages/stream_chat_flutter/test/src/goldens/message_dialog_0.png index 1a7faf9c..68abd020 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/message_dialog_0.png and b/packages/stream_chat_flutter/test/src/goldens/message_dialog_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/message_dialog_1.png b/packages/stream_chat_flutter/test/src/goldens/message_dialog_1.png index ea7f428c..b24758f9 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/message_dialog_1.png and b/packages/stream_chat_flutter/test/src/goldens/message_dialog_1.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/message_dialog_2.png b/packages/stream_chat_flutter/test/src/goldens/message_dialog_2.png index 75313e05..4e31bd43 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/message_dialog_2.png and b/packages/stream_chat_flutter/test/src/goldens/message_dialog_2.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png index 9a4aac36..aeb97727 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_2.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png index 1694dcb6..45d6fd59 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png index d67e928d..3b9e6366 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_3_light.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_dark.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_dark.png index 86a7ae2c..af735d6a 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_dark.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_dark.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_light.png b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_light.png index 86cdacac..7187ab2d 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_light.png and b/packages/stream_chat_flutter/test/src/goldens/reaction_bubble_like_light.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/sending_indicator_0.png b/packages/stream_chat_flutter/test/src/goldens/sending_indicator_0.png index 27eb2714..58d1f890 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/sending_indicator_0.png and b/packages/stream_chat_flutter/test/src/goldens/sending_indicator_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/sending_indicator_1.png b/packages/stream_chat_flutter/test/src/goldens/sending_indicator_1.png index dc823015..4c92e52e 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/sending_indicator_1.png and b/packages/stream_chat_flutter/test/src/goldens/sending_indicator_1.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/sending_indicator_2.png b/packages/stream_chat_flutter/test/src/goldens/sending_indicator_2.png index dc823015..4c92e52e 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/sending_indicator_2.png and b/packages/stream_chat_flutter/test/src/goldens/sending_indicator_2.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/stream_chat_context_menu_item_0.png b/packages/stream_chat_flutter/test/src/goldens/stream_chat_context_menu_item_0.png index 9b6e8ed0..558b8753 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/stream_chat_context_menu_item_0.png and b/packages/stream_chat_flutter/test/src/goldens/stream_chat_context_menu_item_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_0.png b/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_0.png index 9b5f1769..b0730449 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_0.png and b/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_1.png b/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_1.png index 1d936b36..3e86d820 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_1.png and b/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_1.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_2.png b/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_2.png index 8bb24319..c4cfff20 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_2.png and b/packages/stream_chat_flutter/test/src/goldens/upload_progress_indicator_2.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/user_avatar_0.png b/packages/stream_chat_flutter/test/src/goldens/user_avatar_0.png index a155ccc4..0224053b 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/user_avatar_0.png and b/packages/stream_chat_flutter/test/src/goldens/user_avatar_0.png differ diff --git a/packages/stream_chat_flutter/test/src/goldens/user_avatar_1.png b/packages/stream_chat_flutter/test/src/goldens/user_avatar_1.png index 189e3432..9546c827 100644 Binary files a/packages/stream_chat_flutter/test/src/goldens/user_avatar_1.png and b/packages/stream_chat_flutter/test/src/goldens/user_avatar_1.png differ diff --git a/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart index 1b5499dd..576e1a8a 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_header_theme_test.dart @@ -70,7 +70,7 @@ final _channelThemeControlMidLerp = StreamChannelHeaderThemeData( width: 40, ), ), - color: const Color(0xff101418), + color: const Color(0xff111417), titleStyle: const TextStyle( color: Color(0xffffffff), fontWeight: FontWeight.bold, diff --git a/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart index 14c09706..1dd0340e 100644 --- a/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/channel_list_header_theme_test.dart @@ -73,7 +73,7 @@ final _channelListHeaderThemeControlMidLerp = StreamChannelListHeaderThemeData( width: 40, ), ), - color: const Color(0xff87898b), + color: const Color(0xff88898a), titleStyle: const TextStyle( color: Color(0xff7f7f7f), fontSize: 16, diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart index f8d1d164..c66d41f1 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart @@ -73,11 +73,7 @@ void main() { home: Builder( builder: (context) { _context = context; - return Scaffold( - appBar: StreamGalleryFooter( - mediaAttachmentPackages: Message().getAttachmentPackageList(), - ), - ); + return const SizedBox.shrink(); }, ), ), @@ -116,11 +112,7 @@ void main() { home: Builder( builder: (context) { _context = context; - return Scaffold( - appBar: StreamGalleryFooter( - mediaAttachmentPackages: Message().getAttachmentPackageList(), - ), - ); + return const SizedBox.shrink(); }, ), ), @@ -160,7 +152,7 @@ final _galleryFooterThemeDataControl = StreamGalleryFooterThemeData( // Mid-lerp theme control const _galleryFooterThemeDataControlMidLerp = StreamGalleryFooterThemeData( - backgroundColor: Color(0xff87898b), + backgroundColor: Color(0xff88898a), shareIconColor: Color(0xff7f7f7f), titleTextStyle: TextStyle( color: Color(0xff7f7f7f), @@ -169,7 +161,7 @@ const _galleryFooterThemeDataControlMidLerp = StreamGalleryFooterThemeData( ), gridIconButtonColor: Color(0xff7f7f7f), bottomSheetBarrierColor: Color(0x4c000000), - bottomSheetBackgroundColor: Color(0xff87898b), + bottomSheetBackgroundColor: Color(0xff88898a), bottomSheetPhotosTextStyle: TextStyle( color: Color(0xff7f7f7f), fontSize: 16, diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart index 651da1ce..27f98cb3 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart @@ -66,22 +66,7 @@ void main() { home: Builder( builder: (context) { _context = context; - final attachment = Attachment( - type: 'video', - title: 'video.mp4', - ); - final _message = Message( - createdAt: DateTime.now(), - attachments: [ - attachment, - ], - ); - return Scaffold( - appBar: StreamGalleryHeader( - message: _message, - attachment: _message.attachments[0], - ), - ); + return const SizedBox.shrink(); }, ), ), @@ -116,22 +101,7 @@ void main() { home: Builder( builder: (context) { _context = context; - final attachment = Attachment( - type: 'video', - title: 'video.mp4', - ); - final _message = Message( - createdAt: DateTime.now(), - attachments: [ - attachment, - ], - ); - return Scaffold( - appBar: StreamGalleryHeader( - message: _message, - attachment: _message.attachments[0], - ), - ); + return const SizedBox.shrink(); }, ), ), @@ -175,7 +145,7 @@ final _galleryHeaderThemeDataControl = StreamGalleryHeaderThemeData( // Light theme test control. final _galleryHeaderThemeDataHalfLerpControl = StreamGalleryHeaderThemeData( closeButtonColor: const Color(0xff7f7f7f), - backgroundColor: const Color(0xff87898b), + backgroundColor: const Color(0xff88898a), iconMenuPointColor: const Color(0xff7f7f7f), titleTextStyle: const TextStyle( fontSize: 16, @@ -194,7 +164,7 @@ final _galleryHeaderThemeDataHalfLerpControl = StreamGalleryHeaderThemeData( // Dark theme test control. final _galleryHeaderThemeDataDarkControl = StreamGalleryHeaderThemeData( closeButtonColor: const Color(0xffffffff), - backgroundColor: const Color(0xff101418), + backgroundColor: const Color(0xff121416), iconMenuPointColor: const Color(0xffffffff), titleTextStyle: const TextStyle( fontSize: 16, diff --git a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart index f267d388..80912e9a 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_input_theme_test.dart @@ -68,7 +68,7 @@ final _messageInputThemeControl = StreamMessageInputThemeData( final _messageInputThemeControlMidLerp = StreamMessageInputThemeData( borderRadius: BorderRadius.circular(20), sendAnimationDuration: const Duration(milliseconds: 300), - inputBackgroundColor: const Color(0xff87898b), + inputBackgroundColor: const Color(0xff88898a), actionButtonColor: const Color(0xff196eff), actionButtonIdleColor: const Color(0xff7a7a7a), sendButtonColor: const Color(0xff196eff), diff --git a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart index 264710ec..79fb6dae 100644 --- a/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/message_list_view_theme_test.dart @@ -68,12 +68,7 @@ void main() { home: Builder( builder: (BuildContext context) { _context = context; - return Scaffold( - body: StreamChannel( - channel: MockChannel(), - child: const StreamMessageListView(), - ), - ); + return const SizedBox.shrink(); }, ), ), @@ -98,12 +93,7 @@ void main() { home: Builder( builder: (BuildContext context) { _context = context; - return Scaffold( - body: StreamChannel( - channel: MockChannel(), - child: const StreamMessageListView(), - ), - ); + return const SizedBox.shrink(); }, ), ), @@ -151,7 +141,7 @@ final _messageListViewThemeDataControl = StreamMessageListViewThemeData( ); const _messageListViewThemeDataControlHalfLerp = StreamMessageListViewThemeData( - backgroundColor: Color(0xff87898b), + backgroundColor: Color(0xff88898a), ); final _messageListViewThemeDataControlDark = StreamMessageListViewThemeData(