Merge pull request #1595 from GetStream/fix/attachment-download-web

This commit is contained in:
Sahil Kumar
2023-06-07 15:20:07 +05:30
committed by GitHub
4 changed files with 84 additions and 69 deletions
@@ -1,5 +1,9 @@
## Upcoming ## Upcoming
🐞 Fixed
- [[#1592]](https://github.com/GetStream/stream-chat-flutter/issues/1592) Fixed broken attachment download on web.
🔄 Changed 🔄 Changed
- Updated `dio` dependency to `^5.2.0`. - Updated `dio` dependency to `^5.2.0`.
@@ -4,9 +4,41 @@ import 'package:dio/dio.dart';
import 'package:file_selector/file_selector.dart'; import 'package:file_selector/file_selector.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Downloads the [attachment] to the device and returns /// Represents the url and bytes of an attachment.
/// the path to the file. class AttachmentData {
Future<String?> downloadWebOrDesktopAttachment( /// 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<AttachmentData> downloadAttachmentData(
Attachment attachment, { Attachment attachment, {
ProgressCallback? onReceiveProgress, ProgressCallback? onReceiveProgress,
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
@@ -34,13 +66,14 @@ Future<String?> downloadWebOrDesktopAttachment(
fileName = attachment.title; fileName = attachment.title;
} }
assert( if (downloadUrl == null) {
downloadUrl != null, throw ArgumentError(
'Attachment must have an assetUrl or imageUrl or thumbUrl', 'Attachment must have an assetUrl or imageUrl or thumbUrl',
); );
}
final response = await Dio().get<List<int>>( final response = await Dio().get<List<int>>(
downloadUrl!, downloadUrl,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
@@ -49,23 +82,12 @@ Future<String?> downloadWebOrDesktopAttachment(
Options(responseType: ResponseType.bytes), Options(responseType: ResponseType.bytes),
); );
// Open the native file browser so the user can select the download path. final bytes = Uint8List.fromList(response.data!);
final path = await getSavePath(suggestedName: fileName);
if (path == null) { return AttachmentData(
// Operation was canceled by the user. bytes: bytes,
return null; downloadUrl: downloadUrl,
} fileName: fileName!,
// Create an XFile for proper file saving
final file = XFile.fromData(
Uint8List.fromList(response.data!),
mimeType: attachment.mimeType, mimeType: attachment.mimeType,
name: fileName,
path: path,
); );
// Save the file to the user's selected path.
await file.saveTo(path);
return path;
} }
@@ -50,13 +50,21 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase {
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
CancelToken? cancelToken, CancelToken? cancelToken,
Options? options, Options? options,
}) { }) async {
return downloadWebOrDesktopAttachment( final data = await downloadAttachmentData(
attachment, attachment,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
options: options, 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;
} }
} }
@@ -1,8 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:file_selector/file_selector.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:stream_chat_flutter/src/attachment/handler/common.dart'; import 'package:stream_chat_flutter/src/attachment/handler/common.dart';
@@ -21,14 +20,29 @@ class StreamAttachmentHandlerDesktop extends StreamAttachmentHandler {
Map<String, dynamic>? queryParameters, Map<String, dynamic>? queryParameters,
CancelToken? cancelToken, CancelToken? cancelToken,
Options? options, Options? options,
}) { }) async {
return downloadWebOrDesktopAttachment( final data = await downloadAttachmentData(
attachment, attachment,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
options: options, 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, CancelToken? cancelToken,
Options? options, Options? options,
}) async { }) async {
final type = attachment.type; final data = await downloadAttachmentData(
attachment,
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<List<int>>(
downloadUrl!,
onReceiveProgress: onReceiveProgress, onReceiveProgress: onReceiveProgress,
queryParameters: queryParameters, queryParameters: queryParameters,
cancelToken: cancelToken, cancelToken: cancelToken,
// set responseType to `bytes` options: options,
options: options?.copyWith(responseType: ResponseType.bytes) ??
Options(responseType: ResponseType.bytes),
); );
final appDir = await getTemporaryDirectory(); 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'; final path = '${appDir.path}/${attachment.id}.$ext';
// Create an XFile for proper file saving // Create an XFile for proper file saving.
final file = XFile.fromData( final file = data.toXFile(path: path);
Uint8List.fromList(response.data!),
mimeType: attachment.mimeType,
name: fileName,
path: path,
);
// Save the file to the user's selected path. // Save the file to the user's selected path.
await file.saveTo(path); await file.saveTo(path);