Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
[image_picker] add requestFullMetadata for iOS (optional permissions)…
Browse files Browse the repository at this point in the history
… - iOS changes (#5713)
  • Loading branch information
PiotrMitkowski authored Aug 31, 2022
1 parent 0e3cefc commit ef20791
Show file tree
Hide file tree
Showing 13 changed files with 737 additions and 92 deletions.
3 changes: 2 additions & 1 deletion packages/image_picker/image_picker_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 0.8.6

* Adds `requestFullMetadata` option to `pickImage`, so images on iOS can be picked without `Photo Library Usage` permission.
* Updates minimum Flutter version to 2.10.

## 0.8.5+6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ - (void)testPluginPickImageDeviceBack {
.andReturn(AVAuthorizationStatusAuthorized);

// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
[plugin setImagePickerControllerOverrides:@[ controller ]];

[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
camera:FLTSourceCameraRear]
maxSize:[[FLTMaxSize alloc] init]
quality:nil
fullMetadata:@(YES)
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
}];

Expand All @@ -78,14 +79,15 @@ - (void)testPluginPickImageDeviceFront {
.andReturn(AVAuthorizationStatusAuthorized);

// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
[plugin setImagePickerControllerOverrides:@[ controller ]];

[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
camera:FLTSourceCameraFront]
maxSize:[[FLTMaxSize alloc] init]
quality:nil
fullMetadata:@(YES)
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
}];

Expand All @@ -110,7 +112,7 @@ - (void)testPluginPickVideoDeviceBack {
.andReturn(AVAuthorizationStatusAuthorized);

// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
[plugin setImagePickerControllerOverrides:@[ controller ]];

Expand Down Expand Up @@ -142,7 +144,7 @@ - (void)testPluginPickVideoDeviceFront {
.andReturn(AVAuthorizationStatusAuthorized);

// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
[plugin setImagePickerControllerOverrides:@[ controller ]];

Expand All @@ -165,32 +167,69 @@ - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 {
OCMStub(ClassMethod([photoLibrary authorizationStatus]))
.andReturn(PHAuthorizationStatusAuthorized);

FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
[plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];

[plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
quality:@(50)
fullMetadata:@(YES)
completion:^(NSArray<NSString *> *_Nullable result,
FlutterError *_Nullable error){
}];
OCMVerify(times(1),
[mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]);
}

- (void)testPickImageWithoutFullMetadata API_AVAILABLE(ios(11)) {
id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
id photoLibrary = OCMClassMock([PHPhotoLibrary class]);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
[plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];

[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery
camera:FLTSourceCameraFront]
maxSize:[[FLTMaxSize alloc] init]
quality:nil
fullMetadata:@(NO)
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
}];

OCMVerify(times(0), [photoLibrary authorizationStatus]);
}

- (void)testPickMultiImageWithoutFullMetadata API_AVAILABLE(ios(11)) {
id mockUIImagePicker = OCMClassMock([UIImagePickerController class]);
id photoLibrary = OCMClassMock([PHPhotoLibrary class]);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
[plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];

[plugin pickMultiImageWithMaxSize:[[FLTMaxSize alloc] init]
quality:nil
fullMetadata:@(NO)
completion:^(NSArray<NSString *> *_Nullable result,
FlutterError *_Nullable error){
}];

OCMVerify(times(0), [photoLibrary authorizationStatus]);
}

#pragma mark - Test camera devices, no op on simulators

- (void)testPluginPickImageDeviceCancelClickMultipleTimes {
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
return;
}
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
plugin.imagePickerControllerOverrides = @[ controller ];

[plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
camera:FLTSourceCameraRear]
maxSize:[[FLTMaxSize alloc] init]
quality:nil
fullMetadata:@(YES)
completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
}];

