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

[image_picker_ios] Pass through error message from image saving #6858

Merged
merged 4 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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+3

* Returns error on image load failure.

## 0.8.6+2

* Fixes issue where selectable images of certain types (such as ProRAW images) could not be loaded.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

@import image_picker_ios;
@import image_picker_ios.Test;
@import UniformTypeIdentifiers;
@import XCTest;

#import <OCMock/OCMock.h>

@interface MockViewController : UIViewController
Expand Down Expand Up @@ -269,37 +271,130 @@ - (void)testViewController {
- (void)testPluginMultiImagePathHasNullItem {
FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

nice

__block FlutterError *pickImageResult = nil;
XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];
plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
pickImageResult = error;
dispatch_semaphore_signal(resultSemaphore);
XCTAssertEqualObjects(error.code, @"create_error");
[resultExpectation fulfill];
}];
[plugin sendCallResultWithSavedPathList:@[ [NSNull null] ]];

dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);

XCTAssertEqualObjects(pickImageResult.code, @"create_error");
[self waitForExpectationsWithTimeout:30 handler:nil];
}

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

dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
__block id pickImageResult = nil;
XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
pickImageResult = result;
dispatch_semaphore_signal(resultSemaphore);
XCTAssertEqualObjects(result, pathList);
[resultExpectation fulfill];
}];
[plugin sendCallResultWithSavedPathList:pathList];

dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSendsImageInvalidSourceError API_AVAILABLE(ios(14)) {
id mockPickerViewController = OCMClassMock([PHPickerViewController class]);

id mockItemProvider = OCMClassMock([NSItemProvider class]);
// Does not conform to image, invalid source.
OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(NO);

PHPickerResult *failResult1 = OCMClassMock([PHPickerResult class]);
OCMStub([failResult1 itemProvider]).andReturn(mockItemProvider);

PHPickerResult *failResult2 = OCMClassMock([PHPickerResult class]);
OCMStub([failResult2 itemProvider]).andReturn(mockItemProvider);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
XCTAssertTrue(NSThread.isMainThread);
XCTAssertNil(result);
XCTAssertEqualObjects(error.code, @"invalid_source");
[resultExpectation fulfill];
}];

[plugin picker:mockPickerViewController didFinishPicking:@[ failResult1, failResult2 ]];

[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSendsImageInvalidErrorWhenOneFails API_AVAILABLE(ios(14)) {
id mockPickerViewController = OCMClassMock([PHPickerViewController class]);
NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil];

id mockFailItemProvider = OCMClassMock([NSItemProvider class]);
OCMStub([mockFailItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES);
[[mockFailItemProvider stub]
loadDataRepresentationForTypeIdentifier:OCMOCK_ANY
completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null],
loadDataError, nil]];

PHPickerResult *failResult = OCMClassMock([PHPickerResult class]);
OCMStub([failResult itemProvider]).andReturn(mockFailItemProvider);

NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage"
withExtension:@"tiff"];
NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL];
PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]);
OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
XCTAssertTrue(NSThread.isMainThread);
XCTAssertNil(result);
XCTAssertEqualObjects(error.code, @"invalid_image");
[resultExpectation fulfill];
}];

[plugin picker:mockPickerViewController didFinishPicking:@[ failResult, tiffResult ]];

[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSavesImages API_AVAILABLE(ios(14)) {
id mockPickerViewController = OCMClassMock([PHPickerViewController class]);

NSURL *tiffURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"tiffImage"
withExtension:@"tiff"];
NSItemProvider *tiffItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:tiffURL];
PHPickerResult *tiffResult = OCMClassMock([PHPickerResult class]);
OCMStub([tiffResult itemProvider]).andReturn(tiffItemProvider);

NSURL *pngURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage"
withExtension:@"png"];
NSItemProvider *pngItemProvider = [[NSItemProvider alloc] initWithContentsOfURL:pngURL];
PHPickerResult *pngResult = OCMClassMock([PHPickerResult class]);
OCMStub([pngResult itemProvider]).andReturn(pngItemProvider);

FLTImagePickerPlugin *plugin = [[FLTImagePickerPlugin alloc] init];

XCTestExpectation *resultExpectation = [self expectationWithDescription:@"result"];

plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
initWithResult:^(NSArray<NSString *> *result, FlutterError *error) {
XCTAssertTrue(NSThread.isMainThread);
XCTAssertEqual(result.count, 2);
XCTAssertNil(error);
[resultExpectation fulfill];
}];

[plugin picker:mockPickerViewController didFinishPicking:@[ tiffResult, pngResult ]];

