[image_picker] add requestFullMetadata for iOS (optional permissions) - platform interface changes for multi image picking (#5914)
Platform interface changes for #5915 - adding possibility to disable full metadata when picking multiple images
diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
index 0a4e98b..120b7b0 100644
--- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
+++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 2.6.0
+
+* Deprecates `getMultiImage` in favor of a new method `getMultiImageWithOptions`.
+ * Adds `requestFullMetadata` option that allows disabling extra permission requests
+ on certain platforms.
+ * Moves optional image picking parameters to `MultiImagePickerOptions` class.
+
## 2.5.0
* Deprecates `getImage` in favor of a new method `getImageFromSource`.
diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
index ba5d60d..d215fa2 100644
--- a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
+++ b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart
@@ -8,6 +8,7 @@
import 'package:flutter/services.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+import 'package:image_picker_platform_interface/src/types/multi_image_picker_options.dart';
const MethodChannel _channel = MethodChannel('plugins.flutter.io/image_picker');
@@ -57,6 +58,7 @@
double? maxWidth,
double? maxHeight,
int? imageQuality,
+ bool requestFullMetadata = true,
}) {
if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
throw ArgumentError.value(
@@ -77,6 +79,7 @@
'maxWidth': maxWidth,
'maxHeight': maxHeight,
'imageQuality': imageQuality,
+ 'requestFullMetadata': requestFullMetadata,
},
);
}
@@ -234,6 +237,23 @@
}
@override
+ Future<List<XFile>> getMultiImageWithOptions({
+ MultiImagePickerOptions options = const MultiImagePickerOptions(),
+ }) async {
+ final List<dynamic>? paths = await _getMultiImagePath(
+ maxWidth: options.imageOptions.maxWidth,
+ maxHeight: options.imageOptions.maxHeight,
+ imageQuality: options.imageOptions.imageQuality,
+ requestFullMetadata: options.imageOptions.requestFullMetadata,
+ );
+ if (paths == null) {
+ return <XFile>[];
+ }
+
+ return paths.map((dynamic path) => XFile(path as String)).toList();
+ }
+
+ @override
Future<XFile?> getVideo({
required ImageSource source,
CameraDevice preferredCameraDevice = CameraDevice.rear,
diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
index d1d06f9..a2618d5 100644
--- a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
+++ b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart
@@ -6,6 +6,7 @@
import 'package:cross_file/cross_file.dart';
import 'package:image_picker_platform_interface/src/method_channel/method_channel_image_picker.dart';
+import 'package:image_picker_platform_interface/src/types/multi_image_picker_options.dart';
import 'package:image_picker_platform_interface/src/types/types.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
@@ -186,6 +187,8 @@
throw UnimplementedError('getImage() has not been implemented.');
}
+ /// This method is deprecated in favor of [getMultiImageWithOptions] and will be removed in a future update.
+ ///
/// Returns a [List<XFile>] with the images that were picked.
///
/// The images come from the [ImageSource.gallery].
@@ -283,4 +286,23 @@
preferredCameraDevice: options.preferredCameraDevice,
);
}
+
+ /// Returns a [List<XFile>] with the images that were picked.
+ ///
+ /// The images come from the [ImageSource.gallery].
+ ///
+ /// The `options` argument controls additional settings that can be used when
+ /// picking an image. See [MultiImagePickerOptions] for more details.
+ ///
+ /// If no images were picked, returns an empty list.
+ Future<List<XFile>> getMultiImageWithOptions({
+ MultiImagePickerOptions options = const MultiImagePickerOptions(),
+ }) async {
+ final List<XFile>? pickedImages = await getMultiImage(
+ maxWidth: options.imageOptions.maxWidth,
+ maxHeight: options.imageOptions.maxHeight,
+ imageQuality: options.imageOptions.imageQuality,
+ );
+ return pickedImages ?? <XFile>[];
+ }
}
diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/image_options.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/image_options.dart
new file mode 100644
index 0000000..2cc01c9
--- /dev/null
+++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/image_options.dart
@@ -0,0 +1,41 @@
+// 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.
+
+/// Specifies image-specific options for picking.
+class ImageOptions {
+ /// Creates an instance with the given [maxHeight], [maxWidth], [imageQuality]
+ /// and [requestFullMetadata].
+ const ImageOptions({
+ this.maxHeight,
+ this.maxWidth,
+ this.imageQuality,
+ 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;
+
+ /// 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;
+}
diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/multi_image_picker_options.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/multi_image_picker_options.dart
new file mode 100644
index 0000000..4d7971c
--- /dev/null
+++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/multi_image_picker_options.dart
@@ -0,0 +1,16 @@
+// 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:image_picker_platform_interface/src/types/image_options.dart';
+
+/// Specifies options for picking multiple images from the device's gallery.
+class MultiImagePickerOptions {
+ /// Creates an instance with the given [imageOptions].
+ const MultiImagePickerOptions({
+ this.imageOptions = const ImageOptions(),
+ });
+
+ /// The image-specific options for picking.
+ final ImageOptions imageOptions;
+}
diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml
index 4ce1d2f..50d84f8 100644
--- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml
+++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml
@@ -4,7 +4,7 @@
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.5.0
+version: 2.6.0
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart
index 72ed363..27d7016 100644
--- a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart
+++ b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart
@@ -7,6 +7,8 @@
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:image_picker_platform_interface/src/method_channel/method_channel_image_picker.dart';
+import 'package:image_picker_platform_interface/src/types/image_options.dart';
+import 'package:image_picker_platform_interface/src/types/multi_image_picker_options.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -244,6 +246,7 @@
'maxWidth': null,
'maxHeight': null,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
],
);
@@ -283,36 +286,43 @@
'maxWidth': null,
'maxHeight': null,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': null,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': null,
'maxHeight': 10.0,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': 20.0,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': null,
'imageQuality': 70,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': null,
'maxHeight': 10.0,
'imageQuality': 70,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': 20.0,
'imageQuality': 70,
+ 'requestFullMetadata': true,
}),
],
);
@@ -723,6 +733,7 @@
'maxWidth': null,
'maxHeight': null,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
],
);
@@ -762,36 +773,43 @@
'maxWidth': null,
'maxHeight': null,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': null,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': null,
'maxHeight': 10.0,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': 20.0,
'imageQuality': null,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': null,
'imageQuality': 70,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': null,
'maxHeight': 10.0,
'imageQuality': 70,
+ 'requestFullMetadata': true,
}),
isMethodCall('pickMultiImage', arguments: <String, dynamic>{
'maxWidth': 10.0,
'maxHeight': 20.0,
'imageQuality': 70,
+ 'requestFullMetadata': true,
}),
],
);
@@ -1257,5 +1275,211 @@
);
});
});
+
+ group('#getMultiImageWithOptions', () {
+ test('calls the method correctly', () async {
+ returnValue = <dynamic>['0', '1'];
+ await picker.getMultiImageWithOptions();
+
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'requestFullMetadata': true,
+ }),
+ ],
+ );
+ });
+
+ test('passes the width, height and imageQuality arguments correctly',
+ () async {
+ returnValue = <dynamic>['0', '1'];
+ await picker.getMultiImageWithOptions();
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(maxWidth: 10.0),
+ ),
+ );
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(maxHeight: 10.0),
+ ),
+ );
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ ),
+ ),
+ );
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(
+ maxWidth: 10.0,
+ imageQuality: 70,
+ ),
+ ),
+ );
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(
+ maxHeight: 10.0,
+ imageQuality: 70,
+ ),
+ ),
+ );
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ imageQuality: 70,
+ ),
+ ),
+ );
+
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'requestFullMetadata': true,
+ }),
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'requestFullMetadata': true,
+ }),
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': null,
+ 'requestFullMetadata': true,
+ }),
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': null,
+ 'requestFullMetadata': true,
+ }),
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': 70,
+ 'requestFullMetadata': true,
+ }),
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': 70,
+ 'requestFullMetadata': true,
+ }),
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': 70,
+ 'requestFullMetadata': true,
+ }),
+ ],
+ );
+ });
+
+ test('does not accept a negative width or height argument', () {
+ returnValue = <dynamic>['0', '1'];
+ expect(
+ () => picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(maxWidth: -1.0),
+ ),
+ ),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(maxHeight: -1.0),
+ ),
+ ),
+ throwsArgumentError,
+ );
+ });
+
+ test('does not accept an invalid imageQuality argument', () {
+ returnValue = <dynamic>['0', '1'];
+ expect(
+ () => picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(imageQuality: -1),
+ ),
+ ),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(imageQuality: 101),
+ ),
+ ),
+ throwsArgumentError,
+ );
+ });
+
+ test('handles a null image path response gracefully', () async {
+ picker.channel
+ .setMockMethodCallHandler((MethodCall methodCall) => null);
+
+ expect(await picker.getMultiImage(), isNull);
+ expect(await picker.getMultiImage(), isNull);
+ });
+
+ test('Request full metadata argument defaults to true', () async {
+ returnValue = <dynamic>['0', '1'];
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(),
+ );
+
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'requestFullMetadata': true,
+ }),
+ ],
+ );
+ });
+
+ test('passes the request full metadata argument correctly', () async {
+ returnValue = <dynamic>['0', '1'];
+ await picker.getMultiImageWithOptions(
+ options: const MultiImagePickerOptions(
+ imageOptions: ImageOptions(requestFullMetadata: false),
+ ),
+ );
+
+ expect(
+ log,
+ <Matcher>[
+ isMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'requestFullMetadata': false,
+ }),
+ ],
+ );
+ });
+ });
});
}