Expand All @@ -202,7 +241,7 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes {
#pragma mark - Test video duration

- (void)testPickingVideoWithDuration {
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
UIImagePickerController *controller = [[UIImagePickerController alloc] init];
[plugin setImagePickerControllerOverrides:@[ controller ]];

Expand All @@ -223,12 +262,12 @@ - (void)testViewController {
UIViewController *vc2 = [UIViewController new];
vc1.mockPresented = vc2;

FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
XCTAssertEqual([plugin viewControllerWithWindow:window], vc2);
}

- (void)testPluginMultiImagePathHasNullItem {
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
__block FlutterError *pickImageResult = nil;
Expand All @@ -245,7 +284,7 @@ - (void)testPluginMultiImagePathHasNullItem {
}

- (void)testPluginMultiImagePathHasItem {
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];
NSArray *pathList = @[ @"test" ];

dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.6.1
video_player: ^2.1.4

dev_dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ @interface FLTImagePickerPlugin () <UINavigationControllerDelegate,
@implementation FLTImagePickerPlugin

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FLTImagePickerPlugin *instance = [FLTImagePickerPlugin new];
FLTImagePickerPlugin *instance = [[FLTImagePickerPlugin alloc] init];
FLTImagePickerApiSetup(registrar.messenger, instance);
}

Expand Down Expand Up @@ -119,7 +119,11 @@ - (void)launchPHPickerWithContext:(nonnull FLTImagePickerMethodCallContext *)con
_pickerViewController.presentationController.delegate = self;
self.callContext = context;

[self checkPhotoAuthorizationForAccessLevel];
if (context.requestFullMetadata) {
[self checkPhotoAuthorizationForAccessLevel];
} else {
[self showPhotoLibraryWithPHPicker:_pickerViewController];
}
}

- (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source
Expand All @@ -136,7 +140,16 @@ - (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source
camera:[self cameraDeviceForSource:source]];
break;
case FLTSourceTypeGallery:
[self checkPhotoAuthorizationWithImagePicker:imagePickerController];
if (@available(iOS 11, *)) {
if (context.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"
Expand All @@ -151,6 +164,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];
Expand All @@ -166,6 +180,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, *)) {
Expand All @@ -180,12 +195,14 @@ - (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source

- (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize
quality:(nullable NSNumber *)imageQuality
fullMetadata:(NSNumber *)fullMetadata
completion:(nonnull void (^)(NSArray<NSString *> *_Nullable,
FlutterError *_Nullable))completion {
FLTImagePickerMethodCallContext *context =
[[FLTImagePickerMethodCallContext alloc] initWithResult:completion];
context.maxSize = maxSize;
context.imageQuality = imageQuality;
context.requestFullMetadata = [fullMetadata boolValue];

if (@available(iOS 14, *)) {
[self launchPHPickerWithContext:context];
Expand Down Expand Up @@ -554,7 +571,11 @@ - (void)imagePickerController:(UIImagePickerController *)picker
NSNumber *imageQuality = self.callContext.imageQuality;
NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality];

PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info];
PHAsset *originalAsset;
if (_callContext.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ typedef void (^FlutterResultAdapter)(NSArray<NSString *> *_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 -
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>
@protocol FlutterBinaryMessenger;
Expand Down Expand Up @@ -45,9 +45,11 @@ NSObject<FlutterMessageCodec> *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
fullMetadata:(NSNumber *)requestFullMetadata
completion:(void (^)(NSArray<NSString *> *_Nullable,
FlutterError *_Nullable))completion;
- (void)pickVideoWithSource:(FLTSourceSpecification *)source
Expand Down
16 changes: 11 additions & 5 deletions packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
Original file line number Diff line number Diff line change
@@ -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 <Flutter/Flutter.h>
Expand Down Expand Up @@ -144,18 +144,21 @@ void FLTImagePickerApiSetup(id<FlutterBinaryMessenger> 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));
}];
Expand All @@ -170,16 +173,19 @@ void FLTImagePickerApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
binaryMessenger:binaryMessenger
codec:FLTImagePickerApiGetCodec()];
if (api) {
NSCAssert([api respondsToSelector:@selector(pickMultiImageWithMaxSize:quality:completion:)],
NSCAssert([api respondsToSelector:@selector
(pickMultiImageWithMaxSize:quality:fullMetadata:completion:)],
@"FLTImagePickerApi api (%@) doesn't respond to "
@"@selector(pickMultiImageWithMaxSize:quality:completion:)",
@"@selector(pickMultiImageWithMaxSize:quality:fullMetadata:completion:)",
api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
NSArray *args = message;
FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0);
NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1);
NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 2);
[api pickMultiImageWithMaxSize:arg_maxSize
quality:arg_imageQuality
fullMetadata:arg_requestFullMetadata
completion:^(NSArray<NSString *> *_Nullable output,
FlutterError *_Nullable error) {
callback(wrapResult(output, error));
Expand Down
Loading

0 comments on commit ef20791

Please sign in to comment.