diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 98aa9782..9c925f88 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -15,6 +15,9 @@ - Added `Filter.contains` and `Filter.empty` - Added support for `next`, `previous` value pagination in `client.search` , [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination) +- `Attachment` class now has a `fileSize` and `mimeType` property. Setting a `file` will also set the `file_size` + , `mime_type` key on `extraData`, so `attachment.fileSize`, `attachment.mimetype` and `attachment.extraData['file_size']` + , `attachment.extraData['mime_type]` is same respectively. 🐞 Fixed diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index 8973aee2..0ef9db7a 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -33,13 +33,20 @@ class Attachment extends Equatable { this.authorIcon, this.assetUrl, List? actions, - this.extraData = const {}, + Map extraData = const {}, this.file, UploadState? uploadState, }) : id = id ?? const Uuid().v4(), title = title ?? file?.name, localUri = file?.path != null ? Uri.parse(file!.path!) : null, - actions = actions ?? [] { + actions = actions ?? [], + // 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) ? const UploadState.success() @@ -121,6 +128,18 @@ class Attachment extends Equatable { /// This is created locally for uniquely identifying a attachment. final String id; + /// Shortcut for file size. + /// + /// {@macro fileSize} + @JsonKey(ignore: true) + int? get fileSize => extraData['file_size'] as int?; + + /// Shortcut for file mimeType. + /// + /// {@macro mimeType} + @JsonKey(ignore: true) + String? get mimeType => extraData['mime_type'] as String?; + /// Known top level fields. /// Useful for [Serializer] methods. static const topLevelFields = [ 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 7bff5806..c6983b74 100644 --- a/packages/stream_chat/lib/src/core/models/attachment_file.dart +++ b/packages/stream_chat/lib/src/core/models/attachment_file.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:dio/dio.dart' show MultipartFile; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:http_parser/http_parser.dart'; import 'package:meta/meta.dart'; import 'package:stream_chat/src/core/platform_detector/platform_detector.dart'; import 'package:stream_chat/src/core/util/extension.dart'; @@ -65,7 +66,7 @@ class AttachmentFile { AttachmentFile({ required this.size, this.path, - this.name, + String? name, this.bytes, }) : assert( path != null || bytes != null, @@ -74,7 +75,12 @@ class AttachmentFile { assert( !CurrentPlatform.isWeb || bytes != null, 'File by path is not supported in web, Please provide bytes', - ); + ), + assert( + name?.contains('.') ?? true, + 'Invalid file name, should also contain file extension', + ), + _name = name; /// Create a new instance from a json factory AttachmentFile.fromJson(Map json) => @@ -87,8 +93,10 @@ class AttachmentFile { /// ``` final String? path; + final String? _name; + /// File name including its extension. - final String? name; + String? get name => _name ?? path?.split('/').last; /// Byte data for this file. Particularly useful if you want to manipulate /// its data or easily upload to somewhere else. @@ -101,26 +109,26 @@ class AttachmentFile { /// File extension for this file. String? get extension => name?.split('.').last; + /// The mime type of this file. + MediaType? get mimeType => name?.mimeType; + /// Serialize to json Map toJson() => _$AttachmentFileToJson(this); /// Converts this into a [MultipartFile] Future toMultipartFile() async { - final filename = path?.split('/').last ?? name; - final mimeType = filename?.mimeType; - - late MultipartFile multiPartFile; + MultipartFile multiPartFile; if (CurrentPlatform.isWeb) { multiPartFile = MultipartFile.fromBytes( bytes!, - filename: filename, + filename: name, contentType: mimeType, ); } else { multiPartFile = await MultipartFile.fromFile( path!, - filename: filename, + filename: name, contentType: mimeType, ); } diff --git a/packages/stream_chat/test/src/core/models/attachment_test.dart b/packages/stream_chat/test/src/core/models/attachment_test.dart index 37716656..4e08f7f2 100644 --- a/packages/stream_chat/test/src/core/models/attachment_test.dart +++ b/packages/stream_chat/test/src/core/models/attachment_test.dart @@ -1,5 +1,6 @@ import 'package:stream_chat/src/core/models/action.dart'; import 'package:stream_chat/src/core/models/attachment.dart'; +import 'package:stream_chat/src/core/models/attachment_file.dart'; import 'package:test/test.dart'; import '../../utils.dart'; @@ -41,5 +42,59 @@ void main() { }, ); }); + + test('fileName, mimeType property and extraData manipulation', () { + final file = AttachmentFile(size: 3, path: 'myfolder/myfile.txt'); + final attachment = Attachment(file: file); + + expect(attachment.fileSize, 3); + expect(attachment.mimeType, 'text/plain'); + expect(attachment.toJson(), { + 'title': 'myfile.txt', + 'actions': [], + 'file_size': 3, + 'mime_type': 'text/plain' + }); + expect(Attachment.fromJson(attachment.toJson()).toJson(), { + 'title': 'myfile.txt', + 'actions': [], + 'file_size': 3, + 'mime_type': 'text/plain' + }); + + // Setting the size and mimeType using extraData should work fine + var newAttachment = Attachment( + extraData: const { + 'file_size': 6, + 'mime_type': 'application/pdf', + }, + ); + + expect(newAttachment.extraData['file_size'], 6); + expect(newAttachment.extraData['mime_type'], 'application/pdf'); + expect(newAttachment.fileSize, 6); + expect(newAttachment.mimeType, 'application/pdf'); + + // switching a new file should update size and mimeType + final fileTwo = AttachmentFile(size: 12, path: 'myfolder/fileTwo.pdf'); + newAttachment = attachment.copyWith(file: fileTwo); + + expect(newAttachment.extraData['file_size'], 12); + expect(newAttachment.extraData['mime_type'], 'application/pdf'); + expect(newAttachment.fileSize, 12); + expect(newAttachment.mimeType, 'application/pdf'); + + // if file is available, should override size and mimeType. + final fileThree = AttachmentFile(size: 9, path: 'myfolder/fileThree.png'); + newAttachment = attachment.copyWith(file: fileThree, extraData: { + 'file_size': 88, + 'mime_type': 'application/pdf', + }); + + expect(newAttachment.extraData['file_size'], 9); + expect(newAttachment.extraData['mime_type'], 'image/png'); + expect(newAttachment.fileSize, 9); + expect(newAttachment.mimeType, 'image/png'); + }); }); }