mirror of
https://github.com/flutter/packages.git
synced 2025-06-30 14:47:22 +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
|
||||
|
||||
* Adds `getMedia` method.
|
||||
|
@ -4,23 +4,12 @@ A web implementation of [`image_picker`][1].
|
||||
|
||||
## Limitations on the web platform
|
||||
|
||||
Since Web Browsers don't offer direct access to their users' file system,
|
||||
this plugin provides a `PickedFile` abstraction to make access uniform
|
||||
across platforms.
|
||||
### `XFile`
|
||||
|
||||
The web version of the plugin puts network-accessible URIs as the `path`
|
||||
in the returned `PickedFile`.
|
||||
This plugin uses `XFile` objects to abstract files picked/created by the user.
|
||||
|
||||
### URL.createObjectURL()
|
||||
|
||||
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.
|
||||
Read more about `XFile` on the web in
|
||||
[`package:cross_file`'s README](https://pub.dev/packages/cross_file).
|
||||
|
||||
### 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
|
||||
difference in your users' experience.
|
||||
|
||||
### pickImage()
|
||||
The arguments `maxWidth`, `maxHeight` and `imageQuality` are not supported for gif images.
|
||||
The argument `imageQuality` only works for jpeg and webp images.
|
||||
### input file "cancel"
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
Once the user has picked a file, the returned `PickedFile` instance will contain a
|
||||
`network`-accessible URL (pointing to a location within the browser).
|
||||
Once the user has picked a file, the returned `XFile` instance will contain a
|
||||
`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.
|
||||
|
||||
|
@ -33,7 +33,9 @@ void main() {
|
||||
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 ImagePickerPluginTestOverrides overrides =
|
||||
@ -44,29 +46,9 @@ void main() {
|
||||
final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
|
||||
|
||||
// Init the pick file dialog...
|
||||
final Future<PickedFile> file = plugin.pickFile();
|
||||
|
||||
// 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);
|
||||
final Future<XFile?> image = plugin.getImageFromSource(
|
||||
source: ImageSource.camera,
|
||||
);
|
||||
|
||||
// Mock the browser behavior of selecting a file...
|
||||
mockInput.dispatchEvent(html.Event('change'));
|
||||
@ -75,8 +57,9 @@ void main() {
|
||||
expect(image, completes);
|
||||
|
||||
// And readable
|
||||
final XFile file = await image;
|
||||
expect(file.readAsBytes(), completion(isNotEmpty));
|
||||
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);
|
||||
@ -87,8 +70,9 @@ void main() {
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('getMultiImage can select multiple files',
|
||||
(WidgetTester tester) async {
|
||||
testWidgets('getMultiImageWithOptions can select multiple files', (
|
||||
WidgetTester _,
|
||||
) async {
|
||||
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
||||
|
||||
final ImagePickerPluginTestOverrides overrides =
|
||||
@ -100,7 +84,7 @@ void main() {
|
||||
final ImagePickerPlugin plugin = ImagePickerPlugin(overrides: overrides);
|
||||
|
||||
// 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...
|
||||
mockInput.dispatchEvent(html.Event('change'));
|
||||
@ -118,8 +102,7 @@ void main() {
|
||||
expect(secondFile.length(), completion(secondTextFile.size));
|
||||
});
|
||||
|
||||
testWidgets('getMedia can select multiple files',
|
||||
(WidgetTester tester) async {
|
||||
testWidgets('getMedia can select multiple files', (WidgetTester _) async {
|
||||
final html.FileUploadInputElement mockInput = html.FileUploadInputElement();
|
||||
|
||||
final ImagePickerPluginTestOverrides overrides =
|
||||
@ -150,7 +133,72 @@ void main() {
|
||||
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 {
|
||||
expect(
|
||||
@ -208,4 +256,102 @@ void main() {
|
||||
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();
|
||||
}
|
||||
|
||||
/// Returns a [PickedFile] 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.
|
||||
/// Returns an [XFile] with the image that was picked, or `null` if no images were picked.
|
||||
@override
|
||||
Future<PickedFile> pickImage({
|
||||
Future<XFile?> getImageFromSource({
|
||||
required ImageSource source,
|
||||
double? maxWidth,
|
||||
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,
|
||||
ImagePickerOptions options = const ImagePickerOptions(),
|
||||
}) async {
|
||||
final String? capture =
|
||||
computeCaptureAttribute(source, preferredCameraDevice);
|
||||
computeCaptureAttribute(source, options.preferredCameraDevice);
|
||||
final List<XFile> files = await getFiles(
|
||||
accept: _kAcceptImageMimeType,
|
||||
capture: capture,
|
||||
);
|
||||
return _imageResizer.resizeImageIfNeeded(
|
||||
files.first,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
imageQuality,
|
||||
return files.isEmpty
|
||||
? null
|
||||
: _imageResizer.resizeImageIfNeeded(
|
||||
files.first,
|
||||
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.
|
||||
@ -153,7 +98,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
||||
///
|
||||
/// If no images were picked, the return value is null.
|
||||
@override
|
||||
Future<XFile> getVideo({
|
||||
Future<XFile?> getVideo({
|
||||
required ImageSource source,
|
||||
CameraDevice preferredCameraDevice = CameraDevice.rear,
|
||||
Duration? maxDuration,
|
||||
@ -164,30 +109,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
||||
accept: _kAcceptVideoMimeType,
|
||||
capture: capture,
|
||||
);
|
||||
return 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);
|
||||
return files.isEmpty ? null : files.first;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// 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).
|
||||
Future<List<XFile>> _getSelectedXFiles(html.FileUploadInputElement input) {
|
||||
final Completer<List<XFile>> completer = Completer<List<XFile>>();
|
||||
@ -310,6 +261,11 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
||||
}).toList());
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener('cancel', (html.Event _) {
|
||||
completer.complete(<XFile>[]);
|
||||
});
|
||||
|
||||
input.onError.first.then((html.Event event) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.completeError(event);
|
||||
@ -361,6 +317,7 @@ class ImagePickerPlugin extends ImagePickerPlatform {
|
||||
void _injectAndActivate(html.Element element) {
|
||||
_target.children.clear();
|
||||
_target.children.add(element);
|
||||
// TODO(dit): Reimplement this with the showPicker() API, https://github.com/flutter/flutter/issues/130365
|
||||
element.click();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ name: image_picker_for_web
|
||||
description: Web platform implementation of image_picker
|
||||
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
|
||||
version: 2.2.0
|
||||
version: 3.0.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <4.0.0"
|
||||
@ -21,7 +21,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_web_plugins:
|
||||
sdk: flutter
|
||||
image_picker_platform_interface: ^2.8.0
|
||||
image_picker_platform_interface: ^2.9.0
|
||||
mime: ^1.0.4
|
||||
|
||||
dev_dependencies:
|
||||
|
Reference in New Issue
Block a user