diff --git a/example/lib/main.dart b/example/lib/main.dart index 0e6a5f4..88ef145 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,4 @@ +// ignore: avoid_web_libraries_in_flutter import 'dart:html' as html; import 'package:flutter/material.dart'; @@ -106,9 +107,10 @@ class _UploadExampleState extends State { if (pickedFile != null && pickedThumbnail != null) { _largeFileUploader.upload( uploadUrl: url, + name: '1', headers: {"Authorization": "Bearer $accessToken"}, data: {"title": "Sample Title", "thumbnail": pickedThumbnail, "file": pickedFile}, - onSendProgress: (progress) => debugPrint(progress.toString()), + onSendProgress: (progress, id) => debugPrint('$id: $progress'), onComplete: (response) => debugPrint(response.toString()), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index 1d7fbb1..73ed609 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" boolean_selector: dependency: transitive description: @@ -21,7 +21,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -42,7 +42,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" crypto: dependency: transitive description: @@ -70,7 +70,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" flutter: dependency: "direct main" description: flutter @@ -101,7 +101,7 @@ packages: path: ".." relative: true source: path - version: "0.0.3" + version: "0.0.7" lints: dependency: transitive description: @@ -115,7 +115,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" meta: dependency: transitive description: @@ -129,7 +136,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" sky_engine: dependency: transitive description: flutter @@ -141,7 +148,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" stack_trace: dependency: transitive description: @@ -176,7 +183,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.9" typed_data: dependency: transitive description: @@ -204,7 +211,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=1.20.0" diff --git a/example/web/upload_worker.js b/example/web/upload_worker.js deleted file mode 100644 index eb35bdb..0000000 --- a/example/web/upload_worker.js +++ /dev/null @@ -1,53 +0,0 @@ -self.addEventListener('message', async (event) => { - var method = event.data.method; - var uploadUrl = event.data.uploadUrl; - var data = event.data.data; - var headers = event.data.headers; - uploadFile(method, uploadUrl, data, headers); -}); - -function uploadFile(method, uploadUrl, data, headers) { - var xhr = new XMLHttpRequest(); - var formdata = new FormData(); - var uploadPercent; - - setData(formdata, data); - - xhr.upload.addEventListener('progress', function (d) { - if (d.lengthComputable) { - uploadPercent = Math.floor((d.loaded / d.total) * 100); - postMessage(uploadPercent); - } - }, false); - xhr.onreadystatechange = function () { - if (xhr.readyState == XMLHttpRequest.DONE) { - postMessage("done"); - } - } - - xhr.onload = () => { - postMessage(xhr.response); - }; - - xhr.onerror = function () { - // only triggers if the request couldn't be made at all - postMessage("request failed"); - }; - - xhr.open(method, uploadUrl, true); - setHeaders(xhr, headers); - - xhr.send(formdata); -} - -function setData(formdata, data) { - for (let key in data) { - formdata.append(key, data[key]) - } -} - -function setHeaders(xhr, headers) { - for (let key in headers) { - xhr.setRequestHeader(key, headers[key]) - } -} \ No newline at end of file diff --git a/lib/js/upload_worker.js b/lib/js/upload_worker.js new file mode 100644 index 0000000..5a6af3e --- /dev/null +++ b/lib/js/upload_worker.js @@ -0,0 +1,53 @@ +self.addEventListener('message', async (event) => { + var method = event.data.method; + var uploadUrl = event.data.uploadUrl; + var data = event.data.data; + var headers = event.data.headers; + uploadFile(method, uploadUrl, data, headers); +}); + +function uploadFile(method, uploadUrl, data, headers) { + var xhr = new XMLHttpRequest(); + var formdata = new FormData(); + var uploadPercent; + + setData(formdata, data); + + xhr.upload.addEventListener('progress', function (d) { + if (d.lengthComputable) { + uploadPercent = Math.floor((d.loaded / d.total) * 100); + postMessage(uploadPercent); + } + }, false); + xhr.onreadystatechange = function () { + if (xhr.readyState == XMLHttpRequest.DONE) { + postMessage("done"); + } + } + + xhr.onload = () => { + postMessage(xhr.response); + }; + + xhr.onerror = function () { + // only triggers if the request couldn't be made at all + postMessage("request failed"); + }; + + xhr.open(method, uploadUrl, true); + setHeaders(xhr, headers); + + xhr.send(formdata); +} + +function setData(formdata, data) { + for (let key in data) { + formdata.append(key, data[key]) + } +} + +function setHeaders(xhr, headers) { + for (let key in headers) { + xhr.setRequestHeader(key, headers[key]) + } +} diff --git a/lib/src/large_file_uploader.dart b/lib/src/large_file_uploader.dart index 4f76451..f0ddf70 100644 --- a/lib/src/large_file_uploader.dart +++ b/lib/src/large_file_uploader.dart @@ -4,7 +4,7 @@ import 'package:large_file_uploader/src/enum/file_types.dart'; import 'package:universal_html/html.dart' as html; /// Callback exposing currently upload progress. -typedef UploadProgressListener = Function(int progress); +typedef UploadProgressListener = Function(int progress, String id); /// Callback exposing upload fail event. typedef UploadFailureListener = Function(); @@ -23,10 +23,13 @@ class LargeFileUploader { Timer? _timer; int _fakeProgress = 0; + StreamSubscription? _streamSubscription; + void selectFileAndUpload({ String method = 'POST', FileTypes type = FileTypes.file, String? customFileType, + bool allowMultiple = false, required String uploadUrl, Map? data, Map? headers, @@ -40,10 +43,23 @@ class LargeFileUploader { pick( type: type, customFileType: customFileType, + allowMultiple: allowMultiple, callback: (file) { data ??= {}; data!["file"] = file; - upload(uploadUrl: uploadUrl, onSendProgress: onSendProgress, data: data!); + upload( + method: method, + name: file.name, + uploadUrl: uploadUrl, + data: data!, + headers: headers, + onSendProgress: onSendProgress, + fakePreProcessMaxProgress: fakePreProcessMaxProgress, + fakePreProcessProgressPeriodInMillisecond: fakePreProcessProgressPeriodInMillisecond, + onSendWithFakePreProcessProgress: onSendWithFakePreProcessProgress, + onFailure: onFailure, + onComplete: onComplete, + ); }, ); } @@ -51,32 +67,36 @@ class LargeFileUploader { void pick({ FileTypes type = FileTypes.file, String? customFileType, + bool allowMultiple = false, required OnFileSelectedListener callback, }) { html.FileUploadInputElement fileUploadInputElement = html.FileUploadInputElement(); fileUploadInputElement.accept = customFileType ?? type.value; - fileUploadInputElement.multiple = false; + fileUploadInputElement.multiple = allowMultiple; fileUploadInputElement.click(); fileUploadInputElement.onChange.listen((_) { if (fileUploadInputElement.files != null) { - callback.call(fileUploadInputElement.files!.first); + for (final file in fileUploadInputElement.files!) { + callback.call(file); + } } }); } void upload({ + String method = 'POST', + required String name, required String uploadUrl, - required UploadProgressListener onSendProgress, required Map data, - String method = 'POST', Map? headers, + required UploadProgressListener onSendProgress, int fakePreProcessMaxProgress = 30, int fakePreProcessProgressPeriodInMillisecond = 500, UploadProgressListener? onSendWithFakePreProcessProgress, UploadFailureListener? onFailure, UploadCompleteListener? onComplete, - }) { + }) async { _worker.postMessage({ 'method': method, 'uploadUrl': uploadUrl, @@ -88,25 +108,29 @@ class LargeFileUploader { _timer = Timer.periodic(Duration(milliseconds: fakePreProcessProgressPeriodInMillisecond), (Timer timer) { if (_fakeProgress != fakePreProcessMaxProgress) { _fakeProgress++; - onSendWithFakePreProcessProgress.call(_fakeProgress); + onSendWithFakePreProcessProgress.call(_fakeProgress, 'fake'); } else { _disposeTimerAndFakeProgress(); } }); } - _worker.onMessage.listen((data) { - _handleCallbacks(data.data, - onSendProgress: onSendProgress, - fakePreProcessMaxProgress: fakePreProcessMaxProgress, - onSendWithFakePreProcessProgress: onSendWithFakePreProcessProgress, - onFailure: onFailure, - onComplete: onComplete); + _streamSubscription = _worker.onMessage.listen((data) { + _handleCallbacks( + data.data, + name: name, + onSendProgress: onSendProgress, + fakePreProcessMaxProgress: fakePreProcessMaxProgress, + onSendWithFakePreProcessProgress: onSendWithFakePreProcessProgress, + onFailure: onFailure, + onComplete: onComplete, + ); }); } void _handleCallbacks( data, { + required String name, required UploadProgressListener onSendProgress, required int fakePreProcessMaxProgress, UploadProgressListener? onSendWithFakePreProcessProgress, @@ -116,19 +140,22 @@ class LargeFileUploader { if (data == null) return; if (data is int) { - onSendProgress.call(data); + onSendProgress.call(data, name); if (data != 0) { _disposeTimerAndFakeProgress(); - onSendWithFakePreProcessProgress - ?.call((fakePreProcessMaxProgress + (data * ((100 - fakePreProcessMaxProgress) / 100))).toInt()); + onSendWithFakePreProcessProgress?.call( + (fakePreProcessMaxProgress + (data * ((100 - fakePreProcessMaxProgress) / 100))).toInt(), + name, + ); } } else if (data.toString() == 'request failed') { _disposeTimerAndFakeProgress(); onFailure?.call(); } else { - onSendWithFakePreProcessProgress?.call(100); + onSendWithFakePreProcessProgress?.call(100, name); _disposeTimerAndFakeProgress(); onComplete?.call(data); + _streamSubscription?.cancel(); } }