XCTAssertEqual(pickImageResult, pathList);
[self waitForExpectationsWithTimeout:30 handler:nil];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveWithTheCorrectExtentionAndM
maxWidth:nil
maxHeight:nil
imageQuality:nil];
XCTAssertNotNil(savedPathJPG);
XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4], @".jpg");
XCTAssertEqualObjects([NSURL URLWithString:savedPathJPG].pathExtension, @"jpg");

NSDictionary *originalMetaDataJPG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataJPG];
NSData *newDataJPG = [NSData dataWithContentsOfFile:savedPathJPG];
Expand All @@ -55,8 +54,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveWithTheCorrectExtentionAndM
maxWidth:nil
maxHeight:nil
imageQuality:nil];
XCTAssertNotNil(savedPathPNG);
XCTAssertEqualObjects([savedPathPNG substringFromIndex:savedPathPNG.length - 4], @".png");
XCTAssertEqualObjects([NSURL URLWithString:savedPathPNG].pathExtension, @"png");

NSDictionary *originalMetaDataPNG = [FLTImagePickerMetaDataUtil getMetaDataFromImageData:dataPNG];
NSData *newDataPNG = [NSData dataWithContentsOfFile:savedPathPNG];
Expand All @@ -69,8 +67,6 @@ - (void)testSaveImageWithPickerInfo_ShouldSaveWithDefaultExtention {
NSString *savedPathJPG = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil
image:imageJPG
imageQuality:nil];

XCTAssertNotNil(savedPathJPG);
// should be saved as
XCTAssertEqualObjects([savedPathJPG substringFromIndex:savedPathJPG.length - 4],
kFLTImagePickerDefaultSuffix);
Expand Down Expand Up @@ -98,7 +94,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsGifAnimation {
// test gif
NSData *dataGIF = ImagePickerTestImages.GIFTestData;
UIImage *imageGIF = [UIImage imageWithData:dataGIF];
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil);
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil);

size_t numberOfFrames = CGImageSourceGetCount(imageSource);

Expand All @@ -107,12 +103,12 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsGifAnimation {
maxWidth:nil
maxHeight:nil
imageQuality:nil];
XCTAssertNotNil(savedPathGIF);
XCTAssertEqualObjects([savedPathGIF substringFromIndex:savedPathGIF.length - 4], @".gif");
XCTAssertEqualObjects([NSURL URLWithString:savedPathGIF].pathExtension, @"gif");

NSData *newDataGIF = [NSData dataWithContentsOfFile:savedPathGIF];

CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil);
CGImageSourceRef newImageSource =
CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil);

size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource);

Expand All @@ -124,7 +120,7 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsScalledGifAnimation {
NSData *dataGIF = ImagePickerTestImages.GIFTestData;
UIImage *imageGIF = [UIImage imageWithData:dataGIF];

CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)dataGIF, nil);
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)dataGIF, nil);

size_t numberOfFrames = CGImageSourceGetCount(imageSource);

Expand All @@ -139,7 +135,8 @@ - (void)testSaveImageWithOriginalImageData_ShouldSaveAsScalledGifAnimation {
XCTAssertEqual(newImage.size.width, 3);
XCTAssertEqual(newImage.size.height, 2);

CGImageSourceRef newImageSource = CGImageSourceCreateWithData((CFDataRef)newDataGIF, nil);
CGImageSourceRef newImageSource =
CGImageSourceCreateWithData((__bridge CFDataRef)newDataGIF, nil);

size_t newNumberOfFrames = CGImageSourceGetCount(newImageSource);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
// found in the LICENSE file.

#import <OCMock/OCMock.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>

@import image_picker_ios;
@import image_picker_ios.Test;
@import UniformTypeIdentifiers;
@import XCTest;

@interface PickerSaveImageToPathOperationTests : XCTestCase
Expand Down Expand Up @@ -113,17 +113,71 @@ - (void)testSaveTIFFImage API_AVAILABLE(ios(14)) {
[self verifySavingImageWithPickerResult:result fullMetadata:YES];
}

- (void)testNonexistentImage API_AVAILABLE(ios(14)) {
NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"bogus"
withExtension:@"png"];
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
PHPickerResult *result = [self createPickerResultWithProvider:itemProvider];

XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid source error"];
FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
initWithResult:result
maxHeight:@100
maxWidth:@100
desiredImageQuality:@100
fullMetadata:YES
savedPathBlock:^(NSString *savedPath, FlutterError *error) {
XCTAssertEqualObjects(error.code, @"invalid_source");
[errorExpectation fulfill];
}];

