From b29f673e913f3b76ad75f4199d689fc1f9dc2209 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 17 Feb 2021 20:14:01 +0530 Subject: [PATCH] [LLC] Refactor attachment_file_uploader usage in client and channel Signed-off-by: Sahil Kumar --- packages/stream_chat/lib/src/api/channel.dart | 21 ++-- ...der.dart => attachment_file_uploader.dart} | 67 ++++++------- packages/stream_chat/lib/src/client.dart | 29 +++--- packages/stream_chat/lib/stream_chat.dart | 2 +- .../test/src/api/channel_test.dart | 96 +++++++++++++++---- 5 files changed, 144 insertions(+), 71 deletions(-) rename packages/stream_chat/lib/src/{api/attachment_uploader.dart => attachment_file_uploader.dart} (51%) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 24b08b3f..b63273af 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -245,15 +245,20 @@ class Channel { } final isImage = it.type == 'image'; - final uploader = _client.attachmentUploader; final cancelToken = CancelToken(); Future future; if (isImage) { - future = uploader.uploadImage(it.file, id, type, - onSendProgress: onSendProgress, cancelToken: cancelToken); + future = sendImage( + it.file, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + ).then((it) => it.file); } else { - future = uploader.uploadFile(it.file, id, type, - onSendProgress: onSendProgress, cancelToken: cancelToken); + future = sendFile( + it.file, + onSendProgress: onSendProgress, + cancelToken: cancelToken, + ).then((it) => it.file); } _cancelableAttachmentUploadRequest[it.id] = cancelToken; return future.then((url) { @@ -446,7 +451,7 @@ class Channel { /// Send a file to this channel Future sendFile( - MultipartFile file, { + AttachmentFile file, { ProgressCallback onSendProgress, CancelToken cancelToken, }) { @@ -461,12 +466,12 @@ class Channel { /// Send an image to this channel Future sendImage( - MultipartFile image, { + AttachmentFile file, { ProgressCallback onSendProgress, CancelToken cancelToken, }) { return _client.sendImage( - image, + file, id, type, onSendProgress: onSendProgress, diff --git a/packages/stream_chat/lib/src/api/attachment_uploader.dart b/packages/stream_chat/lib/src/attachment_file_uploader.dart similarity index 51% rename from packages/stream_chat/lib/src/api/attachment_uploader.dart rename to packages/stream_chat/lib/src/attachment_file_uploader.dart index a5d2d576..8db8b168 100644 --- a/packages/stream_chat/lib/src/api/attachment_uploader.dart +++ b/packages/stream_chat/lib/src/attachment_file_uploader.dart @@ -1,17 +1,18 @@ import 'package:dio/dio.dart'; +import 'package:stream_chat/src/api/responses.dart'; import 'package:stream_chat/src/models/attachment_file.dart'; -import '../client.dart'; -import '../extensions/string_extension.dart'; +import 'client.dart'; +import 'extensions/string_extension.dart'; -/// Class responsible for uploading images and files from a given channel -abstract class AttachmentUploader { - /// Uploads a image [file] to the given channel. - /// Returns image [file] URL once sent successfully. +/// Class responsible for uploading images and files to a given channel +abstract class AttachmentFileUploader { + /// Uploads a [image] to the given channel. + /// Returns [SendImageResponse] once sent successfully. /// /// Optionally, access upload progress using [onSendProgress] /// and cancel the request using [cancelToken] - Future uploadImage( - AttachmentFile file, + Future sendImage( + AttachmentFile image, String channelId, String channelType, { ProgressCallback onSendProgress, @@ -19,11 +20,11 @@ abstract class AttachmentUploader { }); /// Uploads a [file] to the given channel. - /// Returns [file] URL once sent successfully. + /// Returns [SendFileResponse] once sent successfully. /// /// Optionally, access upload progress using [onSendProgress] /// and cancel the request using [cancelToken] - Future uploadFile( + Future sendFile( AttachmentFile file, String channelId, String channelType, { @@ -32,15 +33,15 @@ abstract class AttachmentUploader { }); } -/// Stream's default implementation of [AttachmentUploader] -class StreamAttachmentUploader implements AttachmentUploader { +/// Stream's default implementation of [AttachmentFileUploader] +class StreamAttachmentUploader implements AttachmentFileUploader { final StreamChatClient _client; /// Creates a new [StreamAttachmentUploader] instance. const StreamAttachmentUploader(this._client); @override - Future uploadImage( + Future sendImage( AttachmentFile file, String channelId, String channelType, { @@ -49,22 +50,23 @@ class StreamAttachmentUploader implements AttachmentUploader { }) async { final filename = file.path?.split('/')?.last; final mimeType = filename.mimeType; - final res = await _client.sendImage( - await MultipartFile.fromFile( - file.path, - filename: filename, - contentType: mimeType, - ), - channelId, - channelType, + final response = await _client.post( + '/channels/$channelType/$channelId/image', + data: FormData.fromMap({ + 'file': await MultipartFile.fromFile( + file.path, + filename: filename, + contentType: mimeType, + ), + }), onSendProgress: onSendProgress, cancelToken: cancelToken, ); - return res.file; + return _client.decode(response.data, SendImageResponse.fromJson); } @override - Future uploadFile( + Future sendFile( AttachmentFile file, String channelId, String channelType, { @@ -73,17 +75,18 @@ class StreamAttachmentUploader implements AttachmentUploader { }) async { final filename = file.path?.split('/')?.last; final mimeType = filename.mimeType; - final res = await _client.sendFile( - await MultipartFile.fromFile( - file.path, - filename: filename, - contentType: mimeType, - ), - channelId, - channelType, + final response = await _client.post( + '/channels/$channelType/$channelId/file', + data: FormData.fromMap({ + 'file': await MultipartFile.fromFile( + file.path, + filename: filename, + contentType: mimeType, + ), + }), onSendProgress: onSendProgress, cancelToken: cancelToken, ); - return res.file; + return _client.decode(response.data, SendFileResponse.fromJson); } } diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 5118d6b8..6a513151 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -8,11 +8,12 @@ import 'package:pedantic/pedantic.dart' show unawaited; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/api/retry_policy.dart'; import 'package:stream_chat/src/event_type.dart'; +import 'package:stream_chat/src/models/attachment_file.dart'; import 'package:stream_chat/src/models/own_user.dart'; import 'package:stream_chat/version.dart'; import 'package:uuid/uuid.dart'; -import 'api/attachment_uploader.dart'; +import 'attachment_file_uploader.dart'; import 'api/channel.dart'; import 'api/connection_status.dart'; import 'api/requests.dart'; @@ -102,7 +103,7 @@ class StreamChatClient { ChatPersistenceClient chatPersistenceClient; /// Attachment uploader - AttachmentUploader attachmentUploader; + AttachmentFileUploader attachmentUploader; /// Whether the chat persistence is available or not bool get persistenceEnabled => chatPersistenceClient != null; @@ -1043,36 +1044,36 @@ class StreamChatClient { /// Send a [file] to the [channelId] of type [channelType] Future sendFile( - MultipartFile file, + AttachmentFile file, String channelId, String channelType, { ProgressCallback onSendProgress, CancelToken cancelToken, - }) async { - final response = await post( - '/channels/$channelType/$channelId/file', - data: FormData.fromMap({'file': file}), + }) { + return attachmentUploader.sendFile( + file, + channelId, + channelType, onSendProgress: onSendProgress, cancelToken: cancelToken, ); - return decode(response.data, SendFileResponse.fromJson); } /// Send a [image] to the [channelId] of type [channelType] Future sendImage( - MultipartFile image, + AttachmentFile image, String channelId, String channelType, { ProgressCallback onSendProgress, CancelToken cancelToken, - }) async { - final response = await post( - '/channels/$channelType/$channelId/image', - data: FormData.fromMap({'file': image}), + }) { + return attachmentUploader.sendImage( + image, + channelId, + channelType, onSendProgress: onSendProgress, cancelToken: cancelToken, ); - return decode(response.data, SendImageResponse.fromJson); } /// Add a device for Push Notifications. diff --git a/packages/stream_chat/lib/stream_chat.dart b/packages/stream_chat/lib/stream_chat.dart index dd09e8e8..f0b3e60d 100644 --- a/packages/stream_chat/lib/stream_chat.dart +++ b/packages/stream_chat/lib/stream_chat.dart @@ -10,7 +10,7 @@ export './src/api/connection_status.dart'; export './src/api/requests.dart'; export './src/api/requests.dart'; export './src/api/responses.dart'; -export './src/api/attachment_uploader.dart' show AttachmentUploader; +export './src/attachment_file_uploader.dart' show AttachmentFileUploader; export './src/client.dart'; export './src/event_type.dart'; export './src/models/action.dart'; diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index 32ac65c2..def7f417 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -10,8 +10,12 @@ import 'package:stream_chat/src/models/reaction.dart'; import 'package:stream_chat/src/models/own_user.dart'; import 'package:test/test.dart'; +import 'package:stream_chat/stream_chat.dart'; + class MockDio extends Mock implements DioForNative {} +class MockAttachmentUploader extends Mock implements AttachmentFileUploader {} + class MockHttpClientAdapter extends Mock implements HttpClientAdapter {} void main() { @@ -44,6 +48,61 @@ void main() { mockDio.post('/channels/messaging/testid/message', data: { 'message': message.toJson(), })).called(1); + + // final imageFile = MultipartFile.fromString('value'); + // + // channelClient + // .sendImage(imageFile) + // .then((response) { + // final imageUrl = response.file; + // final attachment = Attachment( + // type = "image", + // imageUrl = imageUrl, + // ) + // val message = Message( + // attachments = mutableListOf(attachment), + // ) + // channelClient + // .sendMessage(message) + // .enqueue { + // /* ... */ + // } + // }) + // .catchError(onError); + + //final channelClient = client.channel("messaging", id:'general'); + // + // // Upload an image without detailed progress + // channelClient.sendImage(imageFile).enqueue { result-> + // if (result.isSuccess) { + // // Successful upload, you can now attach this image + // // to an message that you then send to a channel + // val imageUrl = result.data() + // val attachment = Attachment( + // type = "image", + // imageUrl = imageUrl, + // ) + // val message = Message( + // attachments = mutableListOf(attachment), + // ) + // channelClient.sendMessage(message).enqueue { /* ... */ } + // } + // } + // + // // Upload a file, monitoring for progress with a ProgressCallback + // channelClient.sendFile(anyOtherFile, object : ProgressCallback { + // override fun onSuccess(file: String) { + // val fileUrl = file + // } + // + // override fun onError(error: ChatError) { + // // Handle error + // } + // + // override fun onProgress(progress: Long) { + // // You can render the uploading progress here + // } + // }).enqueue() // No callback passed to enqueue, as we'll get notified above anyway }); test('markRead', () async { @@ -170,6 +229,11 @@ void main() { test('sendFile', () async { final mockDio = MockDio(); + final mockUploader = MockAttachmentUploader(); + + final file = AttachmentFile(path: 'filePath/fileName.pdf'); + final channelId = 'testId'; + final channelType = 'messaging'; when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); @@ -178,23 +242,25 @@ void main() { 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', + attachmentUploader: mockUploader, ); - final channelClient = client.channel('messaging', id: 'testid'); - final file = MultipartFile.fromString('file'); + final channelClient = client.channel(channelType, id: channelId); - when(mockDio.post('/channels/messaging/testid/file', - data: argThat(isA(), named: 'data'))) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(mockUploader.sendFile(file, channelId, channelType)) + .thenAnswer((_) async => SendFileResponse()); await channelClient.sendFile(file); - verify(mockDio.post('/channels/messaging/testid/file', - data: argThat(isA(), named: 'data'))) - .called(1); + verify(mockUploader.sendFile(file, channelId, channelType)).called(1); }); test('sendImage', () async { final mockDio = MockDio(); + final mockUploader = MockAttachmentUploader(); + + final image = AttachmentFile(path: 'imagePath/imageName.jpeg'); + final channelId = 'testId'; + final channelType = 'messaging'; when(mockDio.options).thenReturn(BaseOptions()); when(mockDio.interceptors).thenReturn(Interceptors()); @@ -203,18 +269,16 @@ void main() { 'api-key', httpClient: mockDio, tokenProvider: (_) async => '', + attachmentUploader: mockUploader, ); - final channelClient = client.channel('messaging', id: 'testid'); - final file = MultipartFile.fromString('file'); + final channelClient = client.channel(channelType, id: channelId); - when(mockDio.post('/channels/messaging/testid/image', - data: argThat(isA(), named: 'data'))) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(mockUploader.sendImage(image, channelId, channelType)) + .thenAnswer((_) async => SendImageResponse()); - await channelClient.sendImage(file); + await channelClient.sendImage(image); - verify(mockDio.post('/channels/messaging/testid/image', - data: argThat(isA(), named: 'data'))) + verify(mockUploader.sendImage(image, channelId, channelType)) .called(1); });