diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 2e1b875f..981c2986 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,5 +1,9 @@ ## Upcoming +🐞 Fixed + +- [[#1592]](https://github.com/GetStream/stream-chat-flutter/issues/1592) Fixed broken attachment download on web. + 🔄 Changed - Updated `dio` dependency to `^5.2.0`. 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 fb4f192a..96d5e1ed 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/common.dart @@ -4,9 +4,41 @@ import 'package:dio/dio.dart'; import 'package:file_selector/file_selector.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -/// Downloads the [attachment] to the device and returns -/// the path to the file. -Future downloadWebOrDesktopAttachment( +/// Represents the url and bytes of an attachment. +class AttachmentData { + /// Creates a new [AttachmentData] instance. + const AttachmentData({ + required this.bytes, + required this.downloadUrl, + required this.fileName, + this.mimeType, + }); + + /// The data downloaded from the [downloadUrl]. + final Uint8List bytes; + + /// The url of the attachment that was used to download the [bytes]. + final String downloadUrl; + + /// The name of the file to use when saving the [bytes]. + final String fileName; + + /// The mime type of the attachment. + final String? mimeType; + + /// Creates an [XFile] from the [AttachmentData]. + XFile toXFile({String? path}) { + return XFile.fromData( + bytes, + mimeType: mimeType, + name: fileName, + path: path, + ); + } +} + +/// Downloads the [attachment] and returns the [AttachmentData]. +Future downloadAttachmentData( Attachment attachment, { ProgressCallback? onReceiveProgress, Map? queryParameters, @@ -34,13 +66,14 @@ Future downloadWebOrDesktopAttachment( fileName = attachment.title; } - assert( - downloadUrl != null, - 'Attachment must have an assetUrl or imageUrl or thumbUrl', - ); + if (downloadUrl == null) { + throw ArgumentError( + 'Attachment must have an assetUrl or imageUrl or thumbUrl', + ); + } final response = await Dio().get>( - downloadUrl!, + downloadUrl, onReceiveProgress: onReceiveProgress, queryParameters: queryParameters, cancelToken: cancelToken, @@ -49,23 +82,12 @@ Future downloadWebOrDesktopAttachment( Options(responseType: ResponseType.bytes), ); - // Open the native file browser so the user can select the download path. - final path = await getSavePath(suggestedName: fileName); + final bytes = Uint8List.fromList(response.data!); - if (path == null) { - // Operation was canceled by the user. - return null; - } - - // Create an XFile for proper file saving - final file = XFile.fromData( - Uint8List.fromList(response.data!), + return AttachmentData( + bytes: bytes, + downloadUrl: downloadUrl, + fileName: fileName!, mimeType: attachment.mimeType, - name: fileName, - path: path, ); - - // Save the file to the user's selected path. - await file.saveTo(path); - return path; } diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart index 2d3a6fba..b3d4b121 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_html.dart @@ -50,13 +50,21 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { Map? queryParameters, CancelToken? cancelToken, Options? options, - }) { - return downloadWebOrDesktopAttachment( + }) async { + final data = await downloadAttachmentData( attachment, onReceiveProgress: onReceiveProgress, queryParameters: queryParameters, cancelToken: cancelToken, options: options, ); + + // Create an XFile for proper file saving. + final file = data.toXFile(); + + // Save the file. We are not using the path parameter because it is not + // supported on web. + await file.saveTo(''); + return null; } } diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart index 268dbf15..0b04ee61 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart @@ -1,8 +1,7 @@ import 'dart:io'; -import 'dart:typed_data'; -import 'package:dio/dio.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:file_selector/file_selector.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:stream_chat_flutter/src/attachment/handler/common.dart'; @@ -21,14 +20,29 @@ class StreamAttachmentHandlerDesktop extends StreamAttachmentHandler { Map? queryParameters, CancelToken? cancelToken, Options? options, - }) { - return downloadWebOrDesktopAttachment( + }) async { + final data = await downloadAttachmentData( attachment, onReceiveProgress: onReceiveProgress, queryParameters: queryParameters, cancelToken: cancelToken, options: options, ); + + // Open the native file browser so the user can select the download path. + final path = await getSavePath(suggestedName: data.fileName); + + if (path == null) { + // Operation was canceled by the user. + return null; + } + + // Create an XFile for proper file saving. + final file = data.toXFile(path: path); + + // Save the file to the user's selected path. + await file.saveTo(path); + return path; } } @@ -160,53 +174,20 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase { CancelToken? cancelToken, Options? options, }) async { - final type = attachment.type; - - String? downloadUrl; - String? fileName; - /* ---IMAGES/GIFS--- */ - if (type == 'image') { - downloadUrl = attachment.imageUrl ?? attachment.assetUrl; - fileName = attachment.title; - fileName ??= 'attachment.${attachment.mimeType ?? 'png'}'; - } - /* ---GIPHY's--- */ - else if (type == 'giphy') { - downloadUrl = attachment.thumbUrl; - fileName = '${attachment.title}.gif'; - } - /* ---FILES AND VIDEOS--- */ - else if (type == 'file' || type == 'video') { - downloadUrl = attachment.assetUrl; - fileName = attachment.title; - } - - assert( - downloadUrl != null, - 'Attachment must have an assetUrl or imageUrl or thumbUrl', - ); - - final response = await Dio().get>( - downloadUrl!, + final data = await downloadAttachmentData( + attachment, onReceiveProgress: onReceiveProgress, queryParameters: queryParameters, cancelToken: cancelToken, - // set responseType to `bytes` - options: options?.copyWith(responseType: ResponseType.bytes) ?? - Options(responseType: ResponseType.bytes), + options: options, ); final appDir = await getTemporaryDirectory(); - final ext = Uri.parse(downloadUrl).pathSegments.last; + final ext = Uri.parse(data.downloadUrl).pathSegments.last; final path = '${appDir.path}/${attachment.id}.$ext'; - // Create an XFile for proper file saving - final file = XFile.fromData( - Uint8List.fromList(response.data!), - mimeType: attachment.mimeType, - name: fileName, - path: path, - ); + // Create an XFile for proper file saving. + final file = data.toXFile(path: path); // Save the file to the user's selected path. await file.saveTo(path);