diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 36a47b1a3d42..43c49be20504 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.6 + +* Adds `requestFullMetadata` option to `pickImage`, so images on iOS can be picked without `Photo Library Usage` permission. + ## 0.8.5+3 * Adds argument error assertions to the app-facing package, to ensure diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index 84c649028c96..e302173698f0 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -191,6 +191,10 @@ class ImagePicker { /// the front or rear camera should be opened, this function is not guaranteed /// to work on an Android device. /// + /// Use `requestFullMetadata` (defaults to `true`) to control how much additional information the plugin tries to get. + /// If `requestFullMetadata` is set to `true`, the plugin tries to get the full image metadata which may require + /// extra permission requests on some platforms, such as `Photo Library Usage` permission on iOS . + /// /// 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 [retrieveLostData] when your app relaunches to retrieve the lost data. /// @@ -206,6 +210,7 @@ class ImagePicker { double? maxHeight, int? imageQuality, CameraDevice preferredCameraDevice = CameraDevice.rear, + bool requestFullMetadata = true, }) { if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( @@ -218,12 +223,15 @@ class ImagePicker { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } - return platform.getImage( + return platform.getImageFromSource( source: source, - maxWidth: maxWidth, - maxHeight: maxHeight, - imageQuality: imageQuality, - preferredCameraDevice: preferredCameraDevice, + options: ImagePickerOptions( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + preferredCameraDevice: preferredCameraDevice, + requestFullMetadata: requestFullMetadata, + ), ); } diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 9d0cedeec484..5e7c1c5374f5 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.5+3 +version: 0.8.6 environment: sdk: ">=2.14.0 <3.0.0" @@ -25,7 +25,7 @@ dependencies: image_picker_android: ^0.8.4+11 image_picker_for_web: ^2.1.0 image_picker_ios: ^0.8.4+11 - image_picker_platform_interface: ^2.3.0 + image_picker_platform_interface: ^2.5.0 dev_dependencies: build_runner: ^2.1.10 diff --git a/packages/image_picker/image_picker/test/image_picker_test.dart b/packages/image_picker/image_picker/test/image_picker_test.dart index f981195fe1b3..43ac425d6f11 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -29,12 +29,8 @@ void main() { group('#Single image/video', () { group('#pickImage', () { setUp(() { - when(mockPlatform.getImage( - source: anyNamed('source'), - maxWidth: anyNamed('maxWidth'), - maxHeight: anyNamed('maxHeight'), - imageQuality: anyNamed('imageQuality'), - preferredCameraDevice: anyNamed('preferredCameraDevice'))) + when(mockPlatform.getImageFromSource( + source: anyNamed('source'), options: anyNamed('options'))) .thenAnswer((Invocation _) async => null); }); @@ -44,8 +40,20 @@ void main() { await picker.pickImage(source: ImageSource.gallery); verifyInOrder([ - mockPlatform.getImage(source: ImageSource.camera), - mockPlatform.getImage(source: ImageSource.gallery), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf(), + named: 'options', + ), + ), + mockPlatform.getImageFromSource( + source: ImageSource.gallery, + options: argThat( + isInstanceOf(), + named: 'options', + ), + ), ]); }); @@ -76,41 +84,111 @@ void main() { imageQuality: 70); verifyInOrder([ - mockPlatform.getImage( - source: ImageSource.camera, - maxWidth: null, - maxHeight: null, - imageQuality: null), - mockPlatform.getImage( - source: ImageSource.camera, - maxWidth: 10.0, - maxHeight: null, - imageQuality: null), - mockPlatform.getImage( - source: ImageSource.camera, - maxWidth: null, - maxHeight: 10.0, - imageQuality: null), - mockPlatform.getImage( - source: ImageSource.camera, - maxWidth: 10.0, - maxHeight: 20.0, - imageQuality: null), - mockPlatform.getImage( - source: ImageSource.camera, - maxWidth: 10.0, - maxHeight: null, - imageQuality: 70), - mockPlatform.getImage( - source: ImageSource.camera, - maxWidth: null, - maxHeight: 10.0, - imageQuality: 70), - mockPlatform.getImage( - source: ImageSource.camera, - maxWidth: 10.0, - maxHeight: 20.0, - imageQuality: 70), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf() + .having((ImagePickerOptions options) => options.maxWidth, + 'maxWidth', isNull) + .having((ImagePickerOptions options) => options.maxHeight, + 'maxHeight', isNull) + .having( + (ImagePickerOptions options) => options.imageQuality, + 'imageQuality', + isNull), + named: 'options', + ), + ), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf() + .having((ImagePickerOptions options) => options.maxWidth, + 'maxWidth', equals(10.0)) + .having((ImagePickerOptions options) => options.maxHeight, + 'maxHeight', isNull) + .having( + (ImagePickerOptions options) => options.imageQuality, + 'imageQuality', + isNull), + named: 'options', + ), + ), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf() + .having((ImagePickerOptions options) => options.maxWidth, + 'maxWidth', isNull) + .having((ImagePickerOptions options) => options.maxHeight, + 'maxHeight', equals(10.0)) + .having( + (ImagePickerOptions options) => options.imageQuality, + 'imageQuality', + isNull), + named: 'options', + ), + ), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf() + .having((ImagePickerOptions options) => options.maxWidth, + 'maxWidth', equals(10.0)) + .having((ImagePickerOptions options) => options.maxHeight, + 'maxHeight', equals(20.0)) + .having( + (ImagePickerOptions options) => options.imageQuality, + 'imageQuality', + isNull), + named: 'options', + ), + ), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf() + .having((ImagePickerOptions options) => options.maxWidth, + 'maxWidth', equals(10.0)) + .having((ImagePickerOptions options) => options.maxHeight, + 'maxHeight', isNull) + .having( + (ImagePickerOptions options) => options.imageQuality, + 'imageQuality', + equals(70)), + named: 'options', + ), + ), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf() + .having((ImagePickerOptions options) => options.maxWidth, + 'maxWidth', isNull) + .having((ImagePickerOptions options) => options.maxHeight, + 'maxHeight', equals(10.0)) + .having( + (ImagePickerOptions options) => options.imageQuality, + 'imageQuality', + equals(70)), + named: 'options', + ), + ), + mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf() + .having((ImagePickerOptions options) => options.maxWidth, + 'maxWidth', equals(10.0)) + .having((ImagePickerOptions options) => options.maxHeight, + 'maxHeight', equals(20.0)) + .having( + (ImagePickerOptions options) => options.imageQuality, + 'imageQuality', + equals(70)), + named: 'options', + ), + ), ]); }); @@ -138,9 +216,16 @@ void main() { final ImagePicker picker = ImagePicker(); await picker.pickImage(source: ImageSource.camera); - verify(mockPlatform.getImage( - source: ImageSource.camera, - preferredCameraDevice: CameraDevice.rear)); + verify(mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf().having( + (ImagePickerOptions options) => options.preferredCameraDevice, + 'preferredCameraDevice', + equals(CameraDevice.rear)), + named: 'options', + ), + )); }); test('camera position can set to front', () async { @@ -149,9 +234,51 @@ void main() { source: ImageSource.camera, preferredCameraDevice: CameraDevice.front); - verify(mockPlatform.getImage( - source: ImageSource.camera, - preferredCameraDevice: CameraDevice.front)); + verify(mockPlatform.getImageFromSource( + source: ImageSource.camera, + options: argThat( + isInstanceOf().having( + (ImagePickerOptions options) => options.preferredCameraDevice, + 'preferredCameraDevice', + equals(CameraDevice.front)), + named: 'options', + ), + )); + }); + + test('Full metadata argument defaults to true', () async { + final ImagePicker picker = ImagePicker(); + await picker.pickImage(source: ImageSource.gallery); + + verify(mockPlatform.getImageFromSource( + source: ImageSource.gallery, + options: argThat( + isInstanceOf().having( + (ImagePickerOptions options) => options.requestFullMetadata, + 'requestFullMetadata', + isTrue), + named: 'options', + ), + )); + }); + + test('passes the full metadata argument correctly', () async { + final ImagePicker picker = ImagePicker(); + await picker.pickImage( + source: ImageSource.gallery, + requestFullMetadata: false, + ); + + verify(mockPlatform.getImageFromSource( + source: ImageSource.gallery, + options: argThat( + isInstanceOf().having( + (ImagePickerOptions options) => options.requestFullMetadata, + 'requestFullMetadata', + isFalse), + named: 'options', + ), + )); }); }); diff --git a/packages/image_picker/image_picker_android/example/pubspec.yaml b/packages/image_picker/image_picker_android/example/pubspec.yaml index 0d88ae139c71..ad6cecaa8575 100755 --- a/packages/image_picker/image_picker_android/example/pubspec.yaml +++ b/packages/image_picker/image_picker_android/example/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - image_picker_platform_interface: ^2.3.0 + image_picker_platform_interface: ^2.5.0 video_player: ^2.1.4 dev_dependencies: diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 095534654ac5..694de3bbf29b 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.1 - image_picker_platform_interface: ^2.3.0 + image_picker_platform_interface: ^2.5.0 dev_dependencies: flutter_test: diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 96b1c7f0d0a4..12822164000b 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.6 + +* Adds `requestFullMetadata` option to `pickImage`, so images on iOS can be picked without `Photo Library Usage` permission. + ## 0.8.5+2 * Minor fixes for new analysis options. diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m index 04d491131d5b..e2311904f286 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -54,6 +54,7 @@ - (void)testPluginPickImageDeviceBack { camera:FLTSourceCameraRear] maxSize:[[FLTMaxSize alloc] init] quality:nil + fullMetadata:@(YES) completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -86,6 +87,7 @@ - (void)testPluginPickImageDeviceFront { camera:FLTSourceCameraFront] maxSize:[[FLTMaxSize alloc] init] quality:nil + fullMetadata:@(YES) completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; @@ -177,6 +179,27 @@ - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { [mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]); } +- (void)testPickImageWithoutFullMetadataPreiOS14 { + if (@available(iOS 14, *)) { + return; + } + id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]]; + FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" + arguments:@{ + @"source" : @(1), + @"requestFullMetadata" : @(NO), + }]; + + [plugin handleMethodCall:call + result:^(id _Nullable r){ + }]; + + OCMVerify(times(1), + [mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]); +} + #pragma mark - Test camera devices, no op on simulators - (void)testPluginPickImageDeviceCancelClickMultipleTimes { @@ -191,6 +214,7 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { camera:FLTSourceCameraRear] maxSize:[[FLTMaxSize alloc] init] quality:nil + fullMetadata:@(YES) completion:^(NSString *_Nullable result, FlutterError *_Nullable error){ }]; diff --git a/packages/image_picker/image_picker_ios/example/pubspec.yaml b/packages/image_picker/image_picker_ios/example/pubspec.yaml index a47893d7687f..d219831cce03 100755 --- a/packages/image_picker/image_picker_ios/example/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/example/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - image_picker_platform_interface: ^2.3.0 + image_picker_platform_interface: ^2.5.0 video_player: ^2.1.4 dev_dependencies: diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m index 76ed9623a57c..6495725b04b3 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m @@ -119,7 +119,12 @@ - (void)launchPHPickerWithContext:(nonnull FLTImagePickerMethodCallContext *)con _pickerViewController.presentationController.delegate = self; self.callContext = context; - [self checkPhotoAuthorizationForAccessLevel]; + BOOL requestFullMetadata = context.requestFullMetadata; + if (requestFullMetadata) { + [self checkPhotoAuthorizationForAccessLevel]; + } else { + [self showPhotoLibraryWithPHPicker:_pickerViewController]; + } } - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source @@ -130,13 +135,24 @@ - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; self.callContext = context; + BOOL requestFullMetadata = context.requestFullMetadata; + switch (source.type) { case FLTSourceTypeCamera: [self checkCameraAuthorizationWithImagePicker:imagePickerController camera:[self cameraDeviceForSource:source]]; break; case FLTSourceTypeGallery: - [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; + if (@available(iOS 11, *)) { + if (requestFullMetadata) { + [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; + } else { + [self showPhotoLibraryWithImagePicker:imagePickerController]; + } + } else { + // Prior to iOS 11, accessing gallery requires authorization + [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; + } break; default: [self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source" @@ -151,6 +167,7 @@ - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source maxSize:(nonnull FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality + fullMetadata:(NSNumber *)fullMetadata completion: (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion { [self cancelInProgressCall]; @@ -166,6 +183,7 @@ - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source context.maxSize = maxSize; context.imageQuality = imageQuality; context.maxImageCount = 1; + context.requestFullMetadata = [fullMetadata boolValue]; if (source.type == FLTSourceTypeGallery) { // Capture is not possible with PHPicker if (@available(iOS 14, *)) { @@ -227,6 +245,7 @@ - (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source } self.callContext = context; + BOOL requestFullMetadata = context.requestFullMetadata; switch (source.type) { case FLTSourceTypeCamera: @@ -234,7 +253,11 @@ - (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source camera:[self cameraDeviceForSource:source]]; break; case FLTSourceTypeGallery: - [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; + if (requestFullMetadata) { + [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; + } else { + [self showPhotoLibraryWithImagePicker:imagePickerController]; + } break; default: [self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source" @@ -553,8 +576,13 @@ - (void)imagePickerController:(UIImagePickerController *)picker NSNumber *maxHeight = self.callContext.maxSize.height; NSNumber *imageQuality = self.callContext.imageQuality; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; + BOOL requestFullMetadata = _callContext.requestFullMetadata; - PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info]; + PHAsset *originalAsset; + if (requestFullMetadata) { + // Full metadata are available only in PHAsset, which requires gallery permission + originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info]; + } if (maxWidth != nil || maxHeight != nil) { image = [FLTImagePickerImageUtil scaledImage:image diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h index 2c4167746c8e..174b1f7d7ff9 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h @@ -46,6 +46,9 @@ typedef void (^FlutterResultAdapter)(NSArray *_Nullable, FlutterErro /** Maximum number of images to select. 0 indicates no maximum. */ @property(nonatomic, assign) int maxImageCount; +/** Whether the image should be picked with full metadata (requires gallery permissions) */ +@property(nonatomic, assign) BOOL requestFullMetadata; + @end #pragma mark - diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h index 310165f72f4f..0d3e96c842bb 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h +++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v3.0.2), do not edit directly. +// Autogenerated from Pigeon (v3.0.3), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @protocol FlutterBinaryMessenger; @@ -45,6 +45,7 @@ NSObject *FLTImagePickerApiGetCodec(void); - (void)pickImageWithSource:(FLTSourceSpecification *)source maxSize:(FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality + fullMetadata:(NSNumber *)requestFullMetadata completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion; - (void)pickMultiImageWithMaxSize:(FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m index 6c91c0ab264f..b13601480aea 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v3.0.2), do not edit directly. +// Autogenerated from Pigeon (v3.0.3), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "messages.g.h" #import @@ -144,18 +144,21 @@ void FLTImagePickerApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(pickImageWithSource:maxSize:quality:completion:)], + NSCAssert([api respondsToSelector:@selector + (pickImageWithSource:maxSize:quality:fullMetadata:completion:)], @"FLTImagePickerApi api (%@) doesn't respond to " - @"@selector(pickImageWithSource:maxSize:quality:completion:)", + @"@selector(pickImageWithSource:maxSize:quality:fullMetadata:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0); FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 1); NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 2); + NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 3); [api pickImageWithSource:arg_source maxSize:arg_maxSize quality:arg_imageQuality + fullMetadata:arg_requestFullMetadata completion:^(NSString *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); }]; diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart index 3d1413cf0cce..c488874890ed 100644 --- a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart +++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart @@ -51,14 +51,28 @@ class ImagePickerIOS extends ImagePickerPlatform { }) async { final String? path = await _pickImageAsPath( source: source, - maxWidth: maxWidth, - maxHeight: maxHeight, - imageQuality: imageQuality, - preferredCameraDevice: preferredCameraDevice, + options: ImagePickerOptions( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + preferredCameraDevice: preferredCameraDevice, + ), ); return path != null ? PickedFile(path) : null; } + @override + Future getImageFromSource({ + required ImageSource source, + ImagePickerOptions options = const ImagePickerOptions(), + }) async { + final String? path = await _pickImageAsPath( + source: source, + options: options, + ); + return path != null ? XFile(path) : null; + } + @override Future?> pickMultiImage({ double? maxWidth, @@ -104,30 +118,32 @@ class ImagePickerIOS extends ImagePickerPlatform { Future _pickImageAsPath({ required ImageSource source, - double? maxWidth, - double? maxHeight, - int? imageQuality, - CameraDevice preferredCameraDevice = CameraDevice.rear, + ImagePickerOptions options = const ImagePickerOptions(), }) { + final int? imageQuality = options.imageQuality; if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) { throw ArgumentError.value( imageQuality, 'imageQuality', 'must be between 0 and 100'); } + final double? maxWidth = options.maxWidth; if (maxWidth != null && maxWidth < 0) { throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative'); } + final double? maxHeight = options.maxHeight; if (maxHeight != null && maxHeight < 0) { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } return _hostApi.pickImage( SourceSpecification( - type: _convertSource(source), - camera: _convertCamera(preferredCameraDevice)), + type: _convertSource(source), + camera: _convertCamera(options.preferredCameraDevice), + ), MaxSize(width: maxWidth, height: maxHeight), imageQuality, + options.requestFullMetadata, ); } @@ -167,10 +183,12 @@ class ImagePickerIOS extends ImagePickerPlatform { }) async { final String? path = await _pickImageAsPath( source: source, - maxWidth: maxWidth, - maxHeight: maxHeight, - imageQuality: imageQuality, - preferredCameraDevice: preferredCameraDevice, + options: ImagePickerOptions( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + preferredCameraDevice: preferredCameraDevice, + ), ); return path != null ? XFile(path) : null; } diff --git a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart index 0c5859e80ac9..70509b062fd6 100644 --- a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart +++ b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v3.0.2), do not edit directly. +// Autogenerated from Pigeon (v3.0.3), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name // @dart = 2.12 @@ -115,13 +115,16 @@ class ImagePickerApi { static const MessageCodec codec = _ImagePickerApiCodec(); Future pickImage(SourceSpecification arg_source, MaxSize arg_maxSize, - int? arg_imageQuality) async { + int? arg_imageQuality, bool arg_requestFullMetadata) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickImage', codec, binaryMessenger: _binaryMessenger); - final Map? replyMap = - await channel.send([arg_source, arg_maxSize, arg_imageQuality]) - as Map?; + final Map? replyMap = await channel.send([ + arg_source, + arg_maxSize, + arg_imageQuality, + arg_requestFullMetadata + ]) as Map?; if (replyMap == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart index 94ac034606e9..f6bff83959cf 100644 --- a/packages/image_picker/image_picker_ios/pigeons/messages.dart +++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart @@ -35,9 +35,9 @@ class SourceSpecification { @HostApi(dartHostTestHandler: 'TestHostImagePickerApi') abstract class ImagePickerApi { @async - @ObjCSelector('pickImageWithSource:maxSize:quality:') - String? pickImage( - SourceSpecification source, MaxSize maxSize, int? imageQuality); + @ObjCSelector('pickImageWithSource:maxSize:quality:fullMetadata:') + String? pickImage(SourceSpecification source, MaxSize maxSize, + int? imageQuality, bool requestFullMetadata); @async @ObjCSelector('pickMultiImageWithMaxSize:quality:') List? pickMultiImage(MaxSize maxSize, int? imageQuality); diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index d1de0a14ea69..58434b4d2996 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the video_picker plugin. repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.5+2 +version: 0.8.6 environment: sdk: ">=2.14.0 <3.0.0" @@ -19,7 +19,7 @@ flutter: dependencies: flutter: sdk: flutter - image_picker_platform_interface: ^2.3.0 + image_picker_platform_interface: ^2.5.0 dev_dependencies: flutter_test: diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart index 09517f1ef96b..14d1d0be99e6 100644 --- a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart +++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart @@ -39,7 +39,11 @@ class _ApiLogger implements TestHostImagePickerApi { @override Future pickImage( - SourceSpecification source, MaxSize maxSize, int? imageQuality) async { + SourceSpecification source, + MaxSize maxSize, + int? imageQuality, + bool requestFullMetadata, + ) async { // Flatten arguments for easy comparison. calls.add(_LoggedMethodCall('pickImage', arguments: { 'source': source.type, @@ -47,6 +51,7 @@ class _ApiLogger implements TestHostImagePickerApi { 'maxWidth': maxSize.width, 'maxHeight': maxSize.height, 'imageQuality': imageQuality, + 'requestFullMetadata': requestFullMetadata, })); return returnValue as String?; } @@ -103,14 +108,16 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.gallery, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), ], ); @@ -156,49 +163,56 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), ], ); @@ -257,6 +271,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), ], ); @@ -276,6 +291,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.front, + 'requestFullMetadata': true, }), ], ); @@ -524,14 +540,16 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.gallery, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), ], ); @@ -577,49 +595,56 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), const _LoggedMethodCall('pickImage', arguments: { 'source': SourceType.camera, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, - 'cameraDevice': SourceCamera.rear + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), ], ); @@ -678,6 +703,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, }), ], ); @@ -697,6 +723,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': SourceCamera.front, + 'requestFullMetadata': true, }), ], ); @@ -934,4 +961,276 @@ void main() { ); }); }); + + group('#getImageFromSource', () { + test('passes the image source argument correctly', () async { + await picker.getImageFromSource(source: ImageSource.camera); + await picker.getImageFromSource(source: ImageSource.gallery); + + expect( + log.calls, + <_LoggedMethodCall>[ + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.gallery, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + ], + ); + }); + + test('passes the width and height arguments correctly', () async { + await picker.getImageFromSource(source: ImageSource.camera); + await picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions(maxWidth: 10.0), + ); + await picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions(maxHeight: 10.0), + ); + await picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions( + maxWidth: 10.0, + maxHeight: 20.0, + ), + ); + await picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions( + maxWidth: 10.0, + imageQuality: 70, + ), + ); + await picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions( + maxHeight: 10.0, + imageQuality: 70, + ), + ); + await picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions( + maxWidth: 10.0, + maxHeight: 20.0, + imageQuality: 70, + ), + ); + + expect( + log.calls, + <_LoggedMethodCall>[ + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': 70, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': 70, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': 70, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + ], + ); + }); + + test('does not accept a invalid imageQuality argument', () { + expect( + () => picker.getImageFromSource( + source: ImageSource.gallery, + options: const ImagePickerOptions(imageQuality: -1), + ), + throwsArgumentError, + ); + + expect( + () => picker.getImageFromSource( + source: ImageSource.gallery, + options: const ImagePickerOptions(imageQuality: 101), + ), + throwsArgumentError, + ); + + expect( + () => picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions(imageQuality: -1), + ), + throwsArgumentError, + ); + + expect( + () => picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions(imageQuality: 101), + ), + throwsArgumentError, + ); + }); + + test('does not accept a negative width or height argument', () { + expect( + () => picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions(maxWidth: -1.0), + ), + throwsArgumentError, + ); + + expect( + () => picker.getImageFromSource( + source: ImageSource.camera, + options: const ImagePickerOptions(maxHeight: -1.0), + ), + throwsArgumentError, + ); + }); + + test('handles a null image path response gracefully', () async { + log.returnValue = null; + + expect( + await picker.getImageFromSource(source: ImageSource.gallery), isNull); + expect( + await picker.getImageFromSource(source: ImageSource.camera), isNull); + }); + + test('camera position defaults to back', () async { + await picker.getImageFromSource(source: ImageSource.camera); + + expect( + log.calls, + <_LoggedMethodCall>[ + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + ], + ); + }); + + test('camera position can set to front', () async { + await picker.getImageFromSource( + source: ImageSource.camera, + options: + const ImagePickerOptions(preferredCameraDevice: CameraDevice.front), + ); + + expect( + log.calls, + <_LoggedMethodCall>[ + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.camera, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.front, + 'requestFullMetadata': true, + }), + ], + ); + }); + + test('Request full metadata argument defaults to true', () async { + await picker.getImageFromSource(source: ImageSource.gallery); + + expect( + log.calls, + <_LoggedMethodCall>[ + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.gallery, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': true, + }), + ], + ); + }); + + test('passes the request full metadata argument correctly', () async { + await picker.getImageFromSource( + source: ImageSource.gallery, + options: const ImagePickerOptions(requestFullMetadata: false), + ); + + expect( + log.calls, + <_LoggedMethodCall>[ + const _LoggedMethodCall('pickImage', arguments: { + 'source': SourceType.gallery, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': SourceCamera.rear, + 'requestFullMetadata': false, + }), + ], + ); + }); + }); } diff --git a/packages/image_picker/image_picker_ios/test/test_api.dart b/packages/image_picker/image_picker_ios/test/test_api.dart index d22a26b2489b..8bc1be2ca002 100644 --- a/packages/image_picker/image_picker_ios/test/test_api.dart +++ b/packages/image_picker/image_picker_ios/test/test_api.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v3.0.2), do not edit directly. +// Autogenerated from Pigeon (v3.0.3), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis // ignore_for_file: avoid_relative_lib_imports @@ -10,8 +10,6 @@ import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; - -// Manually changed due to https://github.com/flutter/flutter/issues/97744 import 'package:image_picker_ios/src/messages.g.dart'; class _TestHostImagePickerApiCodec extends StandardMessageCodec { @@ -47,8 +45,8 @@ class _TestHostImagePickerApiCodec extends StandardMessageCodec { abstract class TestHostImagePickerApi { static const MessageCodec codec = _TestHostImagePickerApiCodec(); - Future pickImage( - SourceSpecification source, MaxSize maxSize, int? imageQuality); + Future pickImage(SourceSpecification source, MaxSize maxSize, + int? imageQuality, bool requestFullMetadata); Future?> pickMultiImage(MaxSize maxSize, int? imageQuality); Future pickVideo( SourceSpecification source, int? maxDurationSeconds); @@ -73,8 +71,11 @@ abstract class TestHostImagePickerApi { assert(arg_maxSize != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null MaxSize.'); final int? arg_imageQuality = (args[2] as int?); - final String? output = - await api.pickImage(arg_source!, arg_maxSize!, arg_imageQuality); + final bool? arg_requestFullMetadata = (args[3] as bool?); + assert(arg_requestFullMetadata != null, + 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null bool.'); + final String? output = await api.pickImage(arg_source!, arg_maxSize!, + arg_imageQuality, arg_requestFullMetadata!); return {'result': output}; }); }