mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 23:51:55 +08:00
[image_picker] getMedia platform changes (#4174)
Adds `getMedia` and `getMultipleMedia` methods to image_picker_platform_interface. precursor to https://github.com/flutter/packages/pull/3892 part of https://github.com/flutter/flutter/issues/89159
This commit is contained in:
@ -1,3 +1,7 @@
|
||||
## 2.8.0
|
||||
|
||||
* Adds `getMedia` method.
|
||||
|
||||
## 2.7.0
|
||||
|
||||
* Adds `CameraDelegatingImagePickerPlatform` as a base class for platform
|
||||
|
@ -252,6 +252,30 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
|
||||
return paths.map((dynamic path) => XFile(path as String)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<XFile>> getMedia({
|
||||
required MediaOptions options,
|
||||
}) async {
|
||||
final ImageOptions imageOptions = options.imageOptions;
|
||||
|
||||
final Map<String, dynamic> args = <String, dynamic>{
|
||||
'maxImageWidth': imageOptions.maxWidth,
|
||||
'maxImageHeight': imageOptions.maxHeight,
|
||||
'imageQuality': imageOptions.imageQuality,
|
||||
'allowMultiple': options.allowMultiple,
|
||||
};
|
||||
|
||||
final List<XFile>? paths = await _channel
|
||||
.invokeMethod<List<dynamic>?>(
|
||||
'pickMedia',
|
||||
args,
|
||||
)
|
||||
.then((List<dynamic>? paths) =>
|
||||
paths?.map((dynamic path) => XFile(path as String)).toList());
|
||||
|
||||
return paths ?? <XFile>[];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<XFile?> getVideo({
|
||||
required ImageSource source,
|
||||
@ -280,13 +304,21 @@ class MethodChannelImagePicker extends ImagePickerPlatform {
|
||||
assert(result.containsKey('path') != result.containsKey('errorCode'));
|
||||
|
||||
final String? type = result['type'] as String?;
|
||||
assert(type == kTypeImage || type == kTypeVideo);
|
||||
assert(
|
||||
type == kTypeImage || type == kTypeVideo || type == kTypeMedia,
|
||||
);
|
||||
|
||||
RetrieveType? retrieveType;
|
||||
if (type == kTypeImage) {
|
||||
retrieveType = RetrieveType.image;
|
||||
} else if (type == kTypeVideo) {
|
||||
retrieveType = RetrieveType.video;
|
||||
switch (type) {
|
||||
case kTypeImage:
|
||||
retrieveType = RetrieveType.image;
|
||||
break;
|
||||
case kTypeVideo:
|
||||
retrieveType = RetrieveType.video;
|
||||
break;
|
||||
case kTypeMedia:
|
||||
retrieveType = RetrieveType.media;
|
||||
break;
|
||||
}
|
||||
|
||||
PlatformException? exception;
|
||||
|
@ -213,6 +213,24 @@ abstract class ImagePickerPlatform extends PlatformInterface {
|
||||
throw UnimplementedError('getMultiImage() has not been implemented.');
|
||||
}
|
||||
|
||||
/// Returns a [List<XFile>] with the images and/or videos that were picked.
|
||||
/// The images and videos come from the gallery.
|
||||
///
|
||||
/// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and
|
||||
/// above only support HEIC images if used in addition to a size modification,
|
||||
/// of which the usage is explained below.
|
||||
///
|
||||
/// In Android, the MainActivity can be destroyed for various reasons.
|
||||
/// If that happens, the result will be lost in this call. You can then
|
||||
/// call [getLostData] when your app relaunches to retrieve the lost data.
|
||||
///
|
||||
/// If no images or videos were picked, the return value is an empty list.
|
||||
Future<List<XFile>> getMedia({
|
||||
required MediaOptions options,
|
||||
}) {
|
||||
throw UnimplementedError('getMedia() has not been implemented.');
|
||||
}
|
||||
|
||||
/// Returns a [XFile] containing the video that was picked.
|
||||
///
|
||||
/// The [source] argument controls where the video comes from. This can
|
||||
|
@ -2,6 +2,40 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
/// Specifies options for picking a single image from the device's camera or gallery.
|
||||
///
|
||||
/// This class inheritance is a byproduct of the api changing over time.
|
||||
/// It exists solely to avoid breaking changes.
|
||||
class ImagePickerOptions extends ImageOptions {
|
||||
/// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality],
|
||||
/// [referredCameraDevice] and [requestFullMetadata].
|
||||
const ImagePickerOptions({
|
||||
super.maxHeight,
|
||||
super.maxWidth,
|
||||
super.imageQuality,
|
||||
super.requestFullMetadata,
|
||||
this.preferredCameraDevice = CameraDevice.rear,
|
||||
}) : super();
|
||||
|
||||
/// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality],
|
||||
/// [referredCameraDevice] and [requestFullMetadata].
|
||||
ImagePickerOptions.createAndValidate({
|
||||
super.maxHeight,
|
||||
super.maxWidth,
|
||||
super.imageQuality,
|
||||
super.requestFullMetadata,
|
||||
this.preferredCameraDevice = CameraDevice.rear,
|
||||
}) : super.createAndValidate();
|
||||
|
||||
/// Used to specify the camera to use when the `source` is [ImageSource.camera].
|
||||
///
|
||||
/// Ignored if the source is not [ImageSource.camera], or the chosen camera is not
|
||||
/// supported on the device. Defaults to [CameraDevice.rear].
|
||||
final CameraDevice preferredCameraDevice;
|
||||
}
|
||||
|
||||
/// Specifies image-specific options for picking.
|
||||
class ImageOptions {
|
||||
/// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality]
|
||||
@ -13,6 +47,18 @@ class ImageOptions {
|
||||
this.requestFullMetadata = true,
|
||||
});
|
||||
|
||||
/// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality]
|
||||
/// and [requestFullMetadata]. Throws if options are not valid.
|
||||
ImageOptions.createAndValidate({
|
||||
this.maxHeight,
|
||||
this.maxWidth,
|
||||
this.imageQuality,
|
||||
this.requestFullMetadata = true,
|
||||
}) {
|
||||
_validateOptions(
|
||||
maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality);
|
||||
}
|
||||
|
||||
/// The maximum width of the image, in pixels.
|
||||
///
|
||||
/// If null, the image will only be resized if [maxHeight] is specified.
|
||||
@ -38,4 +84,19 @@ class ImageOptions {
|
||||
//
|
||||
// Defaults to true.
|
||||
final bool requestFullMetadata;
|
||||
|
||||
/// Validates that all values are within required ranges. Throws if not.
|
||||
static void _validateOptions(
|
||||
{double? maxWidth, final double? maxHeight, int? imageQuality}) {
|
||||
if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
|
||||
throw ArgumentError.value(
|
||||
imageQuality, 'imageQuality', 'must be between 0 and 100');
|
||||
}
|
||||
if (maxWidth != null && maxWidth < 0) {
|
||||
throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
|
||||
}
|
||||
if (maxHeight != null && maxHeight < 0) {
|
||||
throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +0,0 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'types.dart';
|
||||
|
||||
/// Specifies options for picking a single image from the device's camera or gallery.
|
||||
class ImagePickerOptions {
|
||||
/// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality],
|
||||
/// [referredCameraDevice] and [requestFullMetadata].
|
||||
const ImagePickerOptions({
|
||||
this.maxHeight,
|
||||
this.maxWidth,
|
||||
this.imageQuality,
|
||||
this.preferredCameraDevice = CameraDevice.rear,
|
||||
this.requestFullMetadata = true,
|
||||
});
|
||||
|
||||
/// The maximum width of the image, in pixels.
|
||||
///
|
||||
/// If null, the image will only be resized if [maxHeight] is specified.
|
||||
final double? maxWidth;
|
||||
|
||||
/// The maximum height of the image, in pixels.
|
||||
///
|
||||
/// If null, the image will only be resized if [maxWidth] is specified.
|
||||
final double? maxHeight;
|
||||
|
||||
/// Modifies the quality of the image, ranging from 0-100 where 100 is the
|
||||
/// original/max quality.
|
||||
///
|
||||
/// Compression is only supported for certain image types such as JPEG. If
|
||||
/// compression is not supported for the image that is picked, a warning
|
||||
/// message will be logged.
|
||||
///
|
||||
/// If null, the image will be returned with the original quality.
|
||||
final int? imageQuality;
|
||||
|
||||
/// Used to specify the camera to use when the `source` is [ImageSource.camera].
|
||||
///
|
||||
/// Ignored if the source is not [ImageSource.camera], or the chosen camera is not
|
||||
/// supported on the device. Defaults to [CameraDevice.rear].
|
||||
final CameraDevice preferredCameraDevice;
|
||||
|
||||
/// If true, requests full image metadata, which may require extra permissions
|
||||
/// on some platforms, (e.g., NSPhotoLibraryUsageDescription on iOS).
|
||||
//
|
||||
// Defaults to true.
|
||||
final bool requestFullMetadata;
|
||||
}
|
@ -36,7 +36,8 @@ class LostDataResponse {
|
||||
/// An empty response should have [file], [exception] and [type] to be null.
|
||||
bool get isEmpty => _empty;
|
||||
|
||||
/// The file that was lost in a previous [getImage], [getMultiImage] or [getVideo] call due to MainActivity being destroyed.
|
||||
/// The file that was lost in a previous [getImage], [getMultiImage],
|
||||
/// [getVideo] or [getMedia] call due to MainActivity being destroyed.
|
||||
///
|
||||
/// Can be null if [exception] exists.
|
||||
final XFile? file;
|
||||
@ -51,7 +52,7 @@ class LostDataResponse {
|
||||
/// Note that it is not the exception that caused the destruction of the MainActivity.
|
||||
final PlatformException? exception;
|
||||
|
||||
/// Can either be [RetrieveType.image] or [RetrieveType.video];
|
||||
/// Can either be [RetrieveType.image], [RetrieveType.video], or [RetrieveType.media].
|
||||
///
|
||||
/// If the lost data is empty, this will be null.
|
||||
final RetrieveType? type;
|
||||
|
@ -0,0 +1,23 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../image_picker_platform_interface.dart';
|
||||
|
||||
/// Specifies options for selecting items when using [ImagePickerPlatform.getMedia].
|
||||
@immutable
|
||||
class MediaOptions {
|
||||
/// Construct a new MediaOptions instance.
|
||||
const MediaOptions({
|
||||
this.imageOptions = const ImageOptions(),
|
||||
required this.allowMultiple,
|
||||
});
|
||||
|
||||
/// Options that will apply to images upon selection.
|
||||
final ImageOptions imageOptions;
|
||||
|
||||
/// Whether to allow for selecting multiple media.
|
||||
final bool allowMultiple;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../../image_picker_platform_interface.dart';
|
||||
|
||||
/// The type of media to allow the user to select with [ImagePickerPlatform.getMedia].
|
||||
enum MediaSelectionType {
|
||||
/// Static pictures.
|
||||
image,
|
||||
|
||||
/// Videos.
|
||||
video,
|
||||
}
|
@ -8,5 +8,8 @@ enum RetrieveType {
|
||||
image,
|
||||
|
||||
/// A video. See [ImagePicker.pickVideo].
|
||||
video
|
||||
video,
|
||||
|
||||
/// Either a video or a static picture. See [ImagePicker.pickMedia].
|
||||
media,
|
||||
}
|
||||
|
@ -5,9 +5,10 @@
|
||||
export 'camera_delegate.dart';
|
||||
export 'camera_device.dart';
|
||||
export 'image_options.dart';
|
||||
export 'image_picker_options.dart';
|
||||
export 'image_source.dart';
|
||||
export 'lost_data_response.dart';
|
||||
export 'media_options.dart';
|
||||
export 'media_selection_type.dart';
|
||||
export 'multi_image_picker_options.dart';
|
||||
export 'picked_file/picked_file.dart';
|
||||
export 'retrieve_type.dart';
|
||||
@ -17,3 +18,6 @@ const String kTypeImage = 'image';
|
||||
|
||||
/// Denotes that a video is being picked.
|
||||
const String kTypeVideo = 'video';
|
||||
|
||||
/// Denotes that either a video or image is being picked.
|
||||
const String kTypeMedia = 'media';
|
||||
|
@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/image_picker/
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
|
||||
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
|
||||
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
|
||||
version: 2.7.0
|
||||
version: 2.8.0
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <4.0.0"
|
||||
|
@ -872,6 +872,152 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('#getMedia', () {
|
||||
test('calls the method correctly', () async {
|
||||
returnValue = <String>['0'];
|
||||
await picker.getMedia(options: const MediaOptions(allowMultiple: true));
|
||||
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall('pickMedia', arguments: <String, dynamic>{
|
||||
'maxImageWidth': null,
|
||||
'maxImageHeight': null,
|
||||
'imageQuality': null,
|
||||
'allowMultiple': true,
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('passes the selection options correctly', () async {
|
||||
// Default options
|
||||
returnValue = <String>['0'];
|
||||
await picker.getMedia(options: const MediaOptions(allowMultiple: true));
|
||||
// Various image options
|
||||
returnValue = <String>['0'];
|
||||
await picker.getMedia(
|
||||
options: MediaOptions(
|
||||
allowMultiple: true,
|
||||
imageOptions: ImageOptions.createAndValidate(
|
||||
maxWidth: 10.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
await picker.getMedia(
|
||||
options: MediaOptions(
|
||||
allowMultiple: true,
|
||||
imageOptions: ImageOptions.createAndValidate(
|
||||
maxHeight: 10.0,
|
||||
),
|
||||
),
|
||||
);
|
||||
await picker.getMedia(
|
||||
options: MediaOptions(
|
||||
allowMultiple: true,
|
||||
imageOptions: ImageOptions.createAndValidate(
|
||||
imageQuality: 70,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
log,
|
||||
<Matcher>[
|
||||
isMethodCall('pickMedia', arguments: <String, dynamic>{
|
||||
'maxImageWidth': null,
|
||||
'maxImageHeight': null,
|
||||
'imageQuality': null,
|
||||
'allowMultiple': true,
|
||||
}),
|
||||
isMethodCall('pickMedia', arguments: <String, dynamic>{
|
||||
'maxImageWidth': 10.0,
|
||||
'maxImageHeight': null,
|
||||
'imageQuality': null,
|
||||
'allowMultiple': true,
|
||||
}),
|
||||
isMethodCall('pickMedia', arguments: <String, dynamic>{
|
||||
'maxImageWidth': null,
|
||||
'maxImageHeight': 10.0,
|
||||
'imageQuality': null,
|
||||
'allowMultiple': true,
|
||||
}),
|
||||
isMethodCall('pickMedia', arguments: <String, dynamic>{
|
||||
'maxImageWidth': null,
|
||||
'maxImageHeight': null,
|
||||
'imageQuality': 70,
|
||||
'allowMultiple': true,
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test('does not accept a negative width or height argument', () {
|
||||
returnValue = <String>['0', '1'];
|
||||
expect(
|
||||
() => picker.getMedia(
|
||||
options: MediaOptions(
|
||||
allowMultiple: true,
|
||||
imageOptions: ImageOptions.createAndValidate(
|
||||
maxWidth: -1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
|
||||
expect(
|
||||
() => picker.getMedia(
|
||||
options: MediaOptions(
|
||||
allowMultiple: true,
|
||||
imageOptions: ImageOptions.createAndValidate(
|
||||
maxHeight: -1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
|
||||
test('does not accept a invalid imageQuality argument', () {
|
||||
returnValue = <String>['0', '1'];
|
||||
expect(
|
||||
() => picker.getMedia(
|
||||
options: MediaOptions(
|
||||
allowMultiple: true,
|
||||
imageOptions: ImageOptions.createAndValidate(
|
||||
imageQuality: -1,
|
||||
),
|
||||
),
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
|
||||
expect(
|
||||
() => picker.getMedia(
|
||||
options: MediaOptions(
|
||||
allowMultiple: true,
|
||||
imageOptions: ImageOptions.createAndValidate(
|
||||
imageQuality: 101,
|
||||
),
|
||||
),
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
|
||||
test('handles a null path response gracefully', () async {
|
||||
_ambiguate(TestDefaultBinaryMessengerBinding.instance)!
|
||||
.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
picker.channel, (MethodCall methodCall) => null);
|
||||
expect(
|
||||
await picker.getMedia(
|
||||
options: const MediaOptions(allowMultiple: true)),
|
||||
<XFile>[]);
|
||||
});
|
||||
});
|
||||
|
||||
group('#getVideo', () {
|
||||
test('passes the image source argument correctly', () async {
|
||||
await picker.getVideo(source: ImageSource.camera);
|
||||
|
Reference in New Issue
Block a user