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

[image_picker] Image picker phpicker impl #3835

Merged
merged 44 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9496772
Add PHPicker libraries and its delegate
ydag Apr 28, 2021
a4f8416
Add PHPicker implementation
ydag Apr 28, 2021
e8c128c
Refactor UIImagePickerController implementation
ydag Apr 28, 2021
0305c42
Refactor handleMethodCall function
ydag Apr 28, 2021
55cd773
Refactor showPhotoLibrary function
ydag Apr 28, 2021
1516e73
Add the limited access into the photo library
ydag Apr 28, 2021
5b64833
Add the implementation of picker function
ydag Apr 28, 2021
36b6a2a
Refactor Info.plist file
ydag Apr 28, 2021
1d2679a
Add the function to get PHAssetResult
ydag Apr 29, 2021
6566bf0
Change the image to bypass HEIC problem
ydag Apr 29, 2021
a4d2a12
Add UITest to test select photo functionality
ydag Apr 29, 2021
9ed3fcf
Add unit test for PHAssetResult
ydag Apr 29, 2021
7f81b7b
Format the codes in all files
ydag Apr 29, 2021
62bfcfd
Fix the unit test
ydag Apr 29, 2021
97311f9
Update the version
ydag Apr 29, 2021
cc7c5b1
Add the description into CHANGELOG file
ydag Apr 29, 2021
04a8452
Fix the license header
ydag Apr 30, 2021
b930c97
Remove the unnecessary if statement
ydag Apr 30, 2021
9d4c04d
Refactor PHPickerViewController to use property
ydag Apr 30, 2021
0728176
Refactor phPickerFlag state to use property
ydag Apr 30, 2021
852a828
Refactor the code to combine lines
ydag Apr 30, 2021
13e5e9d
Refactor arguments to use property
ydag Apr 30, 2021
aa8a878
Revert formatting changes
ydag Apr 30, 2021
f14c616
Fix the unit test
ydag Apr 30, 2021
7710140
Format the code
ydag Apr 30, 2021
29e5dfd
Refactor the UITest to skip lower versions
ydag May 3, 2021
a83440f
Fix the property's name
ydag May 3, 2021
97f24f8
Refactor the properties to pass them via methods
ydag May 3, 2021
6b295e0
Add the getDesiredImageQuality method
ydag May 3, 2021
f1ae1f2
Add API_AVAILABLE for limited access method
ydag May 3, 2021
30d9597
Refactor PHPickerController to use as a property
ydag May 3, 2021
89a6433
Refactor PHPicker picker method and UITest
ydag May 3, 2021
f3f84b4
Refactor the UITest
ydag May 3, 2021
6e41e86
Change the team to None in the RunnerUITestiOS14
ydag May 3, 2021
55252bf
Fix the UITest
ydag May 4, 2021
4785b7c
Refactor the method to fix dispatch
ydag May 4, 2021
202699c
Change to use self instead of weakSelf
ydag May 4, 2021
aab9714
Fix the UITest to use XCTSkip
ydag May 4, 2021
c04a4dd
Add ImagePickerClassType to use enum
ydag May 5, 2021
615e5a6
Remove unused method
ydag May 5, 2021
362bede
Fix property to change copy to strong
ydag May 6, 2021
4d753d3
Refactor enum
ydag May 6, 2021
049c365
Change argument call
ydag May 6, 2021
0812f61
Add checkPhotoAuthorizationForAccessLevel method
ydag May 7, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class AndroidDeviceInfo {
required this.isPhysicalDevice,
required this.androidId,
required List<String> systemFeatures,
}) : supported32BitAbis = List<String>.unmodifiable(supported32BitAbis),
}) : supported32BitAbis = List<String>.unmodifiable(supported32BitAbis),
supported64BitAbis = List<String>.unmodifiable(supported64BitAbis),
supportedAbis = List<String>.unmodifiable(supportedAbis),
systemFeatures = List<String>.unmodifiable(systemFeatures);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class CirclesController extends GeometryController {
/// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
CirclesController({
required StreamController<MapEvent> stream,
}) : _streamController = stream,
}) : _streamController = stream,
_circleIdToController = Map<CircleId, CircleController>();

/// Returns the cache of [CircleController]s. Test only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class MarkersController extends GeometryController {
/// Initialize the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
MarkersController({
required StreamController<MapEvent> stream,
}) : _streamController = stream,
}) : _streamController = stream,
_markerIdToController = Map<MarkerId, MarkerController>();

