Merge pull request #1595 from GetStream/fix/attachment-download-web
This commit is contained in:
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-43
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user