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

[image_picker] add requestFullMetadata for iOS (optional permissions) - iOS changes #5713

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c705774
Moved iOS specific changes to a separate branch
PiotrMitkowski May 13, 2022
bf73c73
Fixed line formatting
PiotrMitkowski May 13, 2022
d48e642
Merge branch 'main' of github.com:flutter/plugins into ios-optional-p…
PiotrMitkowski May 25, 2022
3b3435f
PR remarks
PiotrMitkowski May 25, 2022
420a4c4
PR remarks
PiotrMitkowski May 25, 2022
ca15f9e
Merge branch 'main' of github.com:flutter/plugins into ios-optional-p…
PiotrMitkowski Jul 26, 2022
dff7042
Merge branch 'main' of github.com:flutter/plugins into ios-optional-p…
PiotrMitkowski Jul 29, 2022
b9c4cef
Updated iOS package with new interface method implementation
PiotrMitkowski Jul 29, 2022
b3d81e3
Implemented native part of picking multi images without full metadata
PiotrMitkowski Jul 29, 2022
d95b117
Merge branch 'main' of github.com:flutter/plugins into ios-optional-p…
PiotrMitkowski Aug 3, 2022
4a41df1
Updated versions of the platform interface in iOS implementation
PiotrMitkowski Aug 3, 2022
932ae7c
Fixed import for generated messages file
PiotrMitkowski Aug 3, 2022
2424828
Restored accidentally removed comments from generated test API file
PiotrMitkowski Aug 3, 2022
5685468
Merge branch 'main' of github.com:flutter/plugins into ios-optional-p…
PiotrMitkowski Aug 5, 2022
2464c13
PR remarks
PiotrMitkowski Aug 5, 2022
9252583
Merge branch 'main' of github.com:flutter/plugins into ios-optional-p…
PiotrMitkowski Aug 25, 2022
09a264a
PR remarks
PiotrMitkowski Aug 25, 2022
6aaa4b0
Merge branch 'main' into ios-optional-permissions-ios-changes
stuartmorgan Aug 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/image_picker/image_picker_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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+6

* Updates description.
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