/// Returns the cache of [MarkerController]s. Test only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PolygonsController extends GeometryController {
/// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
PolygonsController({
required StreamController<MapEvent> stream,
}) : _streamController = stream,
}) : _streamController = stream,
_polygonIdToController = Map<PolygonId, PolygonController>();

/// Returns the cache of [PolygonController]s. Test only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PolylinesController extends GeometryController {
/// Initializes the cache. The [StreamController] comes from the [GoogleMapController], and is shared with other controllers.
PolylinesController({
required StreamController<MapEvent> stream,
}) : _streamController = stream,
}) : _streamController = stream,
_polylineIdToController = Map<PolylineId, PolylineController>();

/// Returns the cache of [PolylineContrller]s. Test only.
Expand Down
7 changes: 7 additions & 0 deletions packages/image_picker/image_picker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.7.5

* Fixes an issue where image rotation is wrong when Select Photos chose and image is scaled.
* Breaking Changes:
* Migrate to PHPicker for iOS 14 and higher versions to pick image from the photo library.
* Implement the limited permission to pick photo from the photo library when Select Photo is chose.

## 0.7.4

* Update flutter_plugin_android_lifecycle dependency to 2.0.1 to fix an R8 issue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
5C9513011EC38BD300040975 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */; };
680049262280D736006DD6AB /* MetaDataUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 680049252280D736006DD6AB /* MetaDataUtilTests.m */; };
680049272280D79A006DD6AB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
680049382280F2B9006DD6AB /* pngImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 680049352280F2B8006DD6AB /* pngImage.png */; };
680049392280F2B9006DD6AB /* jpgImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 680049362280F2B8006DD6AB /* jpgImage.jpg */; };
6801C8392555D726009DAF8D /* ImagePickerFromGalleryUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6801C8382555D726009DAF8D /* ImagePickerFromGalleryUITests.m */; };
Expand All @@ -23,6 +22,7 @@
9FC8F0E9229FA49E00C8D58F /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; };
9FC8F0EC229FA68500C8D58F /* gifImage.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9FC8F0E8229FA49E00C8D58F /* gifImage.gif */; };
9FC8F0EE229FB90B00C8D58F /* ImageUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */; };
BECB773E26399EE7007FFBB3 /* ImagePickerFromLimitedGalleryUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = BECB773D26399EE7007FFBB3 /* ImagePickerFromLimitedGalleryUITests.m */; };
F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EC32F6993F4529982D9519F1 /* libPods-Runner.a */; };
F78AF3192342D9D7008449C7 /* ImagePickerTestImages.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -80,6 +80,7 @@
9FC8F0E8229FA49E00C8D58F /* gifImage.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifImage.gif; sourceTree = "<group>"; };
9FC8F0ED229FB90B00C8D58F /* ImageUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ImageUtilTests.m; path = ../../../ios/Tests/ImageUtilTests.m; sourceTree = "<group>"; };
A908FAEEA2A9B26D903C09C5 /* libPods-RunnerUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
BECB773D26399EE7007FFBB3 /* ImagePickerFromLimitedGalleryUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ImagePickerFromLimitedGalleryUITests.m; sourceTree = "<group>"; };
EC32F6993F4529982D9519F1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
F78AF3172342D9D7008449C7 /* ImagePickerTestImages.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ImagePickerTestImages.h; path = ../../../ios/Tests/ImagePickerTestImages.h; sourceTree = "<group>"; };
F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = ImagePickerTestImages.m; path = ../../../ios/Tests/ImagePickerTestImages.m; sourceTree = "<group>"; };
Expand All @@ -90,7 +91,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F10E2DB84567A50A8721C9C7 /* libPods-RunnerUITests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -126,6 +126,7 @@
F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */,
68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */,
6801C83A2555D726009DAF8D /* Info.plist */,
BECB773D26399EE7007FFBB3 /* ImagePickerFromLimitedGalleryUITests.m */,
);
path = RunnerUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -395,6 +396,7 @@
buildActionMask = 2147483647;
files = (
6801C8392555D726009DAF8D /* ImagePickerFromGalleryUITests.m in Sources */,
BECB773E26399EE7007FFBB3 /* ImagePickerFromLimitedGalleryUITests.m in Sources */,
9FC8F0EE229FB90B00C8D58F /* ImageUtilTests.m in Sources */,
F78AF3192342D9D7008449C7 /* ImagePickerTestImages.m in Sources */,
680049262280D736006DD6AB /* MetaDataUtilTests.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PHPhotoLibraryPreventAutomaticLimitedAccessAlert</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ - (void)launchPickerAndPick {
// Find an image and tap on it. (IOS 14 UI, images are showing directly)
XCUIElement* aImage;
if (@available(iOS 14, *)) {
aImage = self.app.scrollViews.firstMatch.images.firstMatch;
aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to select the 2nd element instead of 2? Is this a critical change or just that you think testing the 2nd image is better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because there is a known issue to pick HEIC images with PHPicker implementation. But only for simulators. So we have two options here. Either test with real device or test with non-HEIC images until Apple solves the issue.

} else {
XCUIElement* allPhotosCell = [self.app.cells
elementMatchingPredicate:[NSPredicate predicateWithFormat:@"label == %@", @"All Photos"]];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//
// ImagePickerFromLimitedGalleryUITests.m
// RunnerUITests
//
// Created by Yusuf Dag on 28/04/2021.
// Copyright © 2021 The Flutter Authors. All rights reserved.
//

#import <XCTest/XCTest.h>
#import <os/log.h>

const int kLimitedElementWaitingTime = 30;

@interface ImagePickerFromLimitedGalleryUITests : XCTestCase

@property(nonatomic, strong) XCUIApplication* app;

@end

@implementation ImagePickerFromLimitedGalleryUITests

- (void)setUp {
[super setUp];
// Delete the app if already exists, to test permission popups

self.continueAfterFailure = NO;
self.app = [[XCUIApplication alloc] init];
[self.app launch];
__weak typeof(self) weakSelf = self;
[self addUIInterruptionMonitorWithDescription:@"Permission popups"
handler:^BOOL(XCUIElement* _Nonnull interruptingElement) {
if (@available(iOS 14, *)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think limited photos is only availabe in iOS 14? So maybe we should skip this test for ios 14 and below? This way we don't need to maintain 2 versions for this test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure how to do this. Using API_AVAILABLE(ios(14)) on the top of the test class maybe?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometime like this?:

if (!(@available(iOS 14, *))) {
    return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used XCTSkip, I think it is done!

XCUIElement* limitedPhotoPermission =
[interruptingElement.buttons elementBoundByIndex:0];
if (![limitedPhotoPermission
waitForExistenceWithTimeout:
kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@",
weakSelf.app.debugDescription);
XCTFail(@"Failed due to not able to find "
@"selectPhotos butt on with %@ seconds",
@(kLimitedElementWaitingTime));
}
[limitedPhotoPermission tap];
} else {
XCUIElement* ok = interruptingElement.buttons[@"OK"];
if (![ok waitForExistenceWithTimeout:
kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@",
weakSelf.app.debugDescription);
XCTFail(@"Failed due to not able to find ok button "
@"with %@ seconds",
@(kLimitedElementWaitingTime));
}
[ok tap];
}
return YES;
}];
}

- (void)tearDown {
[super tearDown];
[self.app terminate];
}

- (void)testSelectingFromGallery {
[self launchPickerAndSelect];
}

- (void)launchPickerAndSelect {
// Find and tap on the pick from gallery button.
NSPredicate* predicateToFindImageFromGalleryButton =
[NSPredicate predicateWithFormat:@"label == %@", @"image_picker_example_from_gallery"];

XCUIElement* imageFromGalleryButton =
[self.app.otherElements elementMatchingPredicate:predicateToFindImageFromGalleryButton];
if (![imageFromGalleryButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find image from gallery button with %@ seconds",
@(kLimitedElementWaitingTime));
}

XCTAssertTrue(imageFromGalleryButton.exists);
[imageFromGalleryButton tap];

// Find and tap on the `pick` button.
NSPredicate* predicateToFindPickButton =
[NSPredicate predicateWithFormat:@"label == %@", @"PICK"];

XCUIElement* pickButton = [self.app.buttons elementMatchingPredicate:predicateToFindPickButton];
if (![pickButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find pick button with %@ seconds",
@(kLimitedElementWaitingTime));
}

XCTAssertTrue(pickButton.exists);
[pickButton tap];

// There is a known bug where the permission popups interruption won't get fired until a tap
// happened in the app. We expect a permission popup so we do a tap here.
[self.app tap];

// Find an image and tap on it. (IOS 14 UI, images are showing directly)
XCUIElement* aImage;
if (@available(iOS 14, *)) {
aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1];
} else {
XCUIElement* selectedPhotosCell = [self.app.cells
elementMatchingPredicate:[NSPredicate
predicateWithFormat:@"label == %@", @"Selected Photos"]];
if (![selectedPhotosCell waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find \"Selected Photos\" cell with %@ seconds",
@(kLimitedElementWaitingTime));
}
[selectedPhotosCell tap];
aImage = [self.app.collectionViews elementMatchingType:XCUIElementTypeCollectionView
identifier:@"PhotosGridView"]
.cells.firstMatch;
}
os_log_error(OS_LOG_DEFAULT, "description before picking image %@", self.app.debugDescription);
if (![aImage waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find an image with %@ seconds",
@(kLimitedElementWaitingTime));
}
XCTAssertTrue(aImage.exists);
[aImage tap];

// Find and tap on the `Done` button.
NSPredicate* predicateToFindDoneButton =
[NSPredicate predicateWithFormat:@"label == %@", @"Done"];

XCUIElement* doneButton = [self.app.buttons elementMatchingPredicate:predicateToFindDoneButton];
if (![doneButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find Cancel button with %@ seconds",
@(kLimitedElementWaitingTime));
}

XCTAssertTrue(doneButton.exists);
[doneButton tap];

// Find an image and tap on it to have access to selected photos.
if (@available(iOS 14, *)) {
aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1];
} else {
XCUIElement* selectedPhotosCell = [self.app.cells
elementMatchingPredicate:[NSPredicate
predicateWithFormat:@"label == %@", @"Selected Photos"]];
if (![selectedPhotosCell waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find \"Selected Photos\" cell with %@ seconds",
@(kLimitedElementWaitingTime));
}
[selectedPhotosCell tap];
aImage = [self.app.collectionViews elementMatchingType:XCUIElementTypeCollectionView
identifier:@"PhotosGridView"]
.cells.firstMatch;
}
os_log_error(OS_LOG_DEFAULT, "description before picking image %@", self.app.debugDescription);
if (![aImage waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find an image with %@ seconds",
@(kLimitedElementWaitingTime));
}
XCTAssertTrue(aImage.exists);
[aImage tap];

// Find the picked image.
NSPredicate* predicateToFindPickedImage =
[NSPredicate predicateWithFormat:@"label == %@", @"image_picker_example_picked_image"];

XCUIElement* pickedImage = [self.app.images elementMatchingPredicate:predicateToFindPickedImage];
if (![pickedImage waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
XCTFail(@"Failed due to not able to find pickedImage with %@ seconds",
@(kLimitedElementWaitingTime));
}

XCTAssertTrue(pickedImage.exists);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#import <Foundation/Foundation.h>
#import <Photos/Photos.h>
#import <PhotosUI/PhotosUI.h>

#import "FLTImagePickerImageUtil.h"

Expand All @@ -13,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN

+ (nullable PHAsset *)getAssetFromImagePickerInfo:(NSDictionary *)info;

+ (nullable PHAsset *)getAssetFromPHPickerResult:(PHPickerResult *)result;

// Save image with correct meta data and extention copied from the original asset.
// maxWidth and maxHeight are used only for GIF images.
+ (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ + (PHAsset *)getAssetFromImagePickerInfo:(NSDictionary *)info {
return result.firstObject;
}

+ (PHAsset *)getAssetFromPHPickerResult:(PHPickerResult *)result {
if (@available(iOS 14, *)) {
PHFetchResult *fetchResult =
[PHAsset fetchAssetsWithLocalIdentifiers:@[ result.assetIdentifier ] options:nil];
return fetchResult.firstObject;
}
return nil;
}

+ (NSString *)saveImageWithOriginalImageData:(NSData *)originalImageData
image:(UIImage *)image
maxWidth:(NSNumber *)maxWidth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
// found in the LICENSE file.

#import <Flutter/Flutter.h>
#import <PhotosUI/PhotosUI.h>

API_AVAILABLE(ios(14))
@interface FLTImagePickerPlugin : NSObject <FlutterPlugin>

// For testing only.
- (UIImagePickerController *)getImagePickerController;
- (UIViewController *)viewControllerWithWindow:(UIWindow *)window;
- (PHPickerViewController *)getPickerViewController;

@end
Loading