[operation start];
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testFailingImageLoad API_AVAILABLE(ios(14)) {
NSError *loadDataError = [NSError errorWithDomain:@"PHPickerDomain" code:1234 userInfo:nil];

id mockItemProvider = OCMClassMock([NSItemProvider class]);
OCMStub([mockItemProvider hasItemConformingToTypeIdentifier:OCMOCK_ANY]).andReturn(YES);
[[mockItemProvider stub]
loadDataRepresentationForTypeIdentifier:OCMOCK_ANY
completionHandler:[OCMArg invokeBlockWithArgs:[NSNull null],
loadDataError, nil]];

id pickerResult = OCMClassMock([PHPickerResult class]);
OCMStub([pickerResult itemProvider]).andReturn(mockItemProvider);

XCTestExpectation *errorExpectation = [self expectationWithDescription:@"invalid image error"];

FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
initWithResult:pickerResult
maxHeight:@100
maxWidth:@100
desiredImageQuality:@100
fullMetadata:YES
savedPathBlock:^(NSString *savedPath, FlutterError *error) {
XCTAssertEqualObjects(error.code, @"invalid_image");
XCTAssertEqualObjects(error.message, loadDataError.localizedDescription);
XCTAssertEqualObjects(error.details, @"PHPickerDomain");
[errorExpectation fulfill];
}];

[operation start];
[self waitForExpectationsWithTimeout:30 handler:nil];
}

- (void)testSavePNGImageWithoutFullMetadata API_AVAILABLE(ios(14)) {
id photoAssetUtil = OCMClassMock([PHAsset class]);

NSURL *imageURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"pngImage"
withExtension:@"png"];
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithContentsOfURL:imageURL];
PHPickerResult *result = [self createPickerResultWithProvider:itemProvider];
OCMReject([photoAssetUtil fetchAssetsWithLocalIdentifiers:OCMOCK_ANY options:OCMOCK_ANY]);

[self verifySavingImageWithPickerResult:result fullMetadata:NO];
OCMVerify(times(0), [photoAssetUtil fetchAssetsWithLocalIdentifiers:[OCMArg any]
options:[OCMArg any]]);
OCMVerifyAll(photoAssetUtil);
}

/**
Expand Down Expand Up @@ -153,21 +207,26 @@ - (PHPickerResult *)createPickerResultWithProvider:(NSItemProvider *)itemProvide
- (void)verifySavingImageWithPickerResult:(PHPickerResult *)result
fullMetadata:(BOOL)fullMetadata API_AVAILABLE(ios(14)) {
XCTestExpectation *pathExpectation = [self expectationWithDescription:@"Path was created"];
XCTestExpectation *operationExpectation =
[self expectationWithDescription:@"Operation completed"];

FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc]
initWithResult:result
maxHeight:@100
maxWidth:@100
desiredImageQuality:@100
fullMetadata:fullMetadata
savedPathBlock:^(NSString *savedPath) {
if ([[NSFileManager defaultManager] fileExistsAtPath:savedPath]) {
[pathExpectation fulfill];
}
savedPathBlock:^(NSString *savedPath, FlutterError *error) {
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:savedPath]);
[pathExpectation fulfill];
}];
operation.completionBlock = ^{
[operationExpectation fulfill];
};

[operation start];
[self waitForExpectations:@[ pathExpectation ] timeout:30];
[self waitForExpectationsWithTimeout:30 handler:nil];
XCTAssertTrue(operation.isFinished);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,15 @@ + (GIFInfo *)scaledGIFImage:(NSData *)data
options[(NSString *)kCGImageSourceTypeIdentifierHint] = (NSString *)kUTTypeGIF;

CGImageSourceRef imageSource =
CGImageSourceCreateWithData((CFDataRef)data, (CFDictionaryRef)options);
CGImageSourceCreateWithData((__bridge CFDataRef)data, (__bridge CFDictionaryRef)options);

size_t numberOfFrames = CGImageSourceGetCount(imageSource);
NSMutableArray<UIImage *> *images = [NSMutableArray arrayWithCapacity:numberOfFrames];

NSTimeInterval interval = 0.0;
for (size_t index = 0; index < numberOfFrames; index++) {
CGImageRef imageRef =
CGImageSourceCreateImageAtIndex(imageSource, index, (CFDictionaryRef)options);
CGImageSourceCreateImageAtIndex(imageSource, index, (__bridge CFDictionaryRef)options);

NSDictionary *properties = (NSDictionary *)CFBridgingRelease(
CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ + (NSString *)imageTypeSuffixFromType:(FLTImagePickerMIMEType)type {
}

+ (NSDictionary *)getMetaDataFromImageData:(NSData *)imageData {
CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
NSDictionary *metadata =
(NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL));
CFRelease(source);
Expand Down
Loading