mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 23:03:11 +08:00
[image_picker_web] Listens to file input cancel event. (#4453)
## Changes This PR listens to the `cancel` event from the `input type=file` used by the web implementation of the image_picker plugin, so apps don't end up endlessly awaiting for a file that will never come **in modern browsers** (Chrome 113, Safari 16.4, or newer). _Same API as https://github.com/flutter/packages/pull/3683._ Additionally, this PR: * Removes all code and tests mentioning `PickedFile`. (Deprecated years ago, and unused since https://github.com/flutter/packages/pull/4285) **(Breaking change)** * Updates README to mention `XFile` which is the current return type of the package. * Updates the dependency on `image_picker_platform_interface` to `^2.9.0`. * Implements all non-deprecated methods from the interface, and makes deprecated methods use the fresh ones. * Updates tests. ### Issues * Fixes https://github.com/flutter/flutter/issues/92176 ### Testing * Added integration testing coverage for the 'cancel' event. * Tested manually in Chrome with the example app running on web.
This commit is contained in:
@ -1,3 +1,10 @@
|
|||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* **BREAKING CHANGE:** Removes all code and tests mentioning `PickedFile`.
|
||||||
|
* Listens to `cancel` event on file selection. When the selection is canceled:
|
||||||
|
* `Future<XFile?>` methods return `null`
|
||||||
|
* `Future<List<XFile>>` methods return an empty list.
|
||||||
|
|
||||||
## 2.2.0
|
## 2.2.0
|
||||||
|
|
||||||
* Adds `getMedia` method.
|
* Adds `getMedia` method.
|
||||||
|
@ -4,23 +4,12 @@ A web implementation of [`image_picker`][1].
|
|||||||
|
|
||||||
## Limitations on the web platform
|
## Limitations on the web platform
|
||||||
|
|
||||||
Since Web Browsers don't offer direct access to their users' file system,
|
### `XFile`
|
||||||
this plugin provides a `PickedFile` abstraction to make access uniform
|
|
||||||
across platforms.
|
|
||||||
|
|
||||||
The web version of the plugin puts network-accessible URIs as the `path`
|
This plugin uses `XFile` objects to abstract files picked/created by the user.
|
||||||
in the returned `PickedFile`.
|
|
||||||
|
|
||||||
### URL.createObjectURL()
|
Read more about `XFile` on the web in
|
||||||
|
[`package:cross_file`'s README](https://pub.dev/packages/cross_file).
|
||||||
The `PickedFile` object in web is backed by [`URL.createObjectUrl` Web API](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL),
|
|
||||||
which is reasonably well supported across all browsers:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
However, the returned `path` attribute of the `PickedFile` points to a `network` resource, and not a
|
|
||||||
local path in your users' drive. See **Use the plugin** below for some examples on how to use this
|
|
||||||
return value in a cross-platform way.
|
|
||||||
|
|
||||||
### input file "accept"
|
### input file "accept"
|
||||||
|
|
||||||
@ -42,11 +31,26 @@ In order to "take a photo", some mobile browsers offer a [`capture` attribute](h
|
|||||||
Each browser may implement `capture` any way they please, so it may (or may not) make a
|
Each browser may implement `capture` any way they please, so it may (or may not) make a
|
||||||
difference in your users' experience.
|
difference in your users' experience.
|
||||||
|
|
||||||
### pickImage()
|
### input file "cancel"
|
||||||
The arguments `maxWidth`, `maxHeight` and `imageQuality` are not supported for gif images.
|
|
||||||
The argument `imageQuality` only works for jpeg and webp images.
|
The [`cancel` event](https://caniuse.com/mdn-api_htmlinputelement_cancel_event)
|
||||||
|
used by the plugin to detect when users close the file selector without picking
|
||||||
|
a file is relatively new, and will only work in recent browsers.
|
||||||
|
|
||||||
|
### `ImagePickerOptions` support
|
||||||
|
|
||||||
|
The `ImagePickerOptions` configuration object allows passing resize (`maxWidth`,
|
||||||
|
`maxHeight`) and quality (`imageQuality`) parameters to some methods of this
|
||||||
|
plugin, which in other platforms control how selected images are resized or
|
||||||
|
re-encoded.
|
||||||
|
|
||||||
|
On the web:
|
||||||
|
|
||||||
|
* `maxWidth`, `maxHeight` and `imageQuality` are not supported for `gif` images.
|
||||||
|
* `imageQuality` only affects `jpg` and `webp` images.
|
||||||
|
|
||||||
|
### `getVideo()`
|
||||||
|
|
||||||
### pickVideo()
|
|
||||||
The argument `maxDuration` is not supported on the web.
|
The argument `maxDuration` is not supported on the web.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -65,8 +69,8 @@ should add it to your `pubspec.yaml` as usual.
|
|||||||
|
|
||||||
You should be able to use `package:image_picker` _almost_ as normal.
|
You should be able to use `package:image_picker` _almost_ as normal.
|
||||||
|
|
||||||
Once the user has picked a file, the returned `PickedFile` instance will contain a
|
Once the user has picked a file, the returned `XFile` instance will contain a
|
||||||
`network`-accessible URL (pointing to a location within the browser).
|
`network`-accessible `Blob` URL (pointing to a location within the browser).
|
||||||
|
|
||||||
The instance will also let you retrieve the bytes of the selected file across all platforms.
|
The instance will also let you retrieve the bytes of the selected file across all platforms.
|
||||||
|
|
||||||
|
@ -33,7 +33,9 @@ void main() {
|
|||||||
plugin = ImagePickerPlugin();
|
plugin = ImagePickerPlugin();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Can select a file (Deprecated)', (WidgetTester tester) async {
|
testWidgets('getImageFromSource can select a file', (
|
||||||
|
WidgetTester _,
|
||||||
|
) async {
|
||||||
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
||||||
|
|
||||||
final ImagePickerPluginTestOverrides overrides =
|
final ImagePickerPluginTestOverrides overrides =
|
||||||
@ -44,29 +46,9 @@ void main() {
|
|||||||
final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
|
final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
|
||||||
|
|
||||||
// Init the pick file dialog...
|
// Init the pick file dialog...
|
||||||
final Future<PickedFile> file = plugin.pickFile();
|
final Future<XFile?> image = plugin.getImageFromSource(
|
||||||
|
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);
|
|
||||||
// And readable
|
|
||||||
expect((await file).readAsBytes(), completion(isNotEmpty));
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Can select a file', (WidgetTester tester) async {
|
|
||||||
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
|
||||||
|
|
||||||
final ImagePickerPluginTestOverrides overrides =
|
|
||||||
ImagePickerPluginTestOverrides()
|
|
||||||
..createInputElement = ((_, __) => mockInput)
|
|
||||||
..getMultipleFilesFromInput = ((_) => <html.File>[textFile]);
|
|
||||||
|
|
||||||
final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
|
|
||||||
|
|
||||||
// Init the pick file dialog...
|
|
||||||
final Future<XFile> image = plugin.getImage(source: ImageSource.camera);
|
|
||||||
|
|
||||||
// Mock the browser behavior of selecting a file...
|
// Mock the browser behavior of selecting a file...
|
||||||
mockInput.dispatchEvent(html.Event('change'));
|
mockInput.dispatchEvent(html.Event('change'));
|
||||||
@ -75,8 +57,9 @@ void main() {
|
|||||||
expect(image, completes);
|
expect(image, completes);
|
||||||
|
|
||||||
// And readable
|
// And readable
|
||||||
final XFile file = await image;
|
final XFile? file = await image;
|
||||||
expect(file.readAsBytes(), completion(isNotEmpty));
|
expect(file, isNotNull);
|
||||||
|
expect(file!.readAsBytes(), completion(isNotEmpty));
|
||||||
expect(file.name, textFile.name);
|
expect(file.name, textFile.name);
|
||||||
expect(file.length(), completion(textFile.size));
|
expect(file.length(), completion(textFile.size));
|
||||||
expect(file.mimeType, textFile.type);
|
expect(file.mimeType, textFile.type);
|
||||||
@ -87,8 +70,9 @@ void main() {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('getMultiImage can select multiple files',
|
testWidgets('getMultiImageWithOptions can select multiple files', (
|
||||||
(WidgetTester tester) async {
|
WidgetTester _,
|
||||||
|
) async {
|
||||||
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
||||||
|
|
||||||
final ImagePickerPluginTestOverrides overrides =
|
final ImagePickerPluginTestOverrides overrides =
|
||||||
@ -100,7 +84,7 @@ void main() {
|
|||||||
final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
|
final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
|
||||||
|
|
||||||
// Init the pick file dialog...
|
// Init the pick file dialog...
|
||||||
final Future<List<XFile>> files = plugin.getMultiImage();
|
final Future<List<XFile>> files = plugin.getMultiImageWithOptions();
|
||||||
|
|
||||||
// Mock the browser behavior of selecting a file...
|
// Mock the browser behavior of selecting a file...
|
||||||
mockInput.dispatchEvent(html.Event('change'));
|
mockInput.dispatchEvent(html.Event('change'));
|
||||||
@ -118,8 +102,7 @@ void main() {
|
|||||||
expect(secondFile.length(), completion(secondTextFile.size));
|
expect(secondFile.length(), completion(secondTextFile.size));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('getMedia can select multiple files',
|
testWidgets('getMedia can select multiple files', (WidgetTester _) async {
|
||||||
(WidgetTester tester) async {
|
|
||||||
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
||||||
|
|
||||||
final ImagePickerPluginTestOverrides overrides =
|
final ImagePickerPluginTestOverrides overrides =
|
||||||
@ -150,7 +133,72 @@ void main() {
|
|||||||
expect(secondFile.length(), completion(secondTextFile.size));
|
expect(secondFile.length(), completion(secondTextFile.size));
|
||||||
});
|
});
|
||||||
|
|
||||||
// There's no good way of detecting when the user has "aborted" the selection.
|
group('cancel event', () {
|
||||||
|
late html.FileUploadInputElement mockInput;
|
||||||
|
late ImagePickerPluginTestOverrides overrides;
|
||||||
|
late ImagePickerPlugin plugin;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockInput = html.FileUploadInputElement();
|
||||||
|
overrides = ImagePickerPluginTestOverrides()
|
||||||
|
..createInputElement = ((_, __) => mockInput)
|
||||||
|
..getMultipleFilesFromInput = ((_) => <html.File>[textFile]);
|
||||||
|
plugin = ImagePickerPlugin(overrides: overrides);
|
||||||
|
});
|
||||||
|
|
||||||
|
void mockCancel() {
|
||||||
|
mockInput.dispatchEvent(html.Event('cancel'));
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('getFiles - returns empty list', (WidgetTester _) async {
|
||||||
|
final Future<List<XFile>> files = plugin.getFiles();
|
||||||
|
mockCancel();
|
||||||
|
|
||||||
|
expect(files, completes);
|
||||||
|
expect(await files, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('getMedia - returns empty list', (WidgetTester _) async {
|
||||||
|
final Future<List<XFile>?> files = plugin.getMedia(
|
||||||
|
options: const MediaOptions(
|
||||||
|
allowMultiple: true,
|
||||||
|
));
|
||||||
|
mockCancel();
|
||||||
|
|
||||||
|
expect(files, completes);
|
||||||
|
expect(await files, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('getMultiImageWithOptions - returns empty list', (
|
||||||
|
WidgetTester _,
|
||||||
|
) async {
|
||||||
|
final Future<List<XFile>?> files = plugin.getMultiImageWithOptions();
|
||||||
|
mockCancel();
|
||||||
|
|
||||||
|
expect(files, completes);
|
||||||
|
expect(await files, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('getImageFromSource - returns null', (WidgetTester _) async {
|
||||||
|
final Future<XFile?> file = plugin.getImageFromSource(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
);
|
||||||
|
mockCancel();
|
||||||
|
|
||||||
|
expect(file, completes);
|
||||||
|
expect(await file, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('getVideo - returns null', (WidgetTester _) async {
|
||||||
|
final Future<XFile?> file = plugin.getVideo(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
);
|
||||||
|
mockCancel();
|
||||||
|
|
||||||
|
expect(file, completes);
|
||||||
|
expect(await file, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('computeCaptureAttribute', (WidgetTester tester) async {
|
testWidgets('computeCaptureAttribute', (WidgetTester tester) async {
|
||||||
expect(
|
expect(
|
||||||
@ -208,4 +256,102 @@ void main() {
|
|||||||
expect(input.attributes, contains('multiple'));
|
expect(input.attributes, contains('multiple'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Deprecated methods', () {
|
||||||
|
late html.FileUploadInputElement mockInput;
|
||||||
|
late ImagePickerPluginTestOverrides overrides;
|
||||||
|
late ImagePickerPlugin plugin;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockInput = html.FileUploadInputElement();
|
||||||
|
overrides = ImagePickerPluginTestOverrides()
|
||||||
|
..createInputElement = ((_, __) => mockInput)
|
||||||
|
..getMultipleFilesFromInput = ((_) => <html.File>[textFile]);
|
||||||
|
plugin = ImagePickerPlugin(overrides: overrides);
|
||||||
|
});
|
||||||
|
|
||||||
|
void mockCancel() {
|
||||||
|
mockInput.dispatchEvent(html.Event('cancel'));
|
||||||
|
}
|
||||||
|
|
||||||
|
void mockChange() {
|
||||||
|
mockInput.dispatchEvent(html.Event('change'));
|
||||||
|
}
|
||||||
|
|
||||||
|
group('getImage', () {
|
||||||
|
testWidgets('can select a file', (WidgetTester _) async {
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
final Future<XFile?> image = plugin.getImage(
|
||||||
|
source: ImageSource.camera,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock the browser behavior when selecting a file...
|
||||||
|
mockChange();
|
||||||
|
|
||||||
|
// Now the file should be available
|
||||||
|
expect(image, completes);
|
||||||
|
|
||||||
|
// And readable
|
||||||
|
final XFile? file = await image;
|
||||||
|
expect(file, isNotNull);
|
||||||
|
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('returns null when canceled', (WidgetTester _) async {
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
final Future<XFile?> file = plugin.getImage(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
);
|
||||||
|
mockCancel();
|
||||||
|
|
||||||
|
expect(file, completes);
|
||||||
|
expect(await file, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('getMultiImage', () {
|
||||||
|
testWidgets('can select multiple files', (WidgetTester _) async {
|
||||||
|
// Override the returned files...
|
||||||
|
overrides.getMultipleFilesFromInput =
|
||||||
|
(_) => <html.File>[textFile, secondTextFile];
|
||||||
|
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
final Future<List<XFile>> files = plugin.getMultiImage();
|
||||||
|
|
||||||
|
// Mock the browser behavior of selecting a file...
|
||||||
|
mockChange();
|
||||||
|
|
||||||
|
// Now the file should be available
|
||||||
|
expect(files, completes);
|
||||||
|
|
||||||
|
// And readable
|
||||||
|
expect((await files).first.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));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('returns an empty list when canceled', (
|
||||||
|
WidgetTester _,
|
||||||
|
) async {
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
final Future<List<XFile>?> files = plugin.getMultiImage();
|
||||||
|
mockCancel();
|
||||||
|
|
||||||
|
expect(files, completes);
|
||||||
|
expect(await files, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -42,102 +42,47 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
|||||||
ImagePickerPlatform.instance = ImagePickerPlugin();
|
ImagePickerPlatform.instance = ImagePickerPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [PickedFile] with the image that was picked.
|
/// Returns an [XFile] with the image that was picked, or `null` if no images were picked.
|
||||||
///
|
|
||||||
/// The `source` argument controls where the image comes from. This can
|
|
||||||
/// be either [ImageSource.camera] or [ImageSource.gallery].
|
|
||||||
///
|
|
||||||
/// Note that the `maxWidth`, `maxHeight` and `imageQuality` arguments are not supported on the web. If any of these arguments is supplied, it'll be silently ignored by the web version of the plugin.
|
|
||||||
///
|
|
||||||
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
|
|
||||||
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
|
|
||||||
/// Defaults to [CameraDevice.rear].
|
|
||||||
///
|
|
||||||
/// If no images were picked, the return value is null.
|
|
||||||
@override
|
@override
|
||||||
Future<PickedFile> pickImage({
|
Future<XFile?> getImageFromSource({
|
||||||
required ImageSource source,
|
required ImageSource source,
|
||||||
double? maxWidth,
|
ImagePickerOptions options = const ImagePickerOptions(),
|
||||||
double? maxHeight,
|
|
||||||
int? imageQuality,
|
|
||||||
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
|
||||||
}) {
|
|
||||||
final String? capture =
|
|
||||||
computeCaptureAttribute(source, preferredCameraDevice);
|
|
||||||
return pickFile(accept: _kAcceptImageMimeType, capture: capture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a [PickedFile] containing the video that was picked.
|
|
||||||
///
|
|
||||||
/// The [source] argument controls where the video comes from. This can
|
|
||||||
/// be either [ImageSource.camera] or [ImageSource.gallery].
|
|
||||||
///
|
|
||||||
/// Note that the `maxDuration` argument is not supported on the web. If the argument is supplied, it'll be silently ignored by the web version of the plugin.
|
|
||||||
///
|
|
||||||
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
|
|
||||||
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
|
|
||||||
/// Defaults to [CameraDevice.rear].
|
|
||||||
///
|
|
||||||
/// If no images were picked, the return value is null.
|
|
||||||
@override
|
|
||||||
Future<PickedFile> pickVideo({
|
|
||||||
required ImageSource source,
|
|
||||||
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
|
||||||
Duration? maxDuration,
|
|
||||||
}) {
|
|
||||||
final String? capture =
|
|
||||||
computeCaptureAttribute(source, preferredCameraDevice);
|
|
||||||
return pickFile(accept: _kAcceptVideoMimeType, capture: capture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Injects a file input with the specified accept+capture attributes, and
|
|
||||||
/// returns the PickedFile that the user selected locally.
|
|
||||||
///
|
|
||||||
/// `capture` is only supported in mobile browsers.
|
|
||||||
/// See https://caniuse.com/#feat=html-media-capture
|
|
||||||
@visibleForTesting
|
|
||||||
Future<PickedFile> pickFile({
|
|
||||||
String? accept,
|
|
||||||
String? capture,
|
|
||||||
}) {
|
|
||||||
final html.FileUploadInputElement input =
|
|
||||||
createInputElement(accept, capture) as html.FileUploadInputElement;
|
|
||||||
_injectAndActivate(input);
|
|
||||||
return _getSelectedFile(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an [XFile] with the image that was picked.
|
|
||||||
///
|
|
||||||
/// The `source` argument controls where the image comes from. This can
|
|
||||||
/// be either [ImageSource.camera] or [ImageSource.gallery].
|
|
||||||
///
|
|
||||||
/// Note that the `maxWidth`, `maxHeight` and `imageQuality` arguments are not supported on the web. If any of these arguments is supplied, it'll be silently ignored by the web version of the plugin.
|
|
||||||
///
|
|
||||||
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
|
|
||||||
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
|
|
||||||
/// Defaults to [CameraDevice.rear].
|
|
||||||
///
|
|
||||||
/// If no images were picked, the return value is null.
|
|
||||||
@override
|
|
||||||
Future<XFile> getImage({
|
|
||||||
required ImageSource source,
|
|
||||||
double? maxWidth,
|
|
||||||
double? maxHeight,
|
|
||||||
int? imageQuality,
|
|
||||||
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
|
||||||
}) async {
|
}) async {
|
||||||
final String? capture =
|
final String? capture =
|
||||||
computeCaptureAttribute(source, preferredCameraDevice);
|
computeCaptureAttribute(source, options.preferredCameraDevice);
|
||||||
final List<XFile> files = await getFiles(
|
final List<XFile> files = await getFiles(
|
||||||
accept: _kAcceptImageMimeType,
|
accept: _kAcceptImageMimeType,
|
||||||
capture: capture,
|
capture: capture,
|
||||||
);
|
);
|
||||||
return _imageResizer.resizeImageIfNeeded(
|
return files.isEmpty
|
||||||
files.first,
|
? null
|
||||||
maxWidth,
|
: _imageResizer.resizeImageIfNeeded(
|
||||||
maxHeight,
|
files.first,
|
||||||
imageQuality,
|
options.maxWidth,
|
||||||
|
options.maxHeight,
|
||||||
|
options.imageQuality,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [List<XFile>] with the images that were picked, if any.
|
||||||
|
@override
|
||||||
|
Future<List<XFile>> getMultiImageWithOptions({
|
||||||
|
MultiImagePickerOptions options = const MultiImagePickerOptions(),
|
||||||
|
}) async {
|
||||||
|
final List<XFile> images = await getFiles(
|
||||||
|
accept: _kAcceptImageMimeType,
|
||||||
|
multiple: true,
|
||||||
);
|
);
|
||||||
|
final Iterable<Future<XFile>> resized = images.map(
|
||||||
|
(XFile image) => _imageResizer.resizeImageIfNeeded(
|
||||||
|
image,
|
||||||
|
options.imageOptions.maxWidth,
|
||||||
|
options.imageOptions.maxHeight,
|
||||||
|
options.imageOptions.imageQuality,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Future.wait<XFile>(resized);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an [XFile] containing the video that was picked.
|
/// Returns an [XFile] containing the video that was picked.
|
||||||
@ -153,7 +98,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
|||||||
///
|
///
|
||||||
/// If no images were picked, the return value is null.
|
/// If no images were picked, the return value is null.
|
||||||
@override
|
@override
|
||||||
Future<XFile> getVideo({
|
Future<XFile?> getVideo({
|
||||||
required ImageSource source,
|
required ImageSource source,
|
||||||
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
||||||
Duration? maxDuration,
|
Duration? maxDuration,
|
||||||
@ -164,30 +109,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
|||||||
accept: _kAcceptVideoMimeType,
|
accept: _kAcceptVideoMimeType,
|
||||||
capture: capture,
|
capture: capture,
|
||||||
);
|
);
|
||||||
return files.first;
|
return files.isEmpty ? null : files.first;
|
||||||
}
|
|
||||||
|
|
||||||
/// Injects a file input, and returns a list of XFile images that the user selected locally.
|
|
||||||
@override
|
|
||||||
Future<List<XFile>> getMultiImage({
|
|
||||||
double? maxWidth,
|
|
||||||
double? maxHeight,
|
|
||||||
int? imageQuality,
|
|
||||||
}) async {
|
|
||||||
final List<XFile> images = await getFiles(
|
|
||||||
accept: _kAcceptImageMimeType,
|
|
||||||
multiple: true,
|
|
||||||
);
|
|
||||||
final Iterable<Future<XFile>> resized = images.map(
|
|
||||||
(XFile image) => _imageResizer.resizeImageIfNeeded(
|
|
||||||
image,
|
|
||||||
maxWidth,
|
|
||||||
maxHeight,
|
|
||||||
imageQuality,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Future.wait<XFile>(resized);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Injects a file input, and returns a list of XFile media that the user selected locally.
|
/// Injects a file input, and returns a list of XFile media that the user selected locally.
|
||||||
@ -239,6 +161,58 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
|||||||
return _getSelectedXFiles(input);
|
return _getSelectedXFiles(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated methods follow...
|
||||||
|
|
||||||
|
/// Returns an [XFile] with the image that was picked.
|
||||||
|
///
|
||||||
|
/// The `source` argument controls where the image comes from. This can
|
||||||
|
/// be either [ImageSource.camera] or [ImageSource.gallery].
|
||||||
|
///
|
||||||
|
/// Note that the `maxWidth`, `maxHeight` and `imageQuality` arguments are not supported on the web. If any of these arguments is supplied, it'll be silently ignored by the web version of the plugin.
|
||||||
|
///
|
||||||
|
/// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera].
|
||||||
|
/// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device.
|
||||||
|
/// Defaults to [CameraDevice.rear].
|
||||||
|
///
|
||||||
|
/// If no images were picked, the return value is null.
|
||||||
|
@override
|
||||||
|
@Deprecated('Use getImageFromSource instead.')
|
||||||
|
Future<XFile?> getImage({
|
||||||
|
required ImageSource source,
|
||||||
|
double? maxWidth,
|
||||||
|
double? maxHeight,
|
||||||
|
int? imageQuality,
|
||||||
|
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
||||||
|
}) async {
|
||||||
|
return getImageFromSource(
|
||||||
|
source: source,
|
||||||
|
options: ImagePickerOptions(
|
||||||
|
maxWidth: maxWidth,
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
imageQuality: imageQuality,
|
||||||
|
preferredCameraDevice: preferredCameraDevice,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects a file input, and returns a list of XFile images that the user selected locally.
|
||||||
|
@override
|
||||||
|
@Deprecated('Use getMultiImageWithOptions instead.')
|
||||||
|
Future<List<XFile>> getMultiImage({
|
||||||
|
double? maxWidth,
|
||||||
|
double? maxHeight,
|
||||||
|
int? imageQuality,
|
||||||
|
}) async {
|
||||||
|
return getMultiImageWithOptions(
|
||||||
|
options: MultiImagePickerOptions(
|
||||||
|
imageOptions: ImageOptions(
|
||||||
|
maxWidth: maxWidth,
|
||||||
|
maxHeight: maxHeight,
|
||||||
|
imageQuality: imageQuality,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// DOM methods
|
// DOM methods
|
||||||
|
|
||||||
/// Converts plugin configuration into a proper value for the `capture` attribute.
|
/// Converts plugin configuration into a proper value for the `capture` attribute.
|
||||||
@ -267,29 +241,6 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
|||||||
return input == null ? null : _getFilesFromInput(input);
|
return input == null ? null : _getFilesFromInput(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Monitors an <input type="file"> and returns the selected file.
|
|
||||||
Future<PickedFile> _getSelectedFile(html.FileUploadInputElement input) {
|
|
||||||
final Completer<PickedFile> completer = Completer<PickedFile>();
|
|
||||||
// Observe the input until we can return something
|
|
||||||
input.onChange.first.then((html.Event event) {
|
|
||||||
final List<html.File>? files = _handleOnChangeEvent(event);
|
|
||||||
if (!completer.isCompleted && files != null) {
|
|
||||||
completer.complete(PickedFile(
|
|
||||||
html.Url.createObjectUrl(files.first),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
input.onError.first.then((html.Event 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Monitors an <input type="file"> and returns the selected file(s).
|
/// Monitors an <input type="file"> and returns the selected file(s).
|
||||||
Future<List<XFile>> _getSelectedXFiles(html.FileUploadInputElement input) {
|
Future<List<XFile>> _getSelectedXFiles(html.FileUploadInputElement input) {
|
||||||
final Completer<List<XFile>> completer = Completer<List<XFile>>();
|
final Completer<List<XFile>> completer = Completer<List<XFile>>();
|
||||||
@ -310,6 +261,11 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
|||||||
}).toList());
|
}).toList());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
input.addEventListener('cancel', (html.Event _) {
|
||||||
|
completer.complete(<XFile>[]);
|
||||||
|
});
|
||||||
|
|
||||||
input.onError.first.then((html.Event event) {
|
input.onError.first.then((html.Event event) {
|
||||||
if (!completer.isCompleted) {
|
if (!completer.isCompleted) {
|
||||||
completer.completeError(event);
|
completer.completeError(event);
|
||||||
@ -361,6 +317,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
|||||||
void _injectAndActivate(html.Element element) {
|
void _injectAndActivate(html.Element element) {
|
||||||
_target.children.clear();
|
_target.children.clear();
|
||||||
_target.children.add(element);
|
_target.children.add(element);
|
||||||
|
// TODO(dit): Reimplement this with the showPicker() API, https://github.com/flutter/flutter/issues/130365
|
||||||
element.click();
|
element.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ name: image_picker_for_web
|
|||||||
description: Web platform implementation of image_picker
|
description: Web platform implementation of image_picker
|
||||||
repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_for_web
|
repository: https://github.com/flutter/packages/tree/main/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
|
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
|
||||||
version: 2.2.0
|
version: 3.0.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.18.0 <4.0.0"
|
sdk: ">=2.18.0 <4.0.0"
|
||||||
@ -21,7 +21,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_web_plugins:
|
flutter_web_plugins:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
image_picker_platform_interface: ^2.8.0
|
image_picker_platform_interface: ^2.9.0
|
||||||
mime: ^1.0.4
|
mime: ^1.0.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
Reference in New Issue
Block a user