From 71ff6349a9c6c783d479b98d6e9b9d535f553bcc Mon Sep 17 00:00:00 2001 From: balvinderz Date: Sat, 17 Jul 2021 11:50:20 +0530 Subject: [PATCH 1/9] implement pick multiple image for web --- .../lib/image_picker_for_web.dart | 97 ++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index 08ce801cafbe..0ee6ac89be63 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -96,6 +96,16 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _getSelectedFile(input); } + /// Injects a file input, and returns a list of PickedFile that the user selected locally. + @override + Future?> pickMultiImage( + {double? maxWidth, double? maxHeight, int? imageQuality}) { + html.FileUploadInputElement input = + createInputElement(_kAcceptImageMimeType,null,multiple: true) as html.FileUploadInputElement; + _injectAndActivate(input); + return _getSelectedFiles(input); + } + /// Returns an [XFile] with the image that was picked. /// /// The `source` argument controls where the image comes from. This can @@ -142,8 +152,31 @@ class ImagePickerPlugin extends ImagePickerPlatform { return getFile(accept: _kAcceptVideoMimeType, capture: capture); } + /// Injects a file input, and returns a list of XFile that the user selected locally. + @override + Future?> getMultiImage({double? maxWidth, double? maxHeight, int? imageQuality}) { + return getFiles(accept: _kAcceptImageMimeType); + } + /// Injects a file input with the specified accept+capture attributes, and - /// returns the PickedFile that the user selected locally. + /// returns a list of XFile that the user selected locally. + /// + /// `capture` is only supported in mobile browsers. + /// See https://caniuse.com/#feat=html-media-capture + @visibleForTesting + Future> getFiles({ + String? accept, + String? capture, + }) { + html.FileUploadInputElement input = + createInputElement(accept, capture,multiple: true) as html.FileUploadInputElement; + _injectAndActivate(input); + + return _getSelectedXFiles(input); + } + + /// Injects a file input with the specified accept+capture attributes, and + /// returns the XFile that the user selected locally. /// /// `capture` is only supported in mobile browsers. /// See https://caniuse.com/#feat=html-media-capture @@ -178,6 +211,10 @@ class ImagePickerPlugin extends ImagePickerPlatform { return input.files?.first; } + List? _getFilesFromInput(html.FileUploadInputElement input) { + return input.files; + } + /// Handles the OnChange event from a FileUploadInputElement object /// Returns the objectURL of the selected file. String? _handleOnChangeEvent(html.Event event) { @@ -191,6 +228,16 @@ class ImagePickerPlugin extends ImagePickerPlatform { return null; } + List? _handleOnChangeEventForMultipleFiles(html.Event event) { + final html.FileUploadInputElement input = + event.target as html.FileUploadInputElement; + final List? files = _getFilesFromInput(input); + if (files != null) { + return files.map((e) => html.Url.createObjectUrl(e)).toList(); + } + return null; + } + /// Monitors an and returns the selected file. Future _getSelectedFile(html.FileUploadInputElement input) { final Completer _completer = Completer(); @@ -212,6 +259,26 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _completer.future; } + Future> _getSelectedFiles(html.FileUploadInputElement input) { + final Completer> _completer = Completer>(); + // Observe the input until we can return something + input.onChange.first.then((event) { + final objectUrls = _handleOnChangeEventForMultipleFiles(event); + if (!_completer.isCompleted && objectUrls != null) { + _completer.complete(objectUrls.map((objectUrl) => PickedFile((objectUrl))).toList()); + } + }); + input.onError.first.then((event) { + if (!_completer.isCompleted) { + _completer.completeError(event); + } + }); + // Note that we don't bother detaching from these streams, since the + // "input" gets re-created in the DOM every time the user needs to + // pick a file. + return _completer.future; + } + Future _getSelectedXFile(html.FileUploadInputElement input) { final Completer _completer = Completer(); // Observe the input until we can return something @@ -232,6 +299,26 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _completer.future; } + Future> _getSelectedXFiles(html.FileUploadInputElement input) { + final Completer> _completer = Completer>(); + // Observe the input until we can return something + input.onChange.first.then((event) { + final objectUrls = _handleOnChangeEventForMultipleFiles(event); + if (!_completer.isCompleted && objectUrls != null) { + + _completer.complete(objectUrls.map((e) => XFile(e)).toList()); + } + }); + input.onError.first.then((event) { + if (!_completer.isCompleted) { + _completer.completeError(event); + } + }); + // Note that we don't bother detaching from these streams, since the + // "input" gets re-created in the DOM every time the user needs to + // pick a file. + return _completer.future; + } /// Initializes a DOM container where we can host input elements. html.Element _ensureInitialized(String id) { var target = html.querySelector('#${id}'); @@ -248,12 +335,12 @@ class ImagePickerPlugin extends ImagePickerPlatform { /// Creates an input element that accepts certain file types, and /// allows to `capture` from the device's cameras (where supported) @visibleForTesting - html.Element createInputElement(String? accept, String? capture) { + html.Element createInputElement(String? accept, String? capture,{bool multiple = false} ) { if (_hasOverrides) { return _overrides!.createInputElement(accept, capture); } - html.Element element = html.FileUploadInputElement()..accept = accept; + html.Element element = html.FileUploadInputElement()..accept = accept..multiple = multiple; if (capture != null) { element.setAttribute('capture', capture); @@ -284,6 +371,8 @@ typedef OverrideExtractFilesFromInputFunction = html.File Function( html.Element? input, ); + + /// Overrides for some of the functionality above. @visibleForTesting class ImagePickerPluginTestOverrides { @@ -292,4 +381,6 @@ class ImagePickerPluginTestOverrides { /// Override the extraction of the selected file from an input element. late OverrideExtractFilesFromInputFunction getFileFromInput; + + } From 00c39567094f8abacdd930da06fbf25e04f493ec Mon Sep 17 00:00:00 2001 From: balvinderz Date: Sat, 17 Jul 2021 12:05:15 +0530 Subject: [PATCH 2/9] save file name,length and last modified in xfile --- .../image_picker_for_web/CHANGELOG.md | 4 + .../lib/image_picker_for_web.dart | 90 +++++++++++-------- .../image_picker_for_web/pubspec.yaml | 2 +- 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index b0379ad2c07c..0611b8537b25 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.1 + +* Implement `pickMultiImage` and `getMultiImage` + # 2.1.0 * Implemented `getImage`, `getVideo` and `getFile` methods that return `XFile` instances. diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index 0ee6ac89be63..51d875d4aa7f 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -18,6 +18,7 @@ final String _kAcceptVideoMimeType = 'video/3gpp,video/x-m4v,video/mp4,video/*'; /// This class implements the `package:image_picker` functionality for the web. class ImagePickerPlugin extends ImagePickerPlatform { final ImagePickerPluginTestOverrides? _overrides; + bool get _hasOverrides => _overrides != null; late html.Element _target; @@ -101,7 +102,8 @@ class ImagePickerPlugin extends ImagePickerPlatform { Future?> pickMultiImage( {double? maxWidth, double? maxHeight, int? imageQuality}) { html.FileUploadInputElement input = - createInputElement(_kAcceptImageMimeType,null,multiple: true) as html.FileUploadInputElement; + createInputElement(_kAcceptImageMimeType, null, multiple: true) + as html.FileUploadInputElement; _injectAndActivate(input); return _getSelectedFiles(input); } @@ -154,7 +156,8 @@ class ImagePickerPlugin extends ImagePickerPlatform { /// Injects a file input, and returns a list of XFile that the user selected locally. @override - Future?> getMultiImage({double? maxWidth, double? maxHeight, int? imageQuality}) { + Future?> getMultiImage( + {double? maxWidth, double? maxHeight, int? imageQuality}) { return getFiles(accept: _kAcceptImageMimeType); } @@ -169,7 +172,8 @@ class ImagePickerPlugin extends ImagePickerPlatform { String? capture, }) { html.FileUploadInputElement input = - createInputElement(accept, capture,multiple: true) as html.FileUploadInputElement; + createInputElement(accept, capture, multiple: true) + as html.FileUploadInputElement; _injectAndActivate(input); return _getSelectedXFiles(input); @@ -216,26 +220,19 @@ class ImagePickerPlugin extends ImagePickerPlatform { } /// Handles the OnChange event from a FileUploadInputElement object - /// Returns the objectURL of the selected file. - String? _handleOnChangeEvent(html.Event event) { + /// Returns the selected file. + html.File? _handleOnChangeEvent(html.Event event) { final html.FileUploadInputElement input = event.target as html.FileUploadInputElement; - final html.File? file = _getFileFromInput(input); - - if (file != null) { - return html.Url.createObjectUrl(file); - } - return null; + return _getFileFromInput(input); } - List? _handleOnChangeEventForMultipleFiles(html.Event event) { + /// Handles the OnChange event from a FileUploadInputElement object + /// Returns the list of selected files. + List? _handleOnChangeEventForMultipleFiles(html.Event event) { final html.FileUploadInputElement input = - event.target as html.FileUploadInputElement; - final List? files = _getFilesFromInput(input); - if (files != null) { - return files.map((e) => html.Url.createObjectUrl(e)).toList(); - } - return null; + event.target as html.FileUploadInputElement; + return _getFilesFromInput(input); } /// Monitors an and returns the selected file. @@ -243,9 +240,9 @@ class ImagePickerPlugin extends ImagePickerPlatform { final Completer _completer = Completer(); // Observe the input until we can return something input.onChange.first.then((event) { - final objectUrl = _handleOnChangeEvent(event); - if (!_completer.isCompleted && objectUrl != null) { - _completer.complete(PickedFile(objectUrl)); + final pickedFile = _handleOnChangeEvent(event); + if (!_completer.isCompleted && pickedFile != null) { + _completer.complete(PickedFile(html.Url.createObjectUrl(pickedFile))); } }); input.onError.first.then((event) { @@ -259,13 +256,17 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _completer.future; } - Future> _getSelectedFiles(html.FileUploadInputElement input) { - final Completer> _completer = Completer>(); + Future> _getSelectedFiles( + html.FileUploadInputElement input) { + final Completer> _completer = + Completer>(); // Observe the input until we can return something input.onChange.first.then((event) { - final objectUrls = _handleOnChangeEventForMultipleFiles(event); - if (!_completer.isCompleted && objectUrls != null) { - _completer.complete(objectUrls.map((objectUrl) => PickedFile((objectUrl))).toList()); + final files = _handleOnChangeEventForMultipleFiles(event); + if (!_completer.isCompleted && files != null) { + _completer.complete(files + .map((file) => PickedFile(html.Url.createObjectUrl(file))) + .toList()); } }); input.onError.first.then((event) { @@ -283,9 +284,14 @@ class ImagePickerPlugin extends ImagePickerPlatform { final Completer _completer = Completer(); // Observe the input until we can return something input.onChange.first.then((event) { - final objectUrl = _handleOnChangeEvent(event); - if (!_completer.isCompleted && objectUrl != null) { - _completer.complete(XFile(objectUrl)); + final file = _handleOnChangeEvent(event); + if (!_completer.isCompleted && file != null) { + _completer.complete(XFile(html.Url.createObjectUrl(file), + name: file.name, + length: file.size, + mimeType: file.type, + lastModified: file.lastModifiedDate + )); } }); input.onError.first.then((event) { @@ -303,10 +309,16 @@ class ImagePickerPlugin extends ImagePickerPlatform { final Completer> _completer = Completer>(); // Observe the input until we can return something input.onChange.first.then((event) { - final objectUrls = _handleOnChangeEventForMultipleFiles(event); - if (!_completer.isCompleted && objectUrls != null) { - - _completer.complete(objectUrls.map((e) => XFile(e)).toList()); + final files = _handleOnChangeEventForMultipleFiles(event); + if (!_completer.isCompleted && files != null) { + _completer.complete(files + .map((file) => XFile( + html.Url.createObjectUrl(file), + name: file.name, + length: file.size, + lastModified: file.lastModifiedDate, + )) + .toList()); } }); input.onError.first.then((event) { @@ -319,6 +331,7 @@ class ImagePickerPlugin extends ImagePickerPlatform { // pick a file. return _completer.future; } + /// Initializes a DOM container where we can host input elements. html.Element _ensureInitialized(String id) { var target = html.querySelector('#${id}'); @@ -335,12 +348,15 @@ class ImagePickerPlugin extends ImagePickerPlatform { /// Creates an input element that accepts certain file types, and /// allows to `capture` from the device's cameras (where supported) @visibleForTesting - html.Element createInputElement(String? accept, String? capture,{bool multiple = false} ) { + html.Element createInputElement(String? accept, String? capture, + {bool multiple = false}) { if (_hasOverrides) { return _overrides!.createInputElement(accept, capture); } - html.Element element = html.FileUploadInputElement()..accept = accept..multiple = multiple; + html.Element element = html.FileUploadInputElement() + ..accept = accept + ..multiple = multiple; if (capture != null) { element.setAttribute('capture', capture); @@ -371,8 +387,6 @@ typedef OverrideExtractFilesFromInputFunction = html.File Function( html.Element? input, ); - - /// Overrides for some of the functionality above. @visibleForTesting class ImagePickerPluginTestOverrides { @@ -381,6 +395,4 @@ class ImagePickerPluginTestOverrides { /// Override the extraction of the selected file from an input element. late OverrideExtractFilesFromInputFunction getFileFromInput; - - } diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml index d9b9c5e5cb86..b2479285a3ea 100644 --- a/packages/image_picker/image_picker_for_web/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_for_web description: Web platform implementation of image_picker repository: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_for_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 2.1.0 +version: 2.1.1 environment: sdk: ">=2.12.0 <3.0.0" From ab071aa542d1998628aa912a93a4f4be9d496d8f Mon Sep 17 00:00:00 2001 From: balvinderz Date: Tue, 20 Jul 2021 00:13:26 +0530 Subject: [PATCH 3/9] add test --- .../image_picker_for_web_test.dart | 24 +++++++++++++++++++ .../lib/image_picker_for_web.dart | 16 +++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart index c6d0b3b532ca..6995649fb666 100644 --- a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart +++ b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart @@ -14,6 +14,7 @@ import 'package:integration_test/integration_test.dart'; final String expectedStringContents = "Hello, world!"; final Uint8List bytes = utf8.encode(expectedStringContents) as Uint8List; final html.File textFile = html.File([bytes], "hello.txt"); +final html.File secondTextFile = html.File([bytes], "secondFile.txt"); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -67,6 +68,29 @@ void main() { expect((await file).readAsBytes(), completion(isNotEmpty)); }); + testWidgets('Can select multiple files', (WidgetTester tester) async { + final mockInput = html.FileUploadInputElement(); + + final overrides = ImagePickerPluginTestOverrides() + ..createInputElement = ((_, __) => mockInput) + ..getMultipleFilesFromInput = ((_) => [textFile, secondTextFile]); + + final plugin = ImagePickerPlugin(overrides: overrides); + + // Init the pick file dialog... + final files = plugin.getMultiImage(); + + // Mock the browser behavior of selecting a file... + mockInput.dispatchEvent(html.Event('change')); + + // Now the file should be available + expect(files, completes); + // And readable + + expect((await files)?.first.readAsBytes(), completion(isNotEmpty)); + expect((await files)?[1].readAsBytes(), completion(isNotEmpty)); + }); + // There's no good way of detecting when the user has "aborted" the selection. testWidgets('computeCaptureAttribute', (WidgetTester tester) async { diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index 51d875d4aa7f..ccbf4a700bdc 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -216,6 +216,9 @@ class ImagePickerPlugin extends ImagePickerPlatform { } List? _getFilesFromInput(html.FileUploadInputElement input) { + if (_hasOverrides) { + return _overrides!.getMultipleFilesFromInput(input); + } return input.files; } @@ -290,8 +293,7 @@ class ImagePickerPlugin extends ImagePickerPlatform { name: file.name, length: file.size, mimeType: file.type, - lastModified: file.lastModifiedDate - )); + lastModified: file.lastModifiedDate)); } }); input.onError.first.then((event) { @@ -387,6 +389,13 @@ typedef OverrideExtractFilesFromInputFunction = html.File Function( html.Element? input, ); +/// A function that extracts list of files from the file `input` passed in. +@visibleForTesting +typedef OverrideExtractMultipleFilesFromInputFunction = List + Function( + html.Element? input, +); + /// Overrides for some of the functionality above. @visibleForTesting class ImagePickerPluginTestOverrides { @@ -395,4 +404,7 @@ class ImagePickerPluginTestOverrides { /// Override the extraction of the selected file from an input element. late OverrideExtractFilesFromInputFunction getFileFromInput; + + /// Override the extraction of the selected files from an input element. + late OverrideExtractMultipleFilesFromInputFunction getMultipleFilesFromInput; } From 5655de0d0df086f7f2785ee108180b21531733c6 Mon Sep 17 00:00:00 2001 From: balvinderz Date: Tue, 20 Jul 2021 00:16:38 +0530 Subject: [PATCH 4/9] update changelog and authors --- packages/image_picker/image_picker_for_web/AUTHORS | 1 + packages/image_picker/image_picker_for_web/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/image_picker/image_picker_for_web/AUTHORS b/packages/image_picker/image_picker_for_web/AUTHORS index 493a0b4ef9c2..538f1abe317e 100644 --- a/packages/image_picker/image_picker_for_web/AUTHORS +++ b/packages/image_picker/image_picker_for_web/AUTHORS @@ -64,3 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> +Balvinder Singh Gambhir diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 0611b8537b25..911a4154072f 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,6 +1,7 @@ # 2.1.1 * Implement `pickMultiImage` and `getMultiImage` +* Add support for getting name,length and lastModified from XFile # 2.1.0 From 590fe2a5a0c4017ddf070dcc3de8f930142b29c5 Mon Sep 17 00:00:00 2001 From: balvinderz Date: Tue, 20 Jul 2021 00:44:18 +0530 Subject: [PATCH 5/9] update changelog --- packages/image_picker/image_picker_for_web/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 911a4154072f..6ebae24e4f65 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,7 +1,7 @@ # 2.1.1 -* Implement `pickMultiImage` and `getMultiImage` -* Add support for getting name,length and lastModified from XFile +* Implemented `pickMultiImage` and `getMultiImage` +* Added support for getting name,length and lastModified from XFile # 2.1.0 From 77f01aab42e66036de79228cc132fb5736616794 Mon Sep 17 00:00:00 2001 From: balvinderz Date: Tue, 20 Jul 2021 01:00:47 +0530 Subject: [PATCH 6/9] review changes --- .../image_picker_for_web/CHANGELOG.md | 4 +- .../lib/image_picker_for_web.dart | 88 ++----------------- 2 files changed, 10 insertions(+), 82 deletions(-) diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 6ebae24e4f65..52a075e106a0 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,7 +1,7 @@ # 2.1.1 -* Implemented `pickMultiImage` and `getMultiImage` -* Added support for getting name,length and lastModified from XFile +* Implemented `pickMultiImage`. +* Added support for getting name,length,mimeType and lastModified from XFile. # 2.1.0 diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index ccbf4a700bdc..21cbdedeb742 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -97,17 +97,6 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _getSelectedFile(input); } - /// Injects a file input, and returns a list of PickedFile that the user selected locally. - @override - Future?> pickMultiImage( - {double? maxWidth, double? maxHeight, int? imageQuality}) { - html.FileUploadInputElement input = - createInputElement(_kAcceptImageMimeType, null, multiple: true) - as html.FileUploadInputElement; - _injectAndActivate(input); - return _getSelectedFiles(input); - } - /// Returns an [XFile] with the image that was picked. /// /// The `source` argument controls where the image comes from. This can @@ -188,11 +177,12 @@ class ImagePickerPlugin extends ImagePickerPlatform { Future getFile({ String? accept, String? capture, - }) { + }) async { html.FileUploadInputElement input = createInputElement(accept, capture) as html.FileUploadInputElement; _injectAndActivate(input); - return _getSelectedXFile(input); + final files = await _getSelectedXFiles(input); + return files.first; } // DOM methods @@ -208,13 +198,6 @@ class ImagePickerPlugin extends ImagePickerPlatform { return null; } - html.File? _getFileFromInput(html.FileUploadInputElement input) { - if (_hasOverrides) { - return _overrides!.getFileFromInput(input); - } - return input.files?.first; - } - List? _getFilesFromInput(html.FileUploadInputElement input) { if (_hasOverrides) { return _overrides!.getMultipleFilesFromInput(input); @@ -223,16 +206,8 @@ class ImagePickerPlugin extends ImagePickerPlatform { } /// Handles the OnChange event from a FileUploadInputElement object - /// Returns the selected file. - html.File? _handleOnChangeEvent(html.Event event) { - final html.FileUploadInputElement input = - event.target as html.FileUploadInputElement; - return _getFileFromInput(input); - } - - /// Handles the OnChange event from a FileUploadInputElement object - /// Returns the list of selected files. - List? _handleOnChangeEventForMultipleFiles(html.Event event) { + /// Returns a list of selected files. + List? _handleOnChangeEvent(html.Event event) { final html.FileUploadInputElement input = event.target as html.FileUploadInputElement; return _getFilesFromInput(input); @@ -245,55 +220,8 @@ class ImagePickerPlugin extends ImagePickerPlatform { input.onChange.first.then((event) { final pickedFile = _handleOnChangeEvent(event); if (!_completer.isCompleted && pickedFile != null) { - _completer.complete(PickedFile(html.Url.createObjectUrl(pickedFile))); - } - }); - input.onError.first.then((event) { - if (!_completer.isCompleted) { - _completer.completeError(event); - } - }); - // Note that we don't bother detaching from these streams, since the - // "input" gets re-created in the DOM every time the user needs to - // pick a file. - return _completer.future; - } - - Future> _getSelectedFiles( - html.FileUploadInputElement input) { - final Completer> _completer = - Completer>(); - // Observe the input until we can return something - input.onChange.first.then((event) { - final files = _handleOnChangeEventForMultipleFiles(event); - if (!_completer.isCompleted && files != null) { - _completer.complete(files - .map((file) => PickedFile(html.Url.createObjectUrl(file))) - .toList()); - } - }); - input.onError.first.then((event) { - if (!_completer.isCompleted) { - _completer.completeError(event); - } - }); - // Note that we don't bother detaching from these streams, since the - // "input" gets re-created in the DOM every time the user needs to - // pick a file. - return _completer.future; - } - - Future _getSelectedXFile(html.FileUploadInputElement input) { - final Completer _completer = Completer(); - // Observe the input until we can return something - input.onChange.first.then((event) { - final file = _handleOnChangeEvent(event); - if (!_completer.isCompleted && file != null) { - _completer.complete(XFile(html.Url.createObjectUrl(file), - name: file.name, - length: file.size, - mimeType: file.type, - lastModified: file.lastModifiedDate)); + _completer + .complete(PickedFile(html.Url.createObjectUrl(pickedFile.first))); } }); input.onError.first.then((event) { @@ -311,7 +239,7 @@ class ImagePickerPlugin extends ImagePickerPlatform { final Completer> _completer = Completer>(); // Observe the input until we can return something input.onChange.first.then((event) { - final files = _handleOnChangeEventForMultipleFiles(event); + final files = _handleOnChangeEvent(event); if (!_completer.isCompleted && files != null) { _completer.complete(files .map((file) => XFile( From b135f9b85b48f53936fe01fa4e40d6b5b7a70e95 Mon Sep 17 00:00:00 2001 From: balvinderz Date: Tue, 20 Jul 2021 01:01:44 +0530 Subject: [PATCH 7/9] review changes --- packages/image_picker/image_picker_for_web/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 52a075e106a0..1e06e69e1da4 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,6 +1,6 @@ # 2.1.1 -* Implemented `pickMultiImage`. +* Implemented `getMultiImage`. * Added support for getting name,length,mimeType and lastModified from XFile. # 2.1.0 From 913e8886b55dd378ce1dc049f60805acbe39e4e1 Mon Sep 17 00:00:00 2001 From: balvinderz Date: Tue, 20 Jul 2021 01:16:13 +0530 Subject: [PATCH 8/9] fix test and dartfmt --- .../integration_test/image_picker_for_web_test.dart | 4 ++-- .../image_picker_for_web/lib/image_picker_for_web.dart | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart index 6995649fb666..ef014c127813 100644 --- a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart +++ b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart @@ -31,7 +31,7 @@ void main() { final overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) - ..getFileFromInput = ((_) => textFile); + ..getMultipleFilesFromInput = ((_) => [textFile]); final plugin = ImagePickerPlugin(overrides: overrides); @@ -52,7 +52,7 @@ void main() { final overrides = ImagePickerPluginTestOverrides() ..createInputElement = ((_, __) => mockInput) - ..getFileFromInput = ((_) => textFile); + ..getMultipleFilesFromInput = ((_) => [textFile]); final plugin = ImagePickerPlugin(overrides: overrides); diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index 21cbdedeb742..ca0c15e211c5 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -311,12 +311,6 @@ typedef OverrideCreateInputFunction = html.Element Function( String? capture, ); -/// A function that extracts a [html.File] from the file `input` passed in. -@visibleForTesting -typedef OverrideExtractFilesFromInputFunction = html.File Function( - html.Element? input, -); - /// A function that extracts list of files from the file `input` passed in. @visibleForTesting typedef OverrideExtractMultipleFilesFromInputFunction = List @@ -330,9 +324,6 @@ class ImagePickerPluginTestOverrides { /// Override the creation of the input element. late OverrideCreateInputFunction createInputElement; - /// Override the extraction of the selected file from an input element. - late OverrideExtractFilesFromInputFunction getFileFromInput; - /// Override the extraction of the selected files from an input element. late OverrideExtractMultipleFilesFromInputFunction getMultipleFilesFromInput; } From 3c83bef3164deb63d04407d3c9c123e055910ad6 Mon Sep 17 00:00:00 2001 From: David Iglesias Teixeira Date: Mon, 19 Jul 2021 16:19:50 -0700 Subject: [PATCH 9/9] Some PR feedback. * Removed redundant getFile method (getFiles already exists) * Added tests to verify that 'multiple' attribute is set in file input * Added tests to verify that extra metadata is set. * Added some , to improve dart format output. --- .../image_picker/image_picker_for_web/AUTHORS | 2 +- .../image_picker_for_web/CHANGELOG.md | 3 +- .../image_picker_for_web_test.dart | 59 ++++++++++++-- .../lib/image_picker_for_web.dart | 81 ++++++++++--------- 4 files changed, 98 insertions(+), 47 deletions(-) diff --git a/packages/image_picker/image_picker_for_web/AUTHORS b/packages/image_picker/image_picker_for_web/AUTHORS index 538f1abe317e..d6ad42a677e5 100644 --- a/packages/image_picker/image_picker_for_web/AUTHORS +++ b/packages/image_picker/image_picker_for_web/AUTHORS @@ -64,4 +64,4 @@ Aleksandr Yurkovskiy Anton Borries Alex Li Rahul Raj <64.rahulraj@gmail.com> -Balvinder Singh Gambhir +Balvinder Singh Gambhir diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 1e06e69e1da4..f32a5d8e92cd 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,7 +1,8 @@ # 2.1.1 * Implemented `getMultiImage`. -* Added support for getting name,length,mimeType and lastModified from XFile. +* Initialized the following `XFile` attributes for picked files: + * `name`, `length`, `mimeType` and `lastModified`. # 2.1.0 diff --git a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart index ef014c127813..c1025a9f07d3 100644 --- a/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart +++ b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart @@ -11,10 +11,16 @@ import 'package:image_picker_for_web/image_picker_for_web.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; import 'package:integration_test/integration_test.dart'; -final String expectedStringContents = "Hello, world!"; +final String expectedStringContents = 'Hello, world!'; +final String otherStringContents = 'Hello again, world!'; final Uint8List bytes = utf8.encode(expectedStringContents) as Uint8List; -final html.File textFile = html.File([bytes], "hello.txt"); -final html.File secondTextFile = html.File([bytes], "secondFile.txt"); +final Uint8List otherBytes = utf8.encode(otherStringContents) as Uint8List; +final Map options = { + 'type': 'text/plain', + 'lastModified': DateTime.utc(2017, 12, 13).millisecondsSinceEpoch, +}; +final html.File textFile = html.File([bytes], 'hello.txt', options); +final html.File secondTextFile = html.File([otherBytes], 'secondFile.txt'); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -57,15 +63,25 @@ void main() { final plugin = ImagePickerPlugin(overrides: overrides); // Init the pick file dialog... - final file = plugin.getFile(); + final image = plugin.getImage(source: ImageSource.camera); // Mock the browser behavior of selecting a file... mockInput.dispatchEvent(html.Event('change')); // Now the file should be available - expect(file, completes); + expect(image, completes); + // And readable - expect((await file).readAsBytes(), completion(isNotEmpty)); + final XFile file = await image; + expect(file.readAsBytes(), completion(isNotEmpty)); + expect(file.name, textFile.name); + expect(file.length(), completion(textFile.size)); + expect(file.mimeType, textFile.type); + expect( + file.lastModified(), + completion( + DateTime.fromMillisecondsSinceEpoch(textFile.lastModified!), + )); }); testWidgets('Can select multiple files', (WidgetTester tester) async { @@ -85,10 +101,15 @@ void main() { // Now the file should be available expect(files, completes); + // And readable + expect((await files).first.readAsBytes(), completion(isNotEmpty)); - expect((await files)?.first.readAsBytes(), completion(isNotEmpty)); - expect((await files)?[1].readAsBytes(), completion(isNotEmpty)); + // Peek into the second file... + final XFile secondFile = (await files).elementAt(1); + expect(secondFile.readAsBytes(), completion(isNotEmpty)); + expect(secondFile.name, secondTextFile.name); + expect(secondFile.length(), completion(secondTextFile.size)); }); // There's no good way of detecting when the user has "aborted" the selection. @@ -118,6 +139,7 @@ void main() { expect(input.attributes, containsPair('accept', 'any')); expect(input.attributes, isNot(contains('capture'))); + expect(input.attributes, isNot(contains('multiple'))); }); testWidgets('accept: any, capture: something', (WidgetTester tester) async { @@ -125,6 +147,27 @@ void main() { expect(input.attributes, containsPair('accept', 'any')); expect(input.attributes, containsPair('capture', 'something')); + expect(input.attributes, isNot(contains('multiple'))); + }); + + testWidgets('accept: any, capture: null, multi: true', + (WidgetTester tester) async { + html.Element input = + plugin.createInputElement('any', null, multiple: true); + + expect(input.attributes, containsPair('accept', 'any')); + expect(input.attributes, isNot(contains('capture'))); + expect(input.attributes, contains('multiple')); + }); + + testWidgets('accept: any, capture: something, multi: true', + (WidgetTester tester) async { + html.Element input = + plugin.createInputElement('any', 'something', multiple: true); + + expect(input.attributes, containsPair('accept', 'any')); + expect(input.attributes, containsPair('capture', 'something')); + expect(input.attributes, contains('multiple')); }); }); } diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index ca0c15e211c5..b170ee3256ab 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -116,9 +116,13 @@ class ImagePickerPlugin extends ImagePickerPlatform { double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, - }) { + }) async { String? capture = computeCaptureAttribute(source, preferredCameraDevice); - return getFile(accept: _kAcceptImageMimeType, capture: capture); + List files = await getFiles( + accept: _kAcceptImageMimeType, + capture: capture, + ); + return files.first; } /// Returns an [XFile] containing the video that was picked. @@ -138,53 +142,50 @@ class ImagePickerPlugin extends ImagePickerPlatform { required ImageSource source, CameraDevice preferredCameraDevice = CameraDevice.rear, Duration? maxDuration, - }) { + }) async { String? capture = computeCaptureAttribute(source, preferredCameraDevice); - return getFile(accept: _kAcceptVideoMimeType, capture: capture); + List files = await getFiles( + accept: _kAcceptVideoMimeType, + capture: capture, + ); + return files.first; } /// Injects a file input, and returns a list of XFile that the user selected locally. @override - Future?> getMultiImage( - {double? maxWidth, double? maxHeight, int? imageQuality}) { - return getFiles(accept: _kAcceptImageMimeType); + Future> getMultiImage({ + double? maxWidth, + double? maxHeight, + int? imageQuality, + }) { + return getFiles(accept: _kAcceptImageMimeType, multiple: true); } /// Injects a file input with the specified accept+capture attributes, and /// returns a list of XFile that the user selected locally. /// /// `capture` is only supported in mobile browsers. + /// + /// `multiple` can be passed to allow for multiple selection of files. Defaults + /// to false. + /// /// See https://caniuse.com/#feat=html-media-capture @visibleForTesting Future> getFiles({ String? accept, String? capture, + bool multiple = false, }) { - html.FileUploadInputElement input = - createInputElement(accept, capture, multiple: true) - as html.FileUploadInputElement; + html.FileUploadInputElement input = createInputElement( + accept, + capture, + multiple: multiple, + ) as html.FileUploadInputElement; _injectAndActivate(input); return _getSelectedXFiles(input); } - /// Injects a file input with the specified accept+capture attributes, and - /// returns the XFile that the user selected locally. - /// - /// `capture` is only supported in mobile browsers. - /// See https://caniuse.com/#feat=html-media-capture - @visibleForTesting - Future getFile({ - String? accept, - String? capture, - }) async { - html.FileUploadInputElement input = - createInputElement(accept, capture) as html.FileUploadInputElement; - _injectAndActivate(input); - final files = await _getSelectedXFiles(input); - return files.first; - } - // DOM methods /// Converts plugin configuration into a proper value for the `capture` attribute. @@ -218,10 +219,11 @@ class ImagePickerPlugin extends ImagePickerPlatform { final Completer _completer = Completer(); // Observe the input until we can return something input.onChange.first.then((event) { - final pickedFile = _handleOnChangeEvent(event); - if (!_completer.isCompleted && pickedFile != null) { - _completer - .complete(PickedFile(html.Url.createObjectUrl(pickedFile.first))); + final files = _handleOnChangeEvent(event); + if (!_completer.isCompleted && files != null) { + _completer.complete(PickedFile( + html.Url.createObjectUrl(files.first), + )); } }); input.onError.first.then((event) { @@ -235,6 +237,7 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _completer.future; } + /// Monitors an and returns the selected file(s). Future> _getSelectedXFiles(html.FileUploadInputElement input) { final Completer> _completer = Completer>(); // Observe the input until we can return something @@ -246,7 +249,10 @@ class ImagePickerPlugin extends ImagePickerPlatform { html.Url.createObjectUrl(file), name: file.name, length: file.size, - lastModified: file.lastModifiedDate, + lastModified: DateTime.fromMillisecondsSinceEpoch( + file.lastModified ?? DateTime.now().millisecondsSinceEpoch, + ), + mimeType: file.type, )) .toList()); } @@ -278,8 +284,11 @@ class ImagePickerPlugin extends ImagePickerPlatform { /// Creates an input element that accepts certain file types, and /// allows to `capture` from the device's cameras (where supported) @visibleForTesting - html.Element createInputElement(String? accept, String? capture, - {bool multiple = false}) { + html.Element createInputElement( + String? accept, + String? capture, { + bool multiple = false, + }) { if (_hasOverrides) { return _overrides!.createInputElement(accept, capture); } @@ -314,9 +323,7 @@ typedef OverrideCreateInputFunction = html.Element Function( /// A function that extracts list of files from the file `input` passed in. @visibleForTesting typedef OverrideExtractMultipleFilesFromInputFunction = List - Function( - html.Element? input, -); + Function(html.Element? input); /// Overrides for some of the functionality above. @visibleForTesting