From b1dfdcfc4779e31755da404e6d007bcf5c879e37 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 1 Feb 2022 12:19:34 +0100 Subject: [PATCH 01/18] Default to image gallery when picking multiple images --- .../image_picker/image_picker/CHANGELOG.md | 5 ++++ .../ios/Classes/FLTImagePickerPlugin.m | 24 +++++++++---------- .../image_picker/image_picker/pubspec.yaml | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 1b7d0ffd4b57..c9ab76ad5222 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.8.4+6 + +* Fixes a bug where picking multiple images on pre-iOS 14 devices shows the +camera instead of expected gallery. + ## 0.8.4+5 * Improves the documentation on handling MainActivity being killed by the Android OS. diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index cf3103195482..28231e7b752c 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -101,13 +101,17 @@ - (void)pickImageWithPHPicker:(int)maxImagesAllowed API_AVAILABLE(ios(14)) { } - (void)pickImageWithUIImagePicker { + int imageSource = [[_arguments objectForKey:@"source"] intValue]; + + [self launchUIImagePickerWithSource:imageSource]; +} + +- (void)launchUIImagePickerWithSource:(int)imageSource { _imagePickerController = [[UIImagePickerController alloc] init]; _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; - - int imageSource = [[_arguments objectForKey:@"source"] intValue]; - + self.maxImagesAllowed = 1; switch (imageSource) { @@ -132,10 +136,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result details:nil]); self.result = nil; } + + self.result = result; + self.arguments = call.arguments; if ([@"pickImage" isEqualToString:call.method]) { - self.result = result; - _arguments = call.arguments; int imageSource = [[_arguments objectForKey:@"source"] intValue]; if (imageSource == SOURCE_GALLERY) { // Capture is not possible with PHPicker @@ -151,11 +156,9 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } } else if ([@"pickMultiImage" isEqualToString:call.method]) { if (@available(iOS 14, *)) { - self.result = result; - _arguments = call.arguments; [self pickImageWithPHPicker:0]; } else { - [self pickImageWithUIImagePicker]; + [self launchUIImagePickerWithSource:SOURCE_GALLERY]; } } else if ([@"pickVideo" isEqualToString:call.method]) { _imagePickerController = [[UIImagePickerController alloc] init]; @@ -166,10 +169,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result (NSString *)kUTTypeMPEG4 ]; _imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; - - self.result = result; - _arguments = call.arguments; - + int imageSource = [[_arguments objectForKey:@"source"] intValue]; if ([[_arguments objectForKey:@"maxDuration"] isKindOfClass:[NSNumber class]]) { NSTimeInterval max = [[_arguments objectForKey:@"maxDuration"] doubleValue]; diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 836c3ca93f3c..c072244263d0 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.4+5 +version: 0.8.4+6 environment: sdk: ">=2.14.0 <3.0.0" From 9d15c59f48755b29c194ce85b11d5e29ec8e17f0 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 1 Feb 2022 13:34:27 +0100 Subject: [PATCH 02/18] Add additional unit-tests --- .../image_picker/image_picker/CHANGELOG.md | 6 +- .../ios/RunnerTests/ImagePickerPluginTests.m | 56 +++++++++++++++++++ .../ios/Classes/FLTImagePickerPlugin.m | 14 ++--- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index c9ab76ad5222..332d2b759920 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,7 +1,9 @@ ## 0.8.4+6 -* Fixes a bug where picking multiple images on pre-iOS 14 devices shows the -camera instead of expected gallery. +* Ensures the `result` callback and method call `arguments` send to iOS are +taken into consideration when picking multiple images on pre-iOS 14 devices; +* Configures the `UIImagePicker` to default to gallery instead of camera on +pre-iOS 14 devices when picking multiple images on pre-iOS 14 devices. ## 0.8.4+5 diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index cc901f084071..a8181d949f08 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -23,6 +23,7 @@ - (UIViewController *)presentedViewController { @interface FLTImagePickerPlugin (Test) @property(copy, nonatomic) FlutterResult result; +@property(copy, nonatomic) NSDictionary *arguments; - (void)handleSavedPathList:(NSMutableArray *)pathList; - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; @end @@ -172,6 +173,61 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { [plugin imagePickerControllerDidCancel:[plugin getImagePickerController]]; } +#pragma mark - Test that arguments and results are set for all method calls +- (void)testPickImageShouldSetArgumentsAndResult { + FlutterResult expectedResult = ^(id _Nullable r) { + }; + NSDictionary *expectedArguments = + @{@"source" : @(1), @"maxWidth" : @(200), @"maxHeight" : @(200), @"imageQuality" : @(50)}; + + // Run test + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" + arguments:expectedArguments]; + + [plugin handleMethodCall:call result:expectedResult]; + + XCTAssertEqual(plugin.result, expectedResult); + XCTAssertEqual(plugin.arguments, expectedArguments); +} + +- (void)testPickMultiImageShouldSetArgumentsAndResult { + FlutterResult expectedResult = ^(id _Nullable r) { + }; + NSDictionary *expectedArguments = + @{@"maxWidth" : @(200), @"maxHeight" : @(200), @"imageQuality" : @(50)}; + + // Run test + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickMultiImage" + arguments:expectedArguments]; + + [plugin handleMethodCall:call result:expectedResult]; + + XCTAssertEqual(plugin.result, expectedResult); + XCTAssertEqual(plugin.arguments, expectedArguments); +} + +- (void)testPickVideoShouldSetArgumentsAndResult { + // AVAuthorizationStatusAuthorized is supported + OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) + .andReturn(AVAuthorizationStatusAuthorized); + + FlutterResult expectedResult = ^(id _Nullable r) { + }; + NSDictionary *expectedArguments = @{@"source" : @(1), @"maxDuration" : @(200)}; + + // Run test + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" + arguments:expectedArguments]; + + [plugin handleMethodCall:call result:expectedResult]; + + XCTAssertEqual(plugin.result, expectedResult); + XCTAssertEqual(plugin.arguments, expectedArguments); +} + #pragma mark - Test video duration - (void)testPickingVideoWithDuration { diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 28231e7b752c..3d2838a52d28 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -111,7 +111,7 @@ - (void)launchUIImagePickerWithSource:(int)imageSource { _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; - + self.maxImagesAllowed = 1; switch (imageSource) { @@ -136,12 +136,12 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result details:nil]); self.result = nil; } - + self.result = result; self.arguments = call.arguments; if ([@"pickImage" isEqualToString:call.method]) { - int imageSource = [[_arguments objectForKey:@"source"] intValue]; + int imageSource = [[self.arguments objectForKey:@"source"] intValue]; if (imageSource == SOURCE_GALLERY) { // Capture is not possible with PHPicker if (@available(iOS 14, *)) { @@ -169,10 +169,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result (NSString *)kUTTypeMPEG4 ]; _imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; - - int imageSource = [[_arguments objectForKey:@"source"] intValue]; - if ([[_arguments objectForKey:@"maxDuration"] isKindOfClass:[NSNumber class]]) { - NSTimeInterval max = [[_arguments objectForKey:@"maxDuration"] doubleValue]; + + int imageSource = [[self.arguments objectForKey:@"source"] intValue]; + if ([[self.arguments objectForKey:@"maxDuration"] isKindOfClass:[NSNumber class]]) { + NSTimeInterval max = [[self.arguments objectForKey:@"maxDuration"] doubleValue]; _imagePickerController.videoMaximumDuration = max; } From bd955e90ed3c04536174b05c18d91ebe9bcab35e Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Feb 2022 14:56:05 +0100 Subject: [PATCH 03/18] Refactored tests to expose private interface in separate test header. Refactored the existing unit tests to expose private interfaces in a separate test header instead of an inline interface. This was first introduced in the google_sign_in plugin (#4157) and after that also applied in the camera plugin (#4426) and the webview_flutter plugin (#4480). --- packages/image_picker/image_picker/CHANGELOG.md | 4 ++++ .../ios/RunnerTests/ImagePickerPluginTests.m | 7 +------ .../ios/Classes/FLTImagePickerPlugin.m | 3 +-- .../ios/Classes/FLTImagePickerPlugin_Test.h | 16 ++++++++++++++++ .../ios/Classes/ImagePickerPlugin.modulemap | 10 ++++++++++ .../ios/Classes/image_picker-umbrella.h | 10 ++++++++++ .../image_picker/ios/image_picker.podspec | 3 ++- packages/image_picker/image_picker/pubspec.yaml | 2 +- 8 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h create mode 100644 packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap create mode 100644 packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 1b7d0ffd4b57..a465bedd9e1c 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.4+6 + +* Refactores unit test to expose private interface via a separate test header instead of the inline declaration. + ## 0.8.4+5 * Improves the documentation on handling MainActivity being killed by the Android OS. diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index cc901f084071..bcaf2d1da37b 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -5,6 +5,7 @@ #import "ImagePickerTestImages.h" @import image_picker; +@import image_picker.Test; @import XCTest; #import @@ -21,12 +22,6 @@ - (UIViewController *)presentedViewController { @end -@interface FLTImagePickerPlugin (Test) -@property(copy, nonatomic) FlutterResult result; -- (void)handleSavedPathList:(NSMutableArray *)pathList; -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; -@end - @interface ImagePickerPluginTests : XCTestCase @property(readonly, nonatomic) id mockUIImagePicker; @property(readonly, nonatomic) id mockAVCaptureDevice; diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index cf3103195482..e14cbce90d15 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "FLTImagePickerPlugin.h" +#import "FLTImagePickerPlugin_Test.h" #import #import @@ -21,8 +22,6 @@ @interface FLTImagePickerPlugin () -@property(copy, nonatomic) FlutterResult result; - @property(assign, nonatomic) int maxImagesAllowed; @property(copy, nonatomic) NSDictionary *arguments; diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h new file mode 100644 index 000000000000..5dd3a7439148 --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -0,0 +1,16 @@ +// 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. + +// This header is available in the Test module. Import via "@import image_picker.Test;" + +#import + +/// Methods exposed for unit testing. +@interface FLTImagePickerPlugin () + +@property(copy, nonatomic) FlutterResult result; +- (void)handleSavedPathList:(NSArray *)pathList; +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; + +@end diff --git a/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap b/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap new file mode 100644 index 000000000000..532b6ad20a4a --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap @@ -0,0 +1,10 @@ +framework module image_picker { + umbrella header "image_picker-umbrella.h" + + export * + module * { export * } + + explicit module Test { + header "FLTImagePickerPlugin_Test.h" + } +} diff --git a/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h b/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h new file mode 100644 index 000000000000..b55c4ccd03d3 --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h @@ -0,0 +1,10 @@ +// 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. + +#import +#import +#import +#import +#import +#import diff --git a/packages/image_picker/image_picker/ios/image_picker.podspec b/packages/image_picker/image_picker/ios/image_picker.podspec index a2bba5c5397b..2a10b1ce01a8 100644 --- a/packages/image_picker/image_picker/ios/image_picker.podspec +++ b/packages/image_picker/image_picker/ios/image_picker.podspec @@ -14,8 +14,9 @@ Downloaded by pub (not CocoaPods). s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/image_picker' } s.documentation_url = 'https://pub.dev/packages/image_picker' - s.source_files = 'Classes/**/*' + s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' + s.module_map = 'Classes/ImagePickerPlugin.modulemap' s.dependency 'Flutter' s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 836c3ca93f3c..c072244263d0 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.4+5 +version: 0.8.4+6 environment: sdk: ">=2.14.0 <3.0.0" From e8333f87b5d1071d119cea43caaf84e6981d1f3a Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Feb 2022 21:50:18 +0100 Subject: [PATCH 04/18] Adds declaration comments. --- .../ios/Classes/FLTImagePickerPlugin_Test.h | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index 5dd3a7439148..ccdf5d8c1702 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -6,11 +6,37 @@ #import -/// Methods exposed for unit testing. +/** Methods exposed for unit testing. */ @interface FLTImagePickerPlugin () +/** The Flutter result callback use to report results back to Flutter App. */ @property(copy, nonatomic) FlutterResult result; + +/** + * Applies NSMutableArray on the FLutterResult. + * + * NSString must be returned by FlutterResult if the single image + * mode is active. It is checked by maxImagesAllowed and + * returns the first object of the pathlist. + * + * NSMutableArray must be returned by FlutterResult if the multi-image + * mode is active. After the pathlist count is checked then it returns + * the pathlist. + * + * @param pathList that should be applied to FlutterResult. + */ - (void)handleSavedPathList:(NSArray *)pathList; -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; + +/** + * Tells the delegate that the user cancelled the pick operation. + * + * Your delegate’s implementation of this method should dismiss the picker view + * by calling the dismissModalViewControllerAnimated: method of the parent + * view controller. + * + * Implementation of this method is optional, but expected. + * + * @param picker The controller object managing the image picker interface. + */- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; @end From f60f73dd851f0af019524e8fc76e5e14a3203942 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Feb 2022 22:03:51 +0100 Subject: [PATCH 05/18] Fix spelling error in changelog --- packages/image_picker/image_picker/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index a465bedd9e1c..fb2b2c0e5c8f 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.8.4+6 -* Refactores unit test to expose private interface via a separate test header instead of the inline declaration. +* Refactors unit test to expose private interface via a separate test header instead of the inline declaration. ## 0.8.4+5 From 3921f8d367fbd9ddbf8f68b2e207ffcb8498fbaf Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Feb 2022 08:55:01 +0100 Subject: [PATCH 06/18] Fix formatting --- .../image_picker/ios/Classes/FLTImagePickerPlugin_Test.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index ccdf5d8c1702..de369d650e64 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -37,6 +37,7 @@ * Implementation of this method is optional, but expected. * * @param picker The controller object managing the image picker interface. - */- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; + */ +-(void)imagePickerControllerDidCancel : (UIImagePickerController *)picker; @end From 8707cd81392b42aa4277b7a2c24a853a52b89e3c Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Feb 2022 17:22:01 +0100 Subject: [PATCH 07/18] Applied feedback from PR --- .../ios/RunnerTests/ImagePickerPluginTests.m | 84 +++++++------------ .../ios/Classes/FLTImagePickerPlugin.m | 27 +++++- .../ios/Classes/FLTImagePickerPlugin_Test.h | 9 +- 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index aff4284a7bf4..0385f20747f8 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -22,6 +22,9 @@ - (UIViewController *)presentedViewController { @end +// Set a long timeout to avoid flake due to slow CI. +static const NSTimeInterval kTimeout = 30.0; + @interface ImagePickerPluginTests : XCTestCase @property(readonly, nonatomic) id mockUIImagePicker; @property(readonly, nonatomic) id mockAVCaptureDevice; @@ -146,6 +149,32 @@ - (void)testPluginPickVideoDeviceFront { UIImagePickerControllerCameraDeviceFront); } +- (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { + if (@available(iOS 14, *)) { + return; + } + + id photoLibrary = OCMClassMock([PHPhotoLibrary class]); + OCMStub(ClassMethod([photoLibrary authorizationStatus])) + .andReturn(PHAuthorizationStatusAuthorized); + + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + [plugin setImagePickerControllerOverride:_mockUIImagePicker]; + FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickMultiImage" + arguments:@{ + @"maxWidth" : @(100), + @"maxHeight" : @(200), + @"imageQuality" : @(50), + }]; + + [plugin handleMethodCall:call + result:^(id _Nullable r){ + }]; + + OCMVerify(times(1), + [self->_mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]); +} + #pragma mark - Test camera devices, no op on simulators - (void)testPluginPickImageDeviceCancelClickMultipleTimes { @@ -167,61 +196,6 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { [plugin imagePickerControllerDidCancel:[plugin getImagePickerController]]; } -#pragma mark - Test that arguments and results are set for all method calls -- (void)testPickImageShouldSetArgumentsAndResult { - FlutterResult expectedResult = ^(id _Nullable r) { - }; - NSDictionary *expectedArguments = - @{@"source" : @(1), @"maxWidth" : @(200), @"maxHeight" : @(200), @"imageQuality" : @(50)}; - - // Run test - FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" - arguments:expectedArguments]; - - [plugin handleMethodCall:call result:expectedResult]; - - XCTAssertEqual(plugin.result, expectedResult); - XCTAssertEqual(plugin.arguments, expectedArguments); -} - -- (void)testPickMultiImageShouldSetArgumentsAndResult { - FlutterResult expectedResult = ^(id _Nullable r) { - }; - NSDictionary *expectedArguments = - @{@"maxWidth" : @(200), @"maxHeight" : @(200), @"imageQuality" : @(50)}; - - // Run test - FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickMultiImage" - arguments:expectedArguments]; - - [plugin handleMethodCall:call result:expectedResult]; - - XCTAssertEqual(plugin.result, expectedResult); - XCTAssertEqual(plugin.arguments, expectedArguments); -} - -- (void)testPickVideoShouldSetArgumentsAndResult { - // AVAuthorizationStatusAuthorized is supported - OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) - .andReturn(AVAuthorizationStatusAuthorized); - - FlutterResult expectedResult = ^(id _Nullable r) { - }; - NSDictionary *expectedArguments = @{@"source" : @(1), @"maxDuration" : @(200)}; - - // Run test - FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; - FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" - arguments:expectedArguments]; - - [plugin handleMethodCall:call result:expectedResult]; - - XCTAssertEqual(plugin.result, expectedResult); - XCTAssertEqual(plugin.arguments, expectedArguments); -} - #pragma mark - Test video duration - (void)testPickingVideoWithDuration { diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index edd96cb40b28..905b601cd403 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -36,6 +36,7 @@ @interface FLTImagePickerPlugin () *)registrar { [registrar addMethodCallDelegate:instance channel:channel]; } +/** + * Initializes the _imagePickerController member with a new instance of the + * UIImagePickerController class. + * + * A new instance of the UIImagePickerController is created every time the + * initImagePickerController method is called. For testing purposes this can + * be overriden using the setImagePickerControllerOverride method, in which + * case the set instance of the UIImagePickerController is used to initialize + * the _imagePickerController member. + */ +- (void)initImagePickerController { + if (_imagePickerControllerOverride) { + _imagePickerController = _imagePickerControllerOverride; + } else { + _imagePickerController = [[UIImagePickerController alloc] init]; + } +} + - (UIImagePickerController *)getImagePickerController { return _imagePickerController; } +- (void)setImagePickerControllerOverride:(UIImagePickerController *)imagePickerController { + _imagePickerControllerOverride = imagePickerController; +} + - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { UIWindow *windowToUse = window; if (windowToUse == nil) { @@ -106,7 +129,7 @@ - (void)pickImageWithUIImagePicker { } - (void)launchUIImagePickerWithSource:(int)imageSource { - _imagePickerController = [[UIImagePickerController alloc] init]; + [self initImagePickerController]; _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; @@ -160,7 +183,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self launchUIImagePickerWithSource:SOURCE_GALLERY]; } } else if ([@"pickVideo" isEqualToString:call.method]) { - _imagePickerController = [[UIImagePickerController alloc] init]; + [self initImagePickerController]; _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; _imagePickerController.mediaTypes = @[ diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index de369d650e64..c232145fd1ce 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -38,6 +38,13 @@ * * @param picker The controller object managing the image picker interface. */ --(void)imagePickerControllerDidCancel : (UIImagePickerController *)picker; +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; +/** + * Sets the UIImagePickerController instance that should be used by the + * image_picker plugin. + * + * Should be used for testing purposes only. + */ +- (void)setImagePickerControllerOverride:(UIImagePickerController *)imagePickerController; @end From 441dea1a47c43f79760c6e3f0ec8635186e3509c Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Feb 2022 14:56:05 +0100 Subject: [PATCH 08/18] Refactored tests to expose private interface in separate test header. Refactored the existing unit tests to expose private interfaces in a separate test header instead of an inline interface. This was first introduced in the google_sign_in plugin (#4157) and after that also applied in the camera plugin (#4426) and the webview_flutter plugin (#4480). --- packages/image_picker/image_picker/CHANGELOG.md | 4 ++++ .../ios/RunnerTests/ImagePickerPluginTests.m | 7 +------ .../ios/Classes/FLTImagePickerPlugin.m | 3 +-- .../ios/Classes/FLTImagePickerPlugin_Test.h | 16 ++++++++++++++++ .../ios/Classes/ImagePickerPlugin.modulemap | 10 ++++++++++ .../ios/Classes/image_picker-umbrella.h | 10 ++++++++++ .../image_picker/ios/image_picker.podspec | 3 ++- packages/image_picker/image_picker/pubspec.yaml | 2 +- 8 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h create mode 100644 packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap create mode 100644 packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 1712aa0d3bab..0e33c343e1a2 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.4+7 + +* Refactores unit test to expose private interface via a separate test header instead of the inline declaration. + ## 0.8.4+6 * Fixes minor type issues in iOS implementation. diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index cc901f084071..bcaf2d1da37b 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -5,6 +5,7 @@ #import "ImagePickerTestImages.h" @import image_picker; +@import image_picker.Test; @import XCTest; #import @@ -21,12 +22,6 @@ - (UIViewController *)presentedViewController { @end -@interface FLTImagePickerPlugin (Test) -@property(copy, nonatomic) FlutterResult result; -- (void)handleSavedPathList:(NSMutableArray *)pathList; -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; -@end - @interface ImagePickerPluginTests : XCTestCase @property(readonly, nonatomic) id mockUIImagePicker; @property(readonly, nonatomic) id mockAVCaptureDevice; diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 65a5fb8a71dd..1301398fb0c1 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "FLTImagePickerPlugin.h" +#import "FLTImagePickerPlugin_Test.h" #import #import @@ -30,8 +31,6 @@ @interface FLTImagePickerPlugin () -@property(copy, nonatomic) FlutterResult result; - @property(assign, nonatomic) int maxImagesAllowed; @property(copy, nonatomic) NSDictionary *arguments; diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h new file mode 100644 index 000000000000..5dd3a7439148 --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -0,0 +1,16 @@ +// 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. + +// This header is available in the Test module. Import via "@import image_picker.Test;" + +#import + +/// Methods exposed for unit testing. +@interface FLTImagePickerPlugin () + +@property(copy, nonatomic) FlutterResult result; +- (void)handleSavedPathList:(NSArray *)pathList; +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; + +@end diff --git a/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap b/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap new file mode 100644 index 000000000000..532b6ad20a4a --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap @@ -0,0 +1,10 @@ +framework module image_picker { + umbrella header "image_picker-umbrella.h" + + export * + module * { export * } + + explicit module Test { + header "FLTImagePickerPlugin_Test.h" + } +} diff --git a/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h b/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h new file mode 100644 index 000000000000..b55c4ccd03d3 --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h @@ -0,0 +1,10 @@ +// 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. + +#import +#import +#import +#import +#import +#import diff --git a/packages/image_picker/image_picker/ios/image_picker.podspec b/packages/image_picker/image_picker/ios/image_picker.podspec index a2bba5c5397b..2a10b1ce01a8 100644 --- a/packages/image_picker/image_picker/ios/image_picker.podspec +++ b/packages/image_picker/image_picker/ios/image_picker.podspec @@ -14,8 +14,9 @@ Downloaded by pub (not CocoaPods). s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } s.source = { :http => 'https://github.com/flutter/plugins/tree/main/packages/image_picker' } s.documentation_url = 'https://pub.dev/packages/image_picker' - s.source_files = 'Classes/**/*' + s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/**/*.h' + s.module_map = 'Classes/ImagePickerPlugin.modulemap' s.dependency 'Flutter' s.platform = :ios, '9.0' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index c072244263d0..7142d8238b30 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.4+6 +version: 0.8.4+7 environment: sdk: ">=2.14.0 <3.0.0" From 6fc92d6def4d3d4835a2c31f8d008b73b0253703 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Feb 2022 21:50:18 +0100 Subject: [PATCH 09/18] Adds declaration comments. --- .../ios/Classes/FLTImagePickerPlugin_Test.h | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index 5dd3a7439148..ccdf5d8c1702 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -6,11 +6,37 @@ #import -/// Methods exposed for unit testing. +/** Methods exposed for unit testing. */ @interface FLTImagePickerPlugin () +/** The Flutter result callback use to report results back to Flutter App. */ @property(copy, nonatomic) FlutterResult result; + +/** + * Applies NSMutableArray on the FLutterResult. + * + * NSString must be returned by FlutterResult if the single image + * mode is active. It is checked by maxImagesAllowed and + * returns the first object of the pathlist. + * + * NSMutableArray must be returned by FlutterResult if the multi-image + * mode is active. After the pathlist count is checked then it returns + * the pathlist. + * + * @param pathList that should be applied to FlutterResult. + */ - (void)handleSavedPathList:(NSArray *)pathList; -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; + +/** + * Tells the delegate that the user cancelled the pick operation. + * + * Your delegate’s implementation of this method should dismiss the picker view + * by calling the dismissModalViewControllerAnimated: method of the parent + * view controller. + * + * Implementation of this method is optional, but expected. + * + * @param picker The controller object managing the image picker interface. + */- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; @end From 3a6d1b5b694118a122514e01f918e839761b98de Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 2 Feb 2022 22:03:51 +0100 Subject: [PATCH 10/18] Fix spelling error in changelog --- packages/image_picker/image_picker/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 0e33c343e1a2..3dbf2c51690e 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.8.4+7 -* Refactores unit test to expose private interface via a separate test header instead of the inline declaration. +* Refactors unit test to expose private interface via a separate test header instead of the inline declaration. ## 0.8.4+6 From 69a41d8785797f8daee2845a7864b53513092d62 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 3 Feb 2022 08:55:01 +0100 Subject: [PATCH 11/18] Fix formatting --- .../image_picker/ios/Classes/FLTImagePickerPlugin_Test.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index ccdf5d8c1702..de369d650e64 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -37,6 +37,7 @@ * Implementation of this method is optional, but expected. * * @param picker The controller object managing the image picker interface. - */- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; + */ +-(void)imagePickerControllerDidCancel : (UIImagePickerController *)picker; @end From 31442f3a9787635cde692e894db473722c0b35d2 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 7 Feb 2022 20:40:01 +0100 Subject: [PATCH 12/18] Remove private headers from umbrella --- .../image_picker/example/ios/RunnerTests/ImageUtilTests.m | 1 + .../image_picker/example/ios/RunnerTests/MetaDataUtilTests.m | 1 + .../example/ios/RunnerTests/PhotoAssetUtilTests.m | 1 + .../image_picker/ios/Classes/FLTImagePickerPlugin_Test.h | 2 +- .../image_picker/ios/Classes/ImagePickerPlugin.modulemap | 4 ++++ .../image_picker/ios/Classes/image_picker-umbrella.h | 4 ---- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImageUtilTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImageUtilTests.m index b793d6e1f3e0..9b9719f88116 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImageUtilTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImageUtilTests.m @@ -5,6 +5,7 @@ #import "ImagePickerTestImages.h" @import image_picker; +@import image_picker.Test; @import XCTest; @interface ImageUtilTests : XCTestCase diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/MetaDataUtilTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/MetaDataUtilTests.m index 54f9469f2053..4160c51cc600 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/MetaDataUtilTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/MetaDataUtilTests.m @@ -5,6 +5,7 @@ #import "ImagePickerTestImages.h" @import image_picker; +@import image_picker.Test; @import XCTest; @interface MetaDataUtilTests : XCTestCase diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/PhotoAssetUtilTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/PhotoAssetUtilTests.m index c38930f5cef5..97b4b6cd8eb3 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/PhotoAssetUtilTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/PhotoAssetUtilTests.m @@ -5,6 +5,7 @@ #import "ImagePickerTestImages.h" @import image_picker; +@import image_picker.Test; @import XCTest; @interface PhotoAssetUtilTests : XCTestCase diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index de369d650e64..94eca425eb5e 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -38,6 +38,6 @@ * * @param picker The controller object managing the image picker interface. */ --(void)imagePickerControllerDidCancel : (UIImagePickerController *)picker; +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; @end diff --git a/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap b/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap index 532b6ad20a4a..dc702ea49fb1 100644 --- a/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap +++ b/packages/image_picker/image_picker/ios/Classes/ImagePickerPlugin.modulemap @@ -6,5 +6,9 @@ framework module image_picker { explicit module Test { header "FLTImagePickerPlugin_Test.h" + header "FLTImagePickerImageUtil.h" + header "FLTImagePickerMetaDataUtil.h" + header "FLTImagePickerPhotoAssetUtil.h" + header "FLTPHPickerSaveImageToPathOperation.h" } } diff --git a/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h b/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h index b55c4ccd03d3..0d89b2e1f636 100644 --- a/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h +++ b/packages/image_picker/image_picker/ios/Classes/image_picker-umbrella.h @@ -3,8 +3,4 @@ // found in the LICENSE file. #import -#import -#import -#import #import -#import From 9b7166198d1982d499072c84aa104d34fd3efd05 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 8 Feb 2022 11:23:33 +0100 Subject: [PATCH 13/18] Removes the pickImageWithUIImagePicker trampoline method. --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 356b9b94859b..1b84be3377db 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -131,12 +131,6 @@ - (void)pickImageWithPHPicker:(int)maxImagesAllowed API_AVAILABLE(ios(14)) { [self checkPhotoAuthorizationForAccessLevel]; } -- (void)pickImageWithUIImagePicker { - int imageSource = [[_arguments objectForKey:@"source"] intValue]; - - [self launchUIImagePickerWithSource:imageSource]; -} - - (void)launchUIImagePickerWithSource:(int)imageSource { [self initImagePickerController]; _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; @@ -180,10 +174,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self pickImageWithPHPicker:1]; } else { // UIImagePicker is used - [self pickImageWithUIImagePicker]; + [self launchUIImagePickerWithSource:imageSource]; } } else { - [self pickImageWithUIImagePicker]; + [self launchUIImagePickerWithSource:imageSource]; } } else if ([@"pickMultiImage" isEqualToString:call.method]) { if (@available(iOS 14, *)) { From 76131a79570c21056f46d852852882709a853dea Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Tue, 8 Feb 2022 11:27:56 +0100 Subject: [PATCH 14/18] Use call.arguments instead of self.arguments. --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 1b84be3377db..1688bc185464 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -33,8 +33,6 @@ @interface FLTImagePickerPlugin () Date: Wed, 9 Feb 2022 21:13:57 +0100 Subject: [PATCH 15/18] Apply feedback on PR --- .../ios/RunnerTests/ImagePickerPluginTests.m | 2 +- .../ios/Classes/FLTImagePickerPlugin.m | 83 ++++++++++--------- .../ios/Classes/FLTImagePickerPlugin_Test.h | 10 ++- 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index dbf44d62ac6c..5f6c04eb00fa 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -156,7 +156,7 @@ - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { .andReturn(PHAuthorizationStatusAuthorized); FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; - [plugin setImagePickerControllerOverride:_mockUIImagePicker]; + [plugin setImagePickerControllerOverrides:@[_mockUIImagePicker]]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickMultiImage" arguments:@{ @"maxWidth" : @(100), diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 1688bc185464..0d9180e6fe08 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -33,8 +33,14 @@ @interface FLTImagePickerPlugin () *imagePickerControllerOverrides; + @end static const int SOURCE_CAMERA = 0; @@ -42,11 +48,7 @@ @interface FLTImagePickerPlugin () *)registrar { FlutterMethodChannel *channel = @@ -57,29 +59,30 @@ + (void)registerWithRegistrar:(NSObject *)registrar { } /** - * Initializes the _imagePickerController member with a new instance of the - * UIImagePickerController class. + * Creates a new UIImagePickerController. * * A new instance of the UIImagePickerController is created every time the - * initImagePickerController method is called. For testing purposes this can - * be overriden using the setImagePickerControllerOverride method, in which - * case the set instance of the UIImagePickerController is used to initialize - * the _imagePickerController member. + * createUIImagePickerController method is called. For testing purposes dummy + * instances can be injected using the setImagePickerControllerOverrides + * method. Each call to createImagePickerController will remove the current first + * element from the supplied array untill none are left. Calling + * createImagePickerController after that will return new instance of the + * UIImagePickerController */ -- (void)initImagePickerController { - if (_imagePickerControllerOverride) { - _imagePickerController = _imagePickerControllerOverride; - } else { - _imagePickerController = [[UIImagePickerController alloc] init]; +- (UIImagePickerController *)createImagePickerController { + if ([self.imagePickerControllerOverrides count] > 0) { + UIImagePickerController *controller = [self.imagePickerControllerOverrides firstObject]; + [self.imagePickerControllerOverrides removeObjectAtIndex:0]; + self.imagePickerController = controller; + return controller; } + + self.imagePickerController = [[UIImagePickerController alloc] init]; + return self.imagePickerController; } -- (UIImagePickerController *)getImagePickerController { - return _imagePickerController; -} - -- (void)setImagePickerControllerOverride:(UIImagePickerController *)imagePickerController { - _imagePickerControllerOverride = imagePickerController; +- (void)setImagePickerControllerOverride:(NSArray *)imagePickerControllers { + _imagePickerControllerOverrides = [imagePickerControllers mutableCopy]; } - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { @@ -110,7 +113,7 @@ - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { * @param arguments that should be used to get cameraDevice value. */ - (UIImagePickerControllerCameraDevice)getCameraDeviceFromArguments:(NSDictionary *)arguments { - NSInteger cameraDevice = [[arguments objectForKey:@"cameraDevice"] intValue]; + NSInteger cameraDevice = [arguments[@"cameraDevice"] intValue]; return (cameraDevice == 1) ? UIImagePickerControllerCameraDeviceFront : UIImagePickerControllerCameraDeviceRear; } @@ -131,10 +134,10 @@ - (void)pickImageWithPHPicker:(int)maxImagesAllowed API_AVAILABLE(ios(14)) { } - (void)launchUIImagePickerWithSource:(int)imageSource { - [self initImagePickerController]; - _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; - _imagePickerController.delegate = self; - _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; + UIImagePickerController *imagePickerController = [self createImagePickerController]; + imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + imagePickerController.delegate = self; + imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; self.maxImagesAllowed = 1; @@ -165,7 +168,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result _arguments = call.arguments; if ([@"pickImage" isEqualToString:call.method]) { - int imageSource = [[call.arguments objectForKey:@"source"] intValue]; + int imageSource = [call.arguments[@"source"] intValue]; if (imageSource == SOURCE_GALLERY) { // Capture is not possible with PHPicker if (@available(iOS 14, *)) { @@ -185,19 +188,19 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self launchUIImagePickerWithSource:SOURCE_GALLERY]; } } else if ([@"pickVideo" isEqualToString:call.method]) { - [self initImagePickerController]; - _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; - _imagePickerController.delegate = self; - _imagePickerController.mediaTypes = @[ + UIImagePickerController *imagePickerController = [self createImagePickerController]; + imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + imagePickerController.delegate = self; + imagePickerController.mediaTypes = @[ (NSString *)kUTTypeMovie, (NSString *)kUTTypeAVIMovie, (NSString *)kUTTypeVideo, (NSString *)kUTTypeMPEG4 ]; - _imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; + imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; - int imageSource = [[call.arguments objectForKey:@"source"] intValue]; - if ([[call.arguments objectForKey:@"maxDuration"] isKindOfClass:[NSNumber class]]) { - NSTimeInterval max = [[call.arguments objectForKey:@"maxDuration"] doubleValue]; - _imagePickerController.videoMaximumDuration = max; + int imageSource = [call.arguments[@"source"] intValue]; + if ([call.arguments[@"maxDuration"] isKindOfClass:[NSNumber class]]) { + NSTimeInterval max = [call.arguments[@"maxDuration"] doubleValue]; + imagePickerController.videoMaximumDuration = max; } switch (imageSource) { @@ -465,7 +468,7 @@ - (NSMutableArray *)createNSMutableArrayWithSize:(NSUInteger)size { - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL]; + NSURL *videoURL = info[UIImagePickerControllerMediaURL]; [_imagePickerController dismissViewControllerAnimated:YES completion:nil]; // The method dismissViewControllerAnimated does not immediately prevent // further didFinishPickingMediaWithInfo invocations. A nil check is necessary @@ -500,9 +503,9 @@ - (void)imagePickerController:(UIImagePickerController *)picker self.result = nil; _arguments = nil; } else { - UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage]; + UIImage *image = info[UIImagePickerControllerEditedImage]; if (image == nil) { - image = [info objectForKey:UIImagePickerControllerOriginalImage]; + image = info[UIImagePickerControllerOriginalImage]; } NSNumber *maxWidth = GetNullableValueForKey(_arguments, @"maxWidth"); NSNumber *maxHeight = GetNullableValueForKey(_arguments, @"maxHeight"); diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index 69fe4cb83ebf..8d847d980304 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -41,11 +41,13 @@ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; /** - * Sets the UIImagePickerController instance that should be used by the - * image_picker plugin. - * + * Sets UIImagePickerController instances that will be used when a new + * controller would normally be created. Each call to + * createImagePickerController will remove the current first element from + * the array. + * * Should be used for testing purposes only. */ -- (void)setImagePickerControllerOverride:(UIImagePickerController *)imagePickerController; +- (void)setImagePickerControllerOverrides:(NSArray *)imagePickerControllers; @end From 134ba8baca688cbb253ba3478ec0056b80907ad2 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 9 Feb 2022 21:59:11 +0100 Subject: [PATCH 16/18] Refactored FLTImagePickerPlugin not to be stateful --- .../ios/RunnerTests/ImagePickerPluginTests.m | 81 ++++++++------ .../ios/Classes/FLTImagePickerPlugin.h | 1 - .../ios/Classes/FLTImagePickerPlugin.m | 100 +++++++++--------- .../ios/Classes/FLTImagePickerPlugin_Test.h | 5 +- 4 files changed, 102 insertions(+), 85 deletions(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index 5f6c04eb00fa..5f3287400c5e 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -23,30 +23,26 @@ - (UIViewController *)presentedViewController { @end @interface ImagePickerPluginTests : XCTestCase -@property(readonly, nonatomic) id mockUIImagePicker; -@property(readonly, nonatomic) id mockAVCaptureDevice; + @end @implementation ImagePickerPluginTests -- (void)setUp { - _mockUIImagePicker = OCMClassMock([UIImagePickerController class]); - _mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); -} - - (void)testPluginPickImageDeviceBack { + id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); + id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( - [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) + [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceRear is supported OCMStub(ClassMethod( - [_mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])) + [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported - OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) + OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test @@ -54,27 +50,30 @@ - (void)testPluginPickImageDeviceBack { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; + UIImagePickerController *controller = [[UIImagePickerController alloc] init]; + [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin handleMethodCall:call result:^(id _Nullable r){ }]; - XCTAssertEqual([plugin getImagePickerController].cameraDevice, - UIImagePickerControllerCameraDeviceRear); + XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceRear); } - (void)testPluginPickImageDeviceFront { + id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); + id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( - [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) + [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceFront is supported - OCMStub(ClassMethod([_mockUIImagePicker - isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])) + OCMStub(ClassMethod( + [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported - OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) + OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test @@ -82,27 +81,30 @@ - (void)testPluginPickImageDeviceFront { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; + UIImagePickerController *controller = [[UIImagePickerController alloc] init]; + [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin handleMethodCall:call result:^(id _Nullable r){ }]; - XCTAssertEqual([plugin getImagePickerController].cameraDevice, - UIImagePickerControllerCameraDeviceFront); + XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceFront); } - (void)testPluginPickVideoDeviceBack { + id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); + id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( - [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) + [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceRear is supported OCMStub(ClassMethod( - [_mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])) + [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported - OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) + OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test @@ -110,27 +112,31 @@ - (void)testPluginPickVideoDeviceBack { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; + UIImagePickerController *controller = [[UIImagePickerController alloc] init]; + [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin handleMethodCall:call result:^(id _Nullable r){ }]; - XCTAssertEqual([plugin getImagePickerController].cameraDevice, - UIImagePickerControllerCameraDeviceRear); + XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceRear); } - (void)testPluginPickVideoDeviceFront { + id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); + id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); + // UIImagePickerControllerSourceTypeCamera is supported OCMStub(ClassMethod( - [_mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) + [mockUIImagePicker isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])) .andReturn(YES); // UIImagePickerControllerCameraDeviceFront is supported - OCMStub(ClassMethod([_mockUIImagePicker - isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])) + OCMStub(ClassMethod( + [mockUIImagePicker isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront])) .andReturn(YES); // AVAuthorizationStatusAuthorized is supported - OCMStub([_mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) + OCMStub([mockAVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) .andReturn(AVAuthorizationStatusAuthorized); // Run test @@ -138,12 +144,13 @@ - (void)testPluginPickVideoDeviceFront { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; + UIImagePickerController *controller = [[UIImagePickerController alloc] init]; + [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin handleMethodCall:call result:^(id _Nullable r){ }]; - XCTAssertEqual([plugin getImagePickerController].cameraDevice, - UIImagePickerControllerCameraDeviceFront); + XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceFront); } - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { @@ -151,12 +158,13 @@ - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { return; } + id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id photoLibrary = OCMClassMock([PHPhotoLibrary class]); OCMStub(ClassMethod([photoLibrary authorizationStatus])) .andReturn(PHAuthorizationStatusAuthorized); FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; - [plugin setImagePickerControllerOverrides:@[_mockUIImagePicker]]; + [plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickMultiImage" arguments:@{ @"maxWidth" : @(100), @@ -169,7 +177,7 @@ - (void)testPickMultiImageShouldUseUIImagePickerControllerOnPreiOS14 { }]; OCMVerify(times(1), - [self->_mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]); + [mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]); } #pragma mark - Test camera devices, no op on simulators @@ -182,15 +190,18 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; + UIImagePickerController *controller = [[UIImagePickerController alloc] init]; + plugin.imagePickerControllerOverrides = @[ controller ]; [plugin handleMethodCall:call result:^(id _Nullable r){ }]; plugin.result = ^(id result) { }; + // To ensure the flow does not crash by multiple cancel call - [plugin imagePickerControllerDidCancel:[plugin getImagePickerController]]; - [plugin imagePickerControllerDidCancel:[plugin getImagePickerController]]; + [plugin imagePickerControllerDidCancel:controller]; + [plugin imagePickerControllerDidCancel:controller]; } #pragma mark - Test video duration @@ -200,10 +211,12 @@ - (void)testPickingVideoWithDuration { FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(0), @"maxDuration" : @95}]; + UIImagePickerController *controller = [[UIImagePickerController alloc] init]; + [plugin setImagePickerControllerOverrides:@[ controller ]]; [plugin handleMethodCall:call result:^(id _Nullable r){ }]; - XCTAssertEqual([plugin getImagePickerController].videoMaximumDuration, 95); + XCTAssertEqual(controller.videoMaximumDuration, 95); } - (void)testViewController { diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h index ffd23cd3df6a..c88db0bad72f 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h @@ -8,7 +8,6 @@ @interface FLTImagePickerPlugin : NSObject // For testing only. -- (UIImagePickerController *)getImagePickerController; - (UIViewController *)viewControllerWithWindow:(UIWindow *)window; @end diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 0d9180e6fe08..246f482e9a85 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -39,7 +39,8 @@ @interface FLTImagePickerPlugin () *imagePickerControllerOverrides; +@property(strong, nonatomic) + NSMutableArray *imagePickerControllerOverrides; @end @@ -73,15 +74,14 @@ - (UIImagePickerController *)createImagePickerController { if ([self.imagePickerControllerOverrides count] > 0) { UIImagePickerController *controller = [self.imagePickerControllerOverrides firstObject]; [self.imagePickerControllerOverrides removeObjectAtIndex:0]; - self.imagePickerController = controller; return controller; } - - self.imagePickerController = [[UIImagePickerController alloc] init]; - return self.imagePickerController; + + return [[UIImagePickerController alloc] init]; } -- (void)setImagePickerControllerOverride:(NSArray *)imagePickerControllers { +- (void)setImagePickerControllerOverrides: + (NSArray *)imagePickerControllers { _imagePickerControllerOverrides = [imagePickerControllers mutableCopy]; } @@ -143,10 +143,10 @@ - (void)launchUIImagePickerWithSource:(int)imageSource { switch (imageSource) { case SOURCE_CAMERA: - [self checkCameraAuthorization]; + [self checkCameraAuthorizationWithImagePickerController:imagePickerController]; break; case SOURCE_GALLERY: - [self checkPhotoAuthorization]; + [self checkPhotoAuthorizationWithImagePickerController:imagePickerController]; break; default: self.result([FlutterError errorWithCode:@"invalid_source" @@ -205,10 +205,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result switch (imageSource) { case SOURCE_CAMERA: - [self checkCameraAuthorization]; + [self checkCameraAuthorizationWithImagePickerController:imagePickerController]; break; case SOURCE_GALLERY: - [self checkPhotoAuthorization]; + [self checkPhotoAuthorizationWithImagePickerController:imagePickerController]; break; default: result([FlutterError errorWithCode:@"invalid_source" @@ -221,9 +221,9 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } } -- (void)showCamera { +- (void)showCameraWithImagePickerController:(UIImagePickerController *)imagePickerController { @synchronized(self) { - if (_imagePickerController.beingPresented) { + if (imagePickerController.beingPresented) { return; } } @@ -231,9 +231,9 @@ - (void)showCamera { // Camera is not available on simulators if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] && [UIImagePickerController isCameraDeviceAvailable:device]) { - _imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; - _imagePickerController.cameraDevice = device; - [[self viewControllerWithWindow:nil] presentViewController:_imagePickerController + imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; + imagePickerController.cameraDevice = device; + [[self viewControllerWithWindow:nil] presentViewController:imagePickerController animated:YES completion:nil]; } else { @@ -257,24 +257,26 @@ - (void)showCamera { } } -- (void)checkCameraAuthorization { +- (void)checkCameraAuthorizationWithImagePickerController: + (UIImagePickerController *)imagePickerController { AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; switch (status) { case AVAuthorizationStatusAuthorized: - [self showCamera]; + [self showCameraWithImagePickerController:imagePickerController]; break; case AVAuthorizationStatusNotDetermined: { - [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo - completionHandler:^(BOOL granted) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (granted) { - [self showCamera]; - } else { - [self errorNoCameraAccess:AVAuthorizationStatusDenied]; - } - }); - }]; + [AVCaptureDevice + requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (granted) { + [self showCameraWithImagePickerController:imagePickerController]; + } else { + [self errorNoCameraAccess:AVAuthorizationStatusDenied]; + } + }); + }]; break; } case AVAuthorizationStatusDenied: @@ -285,14 +287,15 @@ - (void)checkCameraAuthorization { } } -- (void)checkPhotoAuthorization { +- (void)checkPhotoAuthorizationWithImagePickerController: + (UIImagePickerController *)imagePickerController { PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; switch (status) { case PHAuthorizationStatusNotDetermined: { [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusAuthorized) { - [self showPhotoLibrary:UIImagePickerClassType]; + [self showPhotoLibraryWithImagePicker:imagePickerController]; } else { [self errorNoPhotoAccess:status]; } @@ -301,7 +304,7 @@ - (void)checkPhotoAuthorization { break; } case PHAuthorizationStatusAuthorized: - [self showPhotoLibrary:UIImagePickerClassType]; + [self showPhotoLibraryWithImagePicker:imagePickerController]; break; case PHAuthorizationStatusDenied: case PHAuthorizationStatusRestricted: @@ -320,9 +323,13 @@ - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { handler:^(PHAuthorizationStatus status) { dispatch_async(dispatch_get_main_queue(), ^{ if (status == PHAuthorizationStatusAuthorized) { - [self showPhotoLibrary:PHPickerClassType]; + [self + showPhotoLibraryWithPHPicker:self-> + _pickerViewController]; } else if (status == PHAuthorizationStatusLimited) { - [self showPhotoLibrary:PHPickerClassType]; + [self + showPhotoLibraryWithPHPicker:self-> + _pickerViewController]; } else { [self errorNoPhotoAccess:status]; } @@ -332,7 +339,7 @@ - (void)checkPhotoAuthorizationForAccessLevel API_AVAILABLE(ios(14)) { } case PHAuthorizationStatusAuthorized: case PHAuthorizationStatusLimited: - [self showPhotoLibrary:PHPickerClassType]; + [self showPhotoLibraryWithPHPicker:_pickerViewController]; break; case PHAuthorizationStatusDenied: case PHAuthorizationStatusRestricted: @@ -374,21 +381,18 @@ - (void)errorNoPhotoAccess:(PHAuthorizationStatus)status { } } -- (void)showPhotoLibrary:(ImagePickerClassType)imagePickerClassType { - // No need to check if SourceType is available. It always is. - switch (imagePickerClassType) { - case PHPickerClassType: - [[self viewControllerWithWindow:nil] presentViewController:_pickerViewController - animated:YES - completion:nil]; - break; - case UIImagePickerClassType: - _imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - [[self viewControllerWithWindow:nil] presentViewController:_imagePickerController - animated:YES - completion:nil]; - break; - } +- (void)showPhotoLibraryWithPHPicker:(PHPickerViewController *)pickerViewController + API_AVAILABLE(ios(14)) { + [[self viewControllerWithWindow:nil] presentViewController:pickerViewController + animated:YES + completion:nil]; +} + +- (void)showPhotoLibraryWithImagePicker:(UIImagePickerController *)imagePickerController { + imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + [[self viewControllerWithWindow:nil] presentViewController:imagePickerController + animated:YES + completion:nil]; } - (NSNumber *)getDesiredImageQuality:(NSNumber *)imageQuality { diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h index 8d847d980304..5442f7d089c6 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin_Test.h @@ -45,9 +45,10 @@ * controller would normally be created. Each call to * createImagePickerController will remove the current first element from * the array. - * + * * Should be used for testing purposes only. */ -- (void)setImagePickerControllerOverrides:(NSArray *)imagePickerControllers; +- (void)setImagePickerControllerOverrides: + (NSArray *)imagePickerControllers; @end From 6bbd53cf35852b57caa9b46b0c2aff060f357080 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 9 Feb 2022 22:07:15 +0100 Subject: [PATCH 17/18] Added declaration comments as required by style --- .../image_picker/image_picker/CHANGELOG.md | 2 -- .../ios/Classes/FLTImagePickerPlugin.m | 33 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 4fb5a31644ae..992c8b299345 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,7 +1,5 @@ ## 0.8.4+8 -* Ensures the `result` callback and method call `arguments` send to iOS are -taken into consideration when picking multiple images on pre-iOS 14 devices; * Configures the `UIImagePicker` to default to gallery instead of camera when picking multiple images on pre-iOS 14 devices. diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 246f482e9a85..503ba75b51f9 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -31,14 +31,28 @@ @interface FLTImagePickerPlugin () +/** + * Gets or sets the maximum amount of images that are allowed to be picked. + */ @property(assign, nonatomic) int maxImagesAllowed; +/** + * Gets or sets the arguments that are passed in from the Flutter method call. + */ @property(copy, nonatomic) NSDictionary *arguments; +/** + * Gets or sets the PHPickerViewController instance used to pick multiple + * images. + */ @property(strong, nonatomic) PHPickerViewController *pickerViewController API_AVAILABLE(ios(14)); -@property(strong, nonatomic) UIImagePickerController *imagePickerController; - +/** + * Gets or sets UIImagePickerController instances that will be used when a new + * controller would normally be created. Each call to + * createImagePickerController will remove the current first element from + * the array. + */ @property(strong, nonatomic) NSMutableArray *imagePickerControllerOverrides; @@ -59,17 +73,6 @@ + (void)registerWithRegistrar:(NSObject *)registrar { [registrar addMethodCallDelegate:instance channel:channel]; } -/** - * Creates a new UIImagePickerController. - * - * A new instance of the UIImagePickerController is created every time the - * createUIImagePickerController method is called. For testing purposes dummy - * instances can be injected using the setImagePickerControllerOverrides - * method. Each call to createImagePickerController will remove the current first - * element from the supplied array untill none are left. Calling - * createImagePickerController after that will return new instance of the - * UIImagePickerController - */ - (UIImagePickerController *)createImagePickerController { if ([self.imagePickerControllerOverrides count] > 0) { UIImagePickerController *controller = [self.imagePickerControllerOverrides firstObject]; @@ -473,7 +476,7 @@ - (NSMutableArray *)createNSMutableArrayWithSize:(NSUInteger)size { - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = info[UIImagePickerControllerMediaURL]; - [_imagePickerController dismissViewControllerAnimated:YES completion:nil]; + [picker dismissViewControllerAnimated:YES completion:nil]; // The method dismissViewControllerAnimated does not immediately prevent // further didFinishPickingMediaWithInfo invocations. A nil check is necessary // to prevent below code to be unwantly executed multiple times and cause a @@ -546,7 +549,7 @@ - (void)imagePickerController:(UIImagePickerController *)picker } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [_imagePickerController dismissViewControllerAnimated:YES completion:nil]; + [picker dismissViewControllerAnimated:YES completion:nil]; if (!self.result) { return; } From f9bfe66ff2e7a0f96e0a97362dcfcdb613335ac2 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Thu, 10 Feb 2022 08:32:50 +0100 Subject: [PATCH 18/18] Apply fixes for nits --- .../ios/Classes/FLTImagePickerPlugin.m | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 503ba75b51f9..cc841d6db447 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -32,23 +32,23 @@ @interface FLTImagePickerPlugin () /** - * Gets or sets the maximum amount of images that are allowed to be picked. + * The maximum amount of images that are allowed to be picked. */ @property(assign, nonatomic) int maxImagesAllowed; /** - * Gets or sets the arguments that are passed in from the Flutter method call. + * The arguments that are passed in from the Flutter method call. */ @property(copy, nonatomic) NSDictionary *arguments; /** - * Gets or sets the PHPickerViewController instance used to pick multiple + * The PHPickerViewController instance used to pick multiple * images. */ @property(strong, nonatomic) PHPickerViewController *pickerViewController API_AVAILABLE(ios(14)); /** - * Gets or sets UIImagePickerController instances that will be used when a new + * The UIImagePickerController instances that will be used when a new * controller would normally be created. Each call to * createImagePickerController will remove the current first element from * the array. @@ -146,10 +146,10 @@ - (void)launchUIImagePickerWithSource:(int)imageSource { switch (imageSource) { case SOURCE_CAMERA: - [self checkCameraAuthorizationWithImagePickerController:imagePickerController]; + [self checkCameraAuthorizationWithImagePicker:imagePickerController]; break; case SOURCE_GALLERY: - [self checkPhotoAuthorizationWithImagePickerController:imagePickerController]; + [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; break; default: self.result([FlutterError errorWithCode:@"invalid_source" @@ -208,10 +208,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result switch (imageSource) { case SOURCE_CAMERA: - [self checkCameraAuthorizationWithImagePickerController:imagePickerController]; + [self checkCameraAuthorizationWithImagePicker:imagePickerController]; break; case SOURCE_GALLERY: - [self checkPhotoAuthorizationWithImagePickerController:imagePickerController]; + [self checkPhotoAuthorizationWithImagePicker:imagePickerController]; break; default: result([FlutterError errorWithCode:@"invalid_source" @@ -224,7 +224,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } } -- (void)showCameraWithImagePickerController:(UIImagePickerController *)imagePickerController { +- (void)showCameraWithImagePicker:(UIImagePickerController *)imagePickerController { @synchronized(self) { if (imagePickerController.beingPresented) { return; @@ -260,26 +260,24 @@ - (void)showCameraWithImagePickerController:(UIImagePickerController *)imagePick } } -- (void)checkCameraAuthorizationWithImagePickerController: - (UIImagePickerController *)imagePickerController { +- (void)checkCameraAuthorizationWithImagePicker:(UIImagePickerController *)imagePickerController { AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; switch (status) { case AVAuthorizationStatusAuthorized: - [self showCameraWithImagePickerController:imagePickerController]; + [self showCameraWithImagePicker:imagePickerController]; break; case AVAuthorizationStatusNotDetermined: { - [AVCaptureDevice - requestAccessForMediaType:AVMediaTypeVideo - completionHandler:^(BOOL granted) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (granted) { - [self showCameraWithImagePickerController:imagePickerController]; - } else { - [self errorNoCameraAccess:AVAuthorizationStatusDenied]; - } - }); - }]; + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (granted) { + [self showCameraWithImagePicker:imagePickerController]; + } else { + [self errorNoCameraAccess:AVAuthorizationStatusDenied]; + } + }); + }]; break; } case AVAuthorizationStatusDenied: @@ -290,8 +288,7 @@ - (void)checkCameraAuthorizationWithImagePickerController: } } -- (void)checkPhotoAuthorizationWithImagePickerController: - (UIImagePickerController *)imagePickerController { +- (void)checkPhotoAuthorizationWithImagePicker:(UIImagePickerController *)imagePickerController { PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; switch (status) { case PHAuthorizationStatusNotDetermined: {