[image_picker] Migrate iOS to Pigeon and improve state handling (#5285)
diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md
index 3472ade..3380d14 100644
--- a/packages/image_picker/image_picker_ios/CHANGELOG.md
+++ b/packages/image_picker/image_picker_ios/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.8.5
+
+* Switches to an in-package method channel based on Pigeon.
+* Fixes invalid casts when selecting multiple images on versions of iOS before
+ 14.0.
+
## 0.8.4+11
* Splits from `image_picker` as a federated implementation.
diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
index 8df5299..04d4911 100644
--- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
+++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m
@@ -47,14 +47,15 @@
// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- 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){
- }];
+
+ [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
+ camera:FLTSourceCameraRear]
+ maxSize:[[FLTMaxSize alloc] init]
+ quality:nil
+ completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
+ }];
XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceRear);
}
@@ -78,14 +79,15 @@
// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- 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){
- }];
+
+ [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
+ camera:FLTSourceCameraFront]
+ maxSize:[[FLTMaxSize alloc] init]
+ quality:nil
+ completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
+ }];
XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceFront);
}
@@ -109,14 +111,14 @@
// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- 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){
- }];
+
+ [plugin pickVideoWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
+ camera:FLTSourceCameraRear]
+ maxDuration:nil
+ completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
+ }];
XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceRear);
}
@@ -141,14 +143,14 @@
// Run test
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- 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){
- }];
+
+ [plugin pickVideoWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
+ camera:FLTSourceCameraFront]
+ maxDuration:nil
+ completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
+ }];
XCTAssertEqual(controller.cameraDevice, UIImagePickerControllerCameraDeviceFront);
}
@@ -165,17 +167,12 @@
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
[plugin setImagePickerControllerOverrides:@[ mockUIImagePicker ]];
- FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickMultiImage"
- arguments:@{
- @"maxWidth" : @(100),
- @"maxHeight" : @(200),
- @"imageQuality" : @(50),
- }];
- [plugin handleMethodCall:call
- result:^(id _Nullable r){
- }];
-
+ [plugin pickMultiImageWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)]
+ quality:@(50)
+ completion:^(NSArray<NSString *> *_Nullable result,
+ FlutterError *_Nullable error){
+ }];
OCMVerify(times(1),
[mockUIImagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary]);
}
@@ -187,17 +184,15 @@
return;
}
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- 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) {
- };
+ [plugin pickImageWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
+ camera:FLTSourceCameraRear]
+ maxSize:[[FLTMaxSize alloc] init]
+ quality:nil
+ completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
+ }];
// To ensure the flow does not crash by multiple cancel call
[plugin imagePickerControllerDidCancel:controller];
@@ -208,14 +203,15 @@
- (void)testPickingVideoWithDuration {
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- 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){
- }];
+
+ [plugin pickVideoWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeCamera
+ camera:FLTSourceCameraRear]
+ maxDuration:@(95)
+ completion:^(NSString *_Nullable result, FlutterError *_Nullable error){
+ }];
+
XCTAssertEqual(controller.videoMaximumDuration, 95);
}
@@ -231,37 +227,17 @@
XCTAssertEqual([plugin viewControllerWithWindow:window], vc2);
}
-- (void)testPluginMultiImagePathIsNil {
- FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
-
- dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
- __block FlutterError *pickImageResult = nil;
-
- plugin.result = ^(id _Nullable r) {
- pickImageResult = r;
- dispatch_semaphore_signal(resultSemaphore);
- };
- [plugin handleSavedPathList:nil];
-
- dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);
-
- XCTAssertEqualObjects(pickImageResult.code, @"create_error");
-}
-
- (void)testPluginMultiImagePathHasNullItem {
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- NSMutableArray *pathList = [NSMutableArray new];
-
- [pathList addObject:[NSNull null]];
dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
__block FlutterError *pickImageResult = nil;
-
- plugin.result = ^(id _Nullable r) {
- pickImageResult = r;
- dispatch_semaphore_signal(resultSemaphore);
- };
- [plugin handleSavedPathList:pathList];
+ plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
+ initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
+ pickImageResult = error;
+ dispatch_semaphore_signal(resultSemaphore);
+ }];
+ [plugin sendCallResultWithSavedPathList:@[ [NSNull null] ]];
dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);
@@ -270,19 +246,17 @@
- (void)testPluginMultiImagePathHasItem {
FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new];
- NSString *savedPath = @"test";
- NSMutableArray *pathList = [NSMutableArray new];
-
- [pathList addObject:savedPath];
+ NSArray *pathList = @[ @"test" ];
dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0);
__block id pickImageResult = nil;
- plugin.result = ^(id _Nullable r) {
- pickImageResult = r;
- dispatch_semaphore_signal(resultSemaphore);
- };
- [plugin handleSavedPathList:pathList];
+ plugin.callContext = [[FLTImagePickerMethodCallContext alloc]
+ initWithResult:^(NSArray<NSString *> *_Nullable result, FlutterError *_Nullable error) {
+ pickImageResult = result;
+ dispatch_semaphore_signal(resultSemaphore);
+ }];
+ [plugin sendCallResultWithSavedPathList:pathList];
dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER);
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
index cc841d6..76ed962 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m
@@ -16,15 +16,18 @@
#import "FLTImagePickerMetaDataUtil.h"
#import "FLTImagePickerPhotoAssetUtil.h"
#import "FLTPHPickerSaveImageToPathOperation.h"
+#import "messages.g.h"
-/**
- * Returns the value for the given key in 'dict', or nil if the value is
- * NSNull.
- */
-id GetNullableValueForKey(NSDictionary *dict, NSString *key) {
- id value = dict[key];
- return value == [NSNull null] ? nil : value;
+@implementation FLTImagePickerMethodCallContext
+- (instancetype)initWithResult:(nonnull FlutterResultAdapter)result {
+ if (self = [super init]) {
+ _result = [result copy];
+ }
+ return self;
}
+@end
+
+#pragma mark -
@interface FLTImagePickerPlugin () <UINavigationControllerDelegate,
UIImagePickerControllerDelegate,
@@ -32,16 +35,6 @@
UIAdaptivePresentationControllerDelegate>
/**
- * The maximum amount of images that are allowed to be picked.
- */
-@property(assign, nonatomic) int maxImagesAllowed;
-
-/**
- * The arguments that are passed in from the Flutter method call.
- */
-@property(copy, nonatomic) NSDictionary *arguments;
-
-/**
* The PHPickerViewController instance used to pick multiple
* images.
*/
@@ -58,19 +51,13 @@
@end
-static const int SOURCE_CAMERA = 0;
-static const int SOURCE_GALLERY = 1;
-
typedef NS_ENUM(NSInteger, ImagePickerClassType) { UIImagePickerClassType, PHPickerClassType };
@implementation FLTImagePickerPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
- FlutterMethodChannel *channel =
- [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/image_picker"
- binaryMessenger:[registrar messenger]];
FLTImagePickerPlugin *instance = [FLTImagePickerPlugin new];
- [registrar addMethodCallDelegate:instance channel:channel];
+ FLTImagePickerApiSetup(registrar.messenger, instance);
}
- (UIImagePickerController *)createImagePickerController {
@@ -107,130 +94,180 @@
}
/**
- * Returns the UIImagePickerControllerCameraDevice to use given [arguments].
+ * Returns the UIImagePickerControllerCameraDevice to use given [source].
*
- * If the cameraDevice value that is fetched from arguments is 1 then returns
- * UIImagePickerControllerCameraDeviceFront. If the cameraDevice value that is fetched
- * from arguments is 0 then returns UIImagePickerControllerCameraDeviceRear.
- *
- * @param arguments that should be used to get cameraDevice value.
+ * @param source The source specification from Dart.
*/
-- (UIImagePickerControllerCameraDevice)getCameraDeviceFromArguments:(NSDictionary *)arguments {
- NSInteger cameraDevice = [arguments[@"cameraDevice"] intValue];
- return (cameraDevice == 1) ? UIImagePickerControllerCameraDeviceFront
- : UIImagePickerControllerCameraDeviceRear;
+- (UIImagePickerControllerCameraDevice)cameraDeviceForSource:(FLTSourceSpecification *)source {
+ switch (source.camera) {
+ case FLTSourceCameraFront:
+ return UIImagePickerControllerCameraDeviceFront;
+ case FLTSourceCameraRear:
+ return UIImagePickerControllerCameraDeviceRear;
+ }
}
-- (void)pickImageWithPHPicker:(int)maxImagesAllowed API_AVAILABLE(ios(14)) {
+- (void)launchPHPickerWithContext:(nonnull FLTImagePickerMethodCallContext *)context
+ API_AVAILABLE(ios(14)) {
PHPickerConfiguration *config =
[[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary];
- config.selectionLimit = maxImagesAllowed; // Setting to zero allow us to pick unlimited photos
+ config.selectionLimit = context.maxImageCount;
config.filter = [PHPickerFilter imagesFilter];
_pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config];
_pickerViewController.delegate = self;
_pickerViewController.presentationController.delegate = self;
-
- self.maxImagesAllowed = maxImagesAllowed;
+ self.callContext = context;
[self checkPhotoAuthorizationForAccessLevel];
}
-- (void)launchUIImagePickerWithSource:(int)imageSource {
+- (void)launchUIImagePickerWithSource:(nonnull FLTSourceSpecification *)source
+ context:(nonnull FLTImagePickerMethodCallContext *)context {
UIImagePickerController *imagePickerController = [self createImagePickerController];
imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
imagePickerController.delegate = self;
imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ];
+ self.callContext = context;
- self.maxImagesAllowed = 1;
-
- switch (imageSource) {
- case SOURCE_CAMERA:
- [self checkCameraAuthorizationWithImagePicker:imagePickerController];
+ switch (source.type) {
+ case FLTSourceTypeCamera:
+ [self checkCameraAuthorizationWithImagePicker:imagePickerController
+ camera:[self cameraDeviceForSource:source]];
break;
- case SOURCE_GALLERY:
+ case FLTSourceTypeGallery:
[self checkPhotoAuthorizationWithImagePicker:imagePickerController];
break;
default:
- self.result([FlutterError errorWithCode:@"invalid_source"
- message:@"Invalid image source."
- details:nil]);
+ [self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source"
+ message:@"Invalid image source."
+ details:nil]];
break;
}
}
-- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
- if (self.result) {
- self.result([FlutterError errorWithCode:@"multiple_request"
- message:@"Cancelled by a second request"
- details:nil]);
- self.result = nil;
- }
+#pragma mark - FLTImagePickerApi
- self.result = result;
- _arguments = call.arguments;
+- (void)pickImageWithSource:(nonnull FLTSourceSpecification *)source
+ maxSize:(nonnull FLTMaxSize *)maxSize
+ quality:(nullable NSNumber *)imageQuality
+ completion:
+ (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion {
+ [self cancelInProgressCall];
+ FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc]
+ initWithResult:^void(NSArray<NSString *> *paths, FlutterError *error) {
+ if (paths && paths.count != 1) {
+ completion(nil, [FlutterError errorWithCode:@"invalid_result"
+ message:@"Incorrect number of return paths provided"
+ details:nil]);
+ }
+ completion(paths.firstObject, error);
+ }];
+ context.maxSize = maxSize;
+ context.imageQuality = imageQuality;
+ context.maxImageCount = 1;
- if ([@"pickImage" isEqualToString:call.method]) {
- int imageSource = [call.arguments[@"source"] intValue];
-
- if (imageSource == SOURCE_GALLERY) { // Capture is not possible with PHPicker
- if (@available(iOS 14, *)) {
- // PHPicker is used
- [self pickImageWithPHPicker:1];
- } else {
- // UIImagePicker is used
- [self launchUIImagePickerWithSource:imageSource];
- }
- } else {
- [self launchUIImagePickerWithSource:imageSource];
- }
- } else if ([@"pickMultiImage" isEqualToString:call.method]) {
+ if (source.type == FLTSourceTypeGallery) { // Capture is not possible with PHPicker
if (@available(iOS 14, *)) {
- [self pickImageWithPHPicker:0];
+ [self launchPHPickerWithContext:context];
} else {
- [self launchUIImagePickerWithSource:SOURCE_GALLERY];
- }
- } else if ([@"pickVideo" isEqualToString:call.method]) {
- UIImagePickerController *imagePickerController = [self createImagePickerController];
- imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
- imagePickerController.delegate = self;
- imagePickerController.mediaTypes = @[
- (NSString *)kUTTypeMovie, (NSString *)kUTTypeAVIMovie, (NSString *)kUTTypeVideo,
- (NSString *)kUTTypeMPEG4
- ];
- imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
-
- int imageSource = [call.arguments[@"source"] intValue];
- if ([call.arguments[@"maxDuration"] isKindOfClass:[NSNumber class]]) {
- NSTimeInterval max = [call.arguments[@"maxDuration"] doubleValue];
- imagePickerController.videoMaximumDuration = max;
- }
-
- switch (imageSource) {
- case SOURCE_CAMERA:
- [self checkCameraAuthorizationWithImagePicker:imagePickerController];
- break;
- case SOURCE_GALLERY:
- [self checkPhotoAuthorizationWithImagePicker:imagePickerController];
- break;
- default:
- result([FlutterError errorWithCode:@"invalid_source"
- message:@"Invalid video source."
- details:nil]);
- break;
+ [self launchUIImagePickerWithSource:source context:context];
}
} else {
- result(FlutterMethodNotImplemented);
+ [self launchUIImagePickerWithSource:source context:context];
}
}
-- (void)showCameraWithImagePicker:(UIImagePickerController *)imagePickerController {
+- (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize
+ quality:(nullable NSNumber *)imageQuality
+ completion:(nonnull void (^)(NSArray<NSString *> *_Nullable,
+ FlutterError *_Nullable))completion {
+ FLTImagePickerMethodCallContext *context =
+ [[FLTImagePickerMethodCallContext alloc] initWithResult:completion];
+ context.maxSize = maxSize;
+ context.imageQuality = imageQuality;
+
+ if (@available(iOS 14, *)) {
+ [self launchPHPickerWithContext:context];
+ } else {
+ // Camera is ignored for gallery mode, so the value here is arbitrary.
+ [self launchUIImagePickerWithSource:[FLTSourceSpecification makeWithType:FLTSourceTypeGallery
+ camera:FLTSourceCameraRear]
+ context:context];
+ }
+}
+
+- (void)pickVideoWithSource:(nonnull FLTSourceSpecification *)source
+ maxDuration:(nullable NSNumber *)maxDurationSeconds
+ completion:
+ (nonnull void (^)(NSString *_Nullable, FlutterError *_Nullable))completion {
+ FLTImagePickerMethodCallContext *context = [[FLTImagePickerMethodCallContext alloc]
+ initWithResult:^void(NSArray<NSString *> *paths, FlutterError *error) {
+ if (paths && paths.count != 1) {
+ completion(nil, [FlutterError errorWithCode:@"invalid_result"
+ message:@"Incorrect number of return paths provided"
+ details:nil]);
+ }
+ completion(paths.firstObject, error);
+ }];
+ context.maxImageCount = 1;
+
+ UIImagePickerController *imagePickerController = [self createImagePickerController];
+ imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
+ imagePickerController.delegate = self;
+ imagePickerController.mediaTypes = @[
+ (NSString *)kUTTypeMovie, (NSString *)kUTTypeAVIMovie, (NSString *)kUTTypeVideo,
+ (NSString *)kUTTypeMPEG4
+ ];
+ imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
+
+ if (maxDurationSeconds) {
+ NSTimeInterval max = [maxDurationSeconds doubleValue];
+ imagePickerController.videoMaximumDuration = max;
+ }
+
+ self.callContext = context;
+
+ switch (source.type) {
+ case FLTSourceTypeCamera:
+ [self checkCameraAuthorizationWithImagePicker:imagePickerController
+ camera:[self cameraDeviceForSource:source]];
+ break;
+ case FLTSourceTypeGallery:
+ [self checkPhotoAuthorizationWithImagePicker:imagePickerController];
+ break;
+ default:
+ [self sendCallResultWithError:[FlutterError errorWithCode:@"invalid_source"
+ message:@"Invalid video source."
+ details:nil]];
+ break;
+ }
+}
+
+#pragma mark -
+
+/**
+ * If a call is still in progress, cancels it by returning an error and then clearing state.
+ *
+ * TODO(stuartmorgan): Eliminate this, and instead track context per image picker (e.g., using
+ * associated objects).
+ */
+- (void)cancelInProgressCall {
+ if (self.callContext) {
+ [self sendCallResultWithError:[FlutterError errorWithCode:@"multiple_request"
+ message:@"Cancelled by a second request"
+ details:nil]];
+ self.callContext = nil;
+ }
+}
+
+- (void)showCamera:(UIImagePickerControllerCameraDevice)device
+ withImagePicker:(UIImagePickerController *)imagePickerController {
@synchronized(self) {
if (imagePickerController.beingPresented) {
return;
}
}
- UIImagePickerControllerCameraDevice device = [self getCameraDeviceFromArguments:_arguments];
// Camera is not available on simulators
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera] &&
[UIImagePickerController isCameraDeviceAvailable:device]) {
@@ -254,25 +291,24 @@
[[self viewControllerWithWindow:nil] presentViewController:cameraErrorAlert
animated:YES
completion:nil];
- self.result(nil);
- self.result = nil;
- _arguments = nil;
+ [self sendCallResultWithSavedPathList:nil];
}
}
-- (void)checkCameraAuthorizationWithImagePicker:(UIImagePickerController *)imagePickerController {
+- (void)checkCameraAuthorizationWithImagePicker:(UIImagePickerController *)imagePickerController
+ camera:(UIImagePickerControllerCameraDevice)device {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (status) {
case AVAuthorizationStatusAuthorized:
- [self showCameraWithImagePicker:imagePickerController];
+ [self showCamera:device withImagePicker:imagePickerController];
break;
case AVAuthorizationStatusNotDetermined: {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
if (granted) {
- [self showCameraWithImagePicker:imagePickerController];
+ [self showCamera:device withImagePicker:imagePickerController];
} else {
[self errorNoCameraAccess:AVAuthorizationStatusDenied];
}
@@ -352,15 +388,17 @@
- (void)errorNoCameraAccess:(AVAuthorizationStatus)status {
switch (status) {
case AVAuthorizationStatusRestricted:
- self.result([FlutterError errorWithCode:@"camera_access_restricted"
- message:@"The user is not allowed to use the camera."
- details:nil]);
+ [self sendCallResultWithError:[FlutterError
+ errorWithCode:@"camera_access_restricted"
+ message:@"The user is not allowed to use the camera."
+ details:nil]];
break;
case AVAuthorizationStatusDenied:
default:
- self.result([FlutterError errorWithCode:@"camera_access_denied"
- message:@"The user did not allow camera access."
- details:nil]);
+ [self sendCallResultWithError:[FlutterError
+ errorWithCode:@"camera_access_denied"
+ message:@"The user did not allow camera access."
+ details:nil]];
break;
}
}
@@ -368,15 +406,17 @@
- (void)errorNoPhotoAccess:(PHAuthorizationStatus)status {
switch (status) {
case PHAuthorizationStatusRestricted:
- self.result([FlutterError errorWithCode:@"photo_access_restricted"
- message:@"The user is not allowed to use the photo."
- details:nil]);
+ [self sendCallResultWithError:[FlutterError
+ errorWithCode:@"photo_access_restricted"
+ message:@"The user is not allowed to use the photo."
+ details:nil]];
break;
case PHAuthorizationStatusDenied:
default:
- self.result([FlutterError errorWithCode:@"photo_access_denied"
- message:@"The user did not allow photo access."
- details:nil]);
+ [self sendCallResultWithError:[FlutterError
+ errorWithCode:@"photo_access_denied"
+ message:@"The user did not allow photo access."
+ details:nil]];
break;
}
}
@@ -406,31 +446,27 @@
return imageQuality;
}
+#pragma mark - UIAdaptivePresentationControllerDelegate
+
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController {
- if (self.result != nil) {
- self.result(nil);
- self.result = nil;
- self->_arguments = nil;
- }
+ [self sendCallResultWithSavedPathList:nil];
}
+#pragma mark - PHPickerViewControllerDelegate
+
- (void)picker:(PHPickerViewController *)picker
didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14)) {
[picker dismissViewControllerAnimated:YES completion:nil];
if (results.count == 0) {
- if (self.result != nil) {
- self.result(nil);
- self.result = nil;
- self->_arguments = nil;
- }
+ [self sendCallResultWithSavedPathList:nil];
return;
}
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(backgroundQueue, ^{
- NSNumber *maxWidth = GetNullableValueForKey(self->_arguments, @"maxWidth");
- NSNumber *maxHeight = GetNullableValueForKey(self->_arguments, @"maxHeight");
- NSNumber *imageQuality = GetNullableValueForKey(self->_arguments, @"imageQuality");
+ NSNumber *maxWidth = self.callContext.maxSize.width;
+ NSNumber *maxHeight = self.callContext.maxSize.height;
+ NSNumber *imageQuality = self.callContext.imageQuality;
NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality];
NSOperationQueue *operationQueue = [NSOperationQueue new];
NSMutableArray *pathList = [self createNSMutableArrayWithSize:results.count];
@@ -449,11 +485,13 @@
}
[operationQueue waitUntilAllOperationsAreFinished];
dispatch_async(dispatch_get_main_queue(), ^{
- [self handleSavedPathList:pathList];
+ [self sendCallResultWithSavedPathList:pathList];
});
});
}
+#pragma mark -
+
/**
* Creates an NSMutableArray of a certain size filled with NSNull objects.
*
@@ -470,6 +508,8 @@
return mutableArray;
}
+#pragma mark - UIImagePickerControllerDelegate
+
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info {
NSURL *videoURL = info[UIImagePickerControllerMediaURL];
@@ -478,7 +518,7 @@
// further didFinishPickingMediaWithInfo invocations. A nil check is necessary
// to prevent below code to be unwantly executed multiple times and cause a
// crash.
- if (!self.result) {
+ if (!self.callContext) {
return;
}
if (videoURL != nil) {
@@ -493,27 +533,25 @@
[[NSFileManager defaultManager] copyItemAtURL:videoURL toURL:destination error:&error];
if (error) {
- self.result([FlutterError errorWithCode:@"flutter_image_picker_copy_video_error"
- message:@"Could not cache the video file."
- details:nil]);
- self.result = nil;
+ [self sendCallResultWithError:[FlutterError
+ errorWithCode:@"flutter_image_picker_copy_video_error"
+ message:@"Could not cache the video file."
+ details:nil]];
return;
}
}
videoURL = destination;
}
}
- self.result(videoURL.path);
- self.result = nil;
- _arguments = nil;
+ [self sendCallResultWithSavedPathList:@[ videoURL.path ]];
} else {
UIImage *image = info[UIImagePickerControllerEditedImage];
if (image == nil) {
image = info[UIImagePickerControllerOriginalImage];
}
- NSNumber *maxWidth = GetNullableValueForKey(_arguments, @"maxWidth");
- NSNumber *maxHeight = GetNullableValueForKey(_arguments, @"maxHeight");
- NSNumber *imageQuality = GetNullableValueForKey(_arguments, @"imageQuality");
+ NSNumber *maxWidth = self.callContext.maxSize.width;
+ NSNumber *maxHeight = self.callContext.maxSize.height;
+ NSNumber *imageQuality = self.callContext.imageQuality;
NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality];
PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info];
@@ -547,14 +585,11 @@
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
- if (!self.result) {
- return;
- }
- self.result(nil);
- self.result = nil;
- _arguments = nil;
+ [self sendCallResultWithSavedPathList:nil];
}
+#pragma mark -
+
- (void)saveImageWithOriginalImageData:(NSData *)originalImageData
image:(UIImage *)image
maxWidth:(NSNumber *)maxWidth
@@ -566,7 +601,7 @@
maxWidth:maxWidth
maxHeight:maxHeight
imageQuality:imageQuality];
- [self handleSavedPathList:@[ savedPath ]];
+ [self sendCallResultWithSavedPathList:@[ savedPath ]];
}
- (void)saveImageWithPickerInfo:(NSDictionary *)info
@@ -575,47 +610,36 @@
NSString *savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:info
image:image
imageQuality:imageQuality];
- [self handleSavedPathList:@[ savedPath ]];
+ [self sendCallResultWithSavedPathList:@[ savedPath ]];
}
-/**
- * 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 {
- if (!self.result) {
+- (void)sendCallResultWithSavedPathList:(nullable NSArray *)pathList {
+ if (!self.callContext) {
return;
}
- if (pathList) {
- if (![pathList containsObject:[NSNull null]]) {
- if ((self.maxImagesAllowed == 1)) {
- self.result(pathList.firstObject);
- } else {
- self.result(pathList);
- }
- } else {
- self.result([FlutterError errorWithCode:@"create_error"
- message:@"pathList's items should not be null"
- details:nil]);
- }
+ if ([pathList containsObject:[NSNull null]]) {
+ self.callContext.result(nil, [FlutterError errorWithCode:@"create_error"
+ message:@"pathList's items should not be null"
+ details:nil]);
} else {
- // This should never happen.
- self.result([FlutterError errorWithCode:@"create_error"
- message:@"pathList should not be nil"
- details:nil]);
+ self.callContext.result(pathList, nil);
}
- self.result = nil;
- _arguments = nil;
+ self.callContext = nil;
+}
+
+/**
+ * Sends the given error via `callContext.result` as the result of the original platform channel
+ * method call, clearing the in-progress call state.
+ *
+ * @param error The error to return.
+ */
+- (void)sendCallResultWithError:(FlutterError *)error {
+ if (!self.callContext) {
+ return;
+ }
+ self.callContext.result(nil, error);
+ self.callContext = nil;
}
@end
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
index 039f76d..2c41677 100644
--- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
+++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin_Test.h
@@ -6,26 +6,65 @@
#import <image_picker_ios/FLTImagePickerPlugin.h>
-/** Methods exposed for unit testing. */
-@interface FLTImagePickerPlugin ()
+#import <messages.g.h>
-/** The Flutter result callback use to report results back to Flutter App. */
-@property(copy, nonatomic) FlutterResult result;
+NS_ASSUME_NONNULL_BEGIN
/**
- * 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.
+ * The return hander used for all method calls, which internally adapts the provided result list
+ * to return either a list or a single element depending on the original call.
*/
-- (void)handleSavedPathList:(NSArray *)pathList;
+typedef void (^FlutterResultAdapter)(NSArray<NSString *> *_Nullable, FlutterError *_Nullable);
+
+/**
+ * A container class for context to use when handling a method call from the Dart side.
+ */
+@interface FLTImagePickerMethodCallContext : NSObject
+
+/**
+ * Initializes a new context that calls |result| on completion of the operation.
+ */
+- (instancetype)initWithResult:(nonnull FlutterResultAdapter)result;
+
+/** The callback to provide results to the Dart caller. */
+@property(nonatomic, copy, nonnull) FlutterResultAdapter result;
+
+/**
+ * The maximum size to enforce on the results.
+ *
+ * If nil, no resizing is done.
+ */
+@property(nonatomic, strong, nullable) FLTMaxSize *maxSize;
+
+/**
+ * The image quality to resample the results to.
+ *
+ * If nil, no resampling is done.
+ */
+@property(nonatomic, strong, nullable) NSNumber *imageQuality;
+
+/** Maximum number of images to select. 0 indicates no maximum. */
+@property(nonatomic, assign) int maxImageCount;
+
+@end
+
+#pragma mark -
+
+/** Methods exposed for unit testing. */
+@interface FLTImagePickerPlugin () <FLTImagePickerApi>
+
+/**
+ * The context of the Flutter method call that is currently being handled, if any.
+ */
+@property(strong, nonatomic, nullable) FLTImagePickerMethodCallContext *callContext;
+
+/**
+ * Validates the provided paths list, then sends it via `callContext.result` as the result of the
+ * original platform channel method call, clearing the in-progress call state.
+ *
+ * @param pathList The paths to return. nil indicates a cancelled operation.
+ */
+- (void)sendCallResultWithSavedPathList:(nullable NSArray *)pathList;
/**
* Tells the delegate that the user cancelled the pick operation.
@@ -52,3 +91,5 @@
(NSArray<UIImagePickerController *> *)imagePickerControllers;
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
new file mode 100644
index 0000000..310165f
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h
@@ -0,0 +1,61 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+#import <Foundation/Foundation.h>
+@protocol FlutterBinaryMessenger;
+@protocol FlutterMessageCodec;
+@class FlutterError;
+@class FlutterStandardTypedData;
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NS_ENUM(NSUInteger, FLTSourceCamera) {
+ FLTSourceCameraRear = 0,
+ FLTSourceCameraFront = 1,
+};
+
+typedef NS_ENUM(NSUInteger, FLTSourceType) {
+ FLTSourceTypeCamera = 0,
+ FLTSourceTypeGallery = 1,
+};
+
+@class FLTMaxSize;
+@class FLTSourceSpecification;
+
+@interface FLTMaxSize : NSObject
++ (instancetype)makeWithWidth:(nullable NSNumber *)width height:(nullable NSNumber *)height;
+@property(nonatomic, strong, nullable) NSNumber *width;
+@property(nonatomic, strong, nullable) NSNumber *height;
+@end
+
+@interface FLTSourceSpecification : NSObject
+/// `init` unavailable to enforce nonnull fields, see the `make` class method.
+- (instancetype)init NS_UNAVAILABLE;
++ (instancetype)makeWithType:(FLTSourceType)type camera:(FLTSourceCamera)camera;
+@property(nonatomic, assign) FLTSourceType type;
+@property(nonatomic, assign) FLTSourceCamera camera;
+@end
+
+/// The codec used by FLTImagePickerApi.
+NSObject<FlutterMessageCodec> *FLTImagePickerApiGetCodec(void);
+
+@protocol FLTImagePickerApi
+- (void)pickImageWithSource:(FLTSourceSpecification *)source
+ maxSize:(FLTMaxSize *)maxSize
+ quality:(nullable NSNumber *)imageQuality
+ completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion;
+- (void)pickMultiImageWithMaxSize:(FLTMaxSize *)maxSize
+ quality:(nullable NSNumber *)imageQuality
+ completion:(void (^)(NSArray<NSString *> *_Nullable,
+ FlutterError *_Nullable))completion;
+- (void)pickVideoWithSource:(FLTSourceSpecification *)source
+ maxDuration:(nullable NSNumber *)maxDurationSeconds
+ completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion;
+@end
+
+extern void FLTImagePickerApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
+ NSObject<FLTImagePickerApi> *_Nullable api);
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
new file mode 100644
index 0000000..6c91c0a
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m
@@ -0,0 +1,216 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+#import "messages.g.h"
+#import <Flutter/Flutter.h>
+
+#if !__has_feature(objc_arc)
+#error File requires ARC to be enabled.
+#endif
+
+static NSDictionary<NSString *, id> *wrapResult(id result, FlutterError *error) {
+ NSDictionary *errorDict = (NSDictionary *)[NSNull null];
+ if (error) {
+ errorDict = @{
+ @"code" : (error.code ? error.code : [NSNull null]),
+ @"message" : (error.message ? error.message : [NSNull null]),
+ @"details" : (error.details ? error.details : [NSNull null]),
+ };
+ }
+ return @{
+ @"result" : (result ? result : [NSNull null]),
+ @"error" : errorDict,
+ };
+}
+static id GetNullableObject(NSDictionary *dict, id key) {
+ id result = dict[key];
+ return (result == [NSNull null]) ? nil : result;
+}
+static id GetNullableObjectAtIndex(NSArray *array, NSInteger key) {
+ id result = array[key];
+ return (result == [NSNull null]) ? nil : result;
+}
+
+@interface FLTMaxSize ()
++ (FLTMaxSize *)fromMap:(NSDictionary *)dict;
+- (NSDictionary *)toMap;
+@end
+@interface FLTSourceSpecification ()
++ (FLTSourceSpecification *)fromMap:(NSDictionary *)dict;
+- (NSDictionary *)toMap;
+@end
+
+@implementation FLTMaxSize
++ (instancetype)makeWithWidth:(nullable NSNumber *)width height:(nullable NSNumber *)height {
+ FLTMaxSize *pigeonResult = [[FLTMaxSize alloc] init];
+ pigeonResult.width = width;
+ pigeonResult.height = height;
+ return pigeonResult;
+}
++ (FLTMaxSize *)fromMap:(NSDictionary *)dict {
+ FLTMaxSize *pigeonResult = [[FLTMaxSize alloc] init];
+ pigeonResult.width = GetNullableObject(dict, @"width");
+ pigeonResult.height = GetNullableObject(dict, @"height");
+ return pigeonResult;
+}
+- (NSDictionary *)toMap {
+ return [NSDictionary
+ dictionaryWithObjectsAndKeys:(self.width ? self.width : [NSNull null]), @"width",
+ (self.height ? self.height : [NSNull null]), @"height", nil];
+}
+@end
+
+@implementation FLTSourceSpecification
++ (instancetype)makeWithType:(FLTSourceType)type camera:(FLTSourceCamera)camera {
+ FLTSourceSpecification *pigeonResult = [[FLTSourceSpecification alloc] init];
+ pigeonResult.type = type;
+ pigeonResult.camera = camera;
+ return pigeonResult;
+}
++ (FLTSourceSpecification *)fromMap:(NSDictionary *)dict {
+ FLTSourceSpecification *pigeonResult = [[FLTSourceSpecification alloc] init];
+ pigeonResult.type = [GetNullableObject(dict, @"type") integerValue];
+ pigeonResult.camera = [GetNullableObject(dict, @"camera") integerValue];
+ return pigeonResult;
+}
+- (NSDictionary *)toMap {
+ return [NSDictionary
+ dictionaryWithObjectsAndKeys:@(self.type), @"type", @(self.camera), @"camera", nil];
+}
+@end
+
+@interface FLTImagePickerApiCodecReader : FlutterStandardReader
+@end
+@implementation FLTImagePickerApiCodecReader
+- (nullable id)readValueOfType:(UInt8)type {
+ switch (type) {
+ case 128:
+ return [FLTMaxSize fromMap:[self readValue]];
+
+ case 129:
+ return [FLTSourceSpecification fromMap:[self readValue]];
+
+ default:
+ return [super readValueOfType:type];
+ }
+}
+@end
+
+@interface FLTImagePickerApiCodecWriter : FlutterStandardWriter
+@end
+@implementation FLTImagePickerApiCodecWriter
+- (void)writeValue:(id)value {
+ if ([value isKindOfClass:[FLTMaxSize class]]) {
+ [self writeByte:128];
+ [self writeValue:[value toMap]];
+ } else if ([value isKindOfClass:[FLTSourceSpecification class]]) {
+ [self writeByte:129];
+ [self writeValue:[value toMap]];
+ } else {
+ [super writeValue:value];
+ }
+}
+@end
+
+@interface FLTImagePickerApiCodecReaderWriter : FlutterStandardReaderWriter
+@end
+@implementation FLTImagePickerApiCodecReaderWriter
+- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data {
+ return [[FLTImagePickerApiCodecWriter alloc] initWithData:data];
+}
+- (FlutterStandardReader *)readerWithData:(NSData *)data {
+ return [[FLTImagePickerApiCodecReader alloc] initWithData:data];
+}
+@end
+
+NSObject<FlutterMessageCodec> *FLTImagePickerApiGetCodec() {
+ static dispatch_once_t sPred = 0;
+ static FlutterStandardMessageCodec *sSharedObject = nil;
+ dispatch_once(&sPred, ^{
+ FLTImagePickerApiCodecReaderWriter *readerWriter =
+ [[FLTImagePickerApiCodecReaderWriter alloc] init];
+ sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter];
+ });
+ return sSharedObject;
+}
+
+void FLTImagePickerApiSetup(id<FlutterBinaryMessenger> binaryMessenger,
+ NSObject<FLTImagePickerApi> *api) {
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickImage"
+ binaryMessenger:binaryMessenger
+ codec:FLTImagePickerApiGetCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(pickImageWithSource:maxSize:quality:completion:)],
+ @"FLTImagePickerApi api (%@) doesn't respond to "
+ @"@selector(pickImageWithSource:maxSize:quality:completion:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ NSArray *args = message;
+ FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0);
+ FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 1);
+ NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 2);
+ [api pickImageWithSource:arg_source
+ maxSize:arg_maxSize
+ quality:arg_imageQuality
+ completion:^(NSString *_Nullable output, FlutterError *_Nullable error) {
+ callback(wrapResult(output, error));
+ }];
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickMultiImage"
+ binaryMessenger:binaryMessenger
+ codec:FLTImagePickerApiGetCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(pickMultiImageWithMaxSize:quality:completion:)],
+ @"FLTImagePickerApi api (%@) doesn't respond to "
+ @"@selector(pickMultiImageWithMaxSize:quality:completion:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ NSArray *args = message;
+ FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0);
+ NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1);
+ [api pickMultiImageWithMaxSize:arg_maxSize
+ quality:arg_imageQuality
+ completion:^(NSArray<NSString *> *_Nullable output,
+ FlutterError *_Nullable error) {
+ callback(wrapResult(output, error));
+ }];
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+ {
+ FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
+ initWithName:@"dev.flutter.pigeon.ImagePickerApi.pickVideo"
+ binaryMessenger:binaryMessenger
+ codec:FLTImagePickerApiGetCodec()];
+ if (api) {
+ NSCAssert([api respondsToSelector:@selector(pickVideoWithSource:maxDuration:completion:)],
+ @"FLTImagePickerApi api (%@) doesn't respond to "
+ @"@selector(pickVideoWithSource:maxDuration:completion:)",
+ api);
+ [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
+ NSArray *args = message;
+ FLTSourceSpecification *arg_source = GetNullableObjectAtIndex(args, 0);
+ NSNumber *arg_maxDurationSeconds = GetNullableObjectAtIndex(args, 1);
+ [api pickVideoWithSource:arg_source
+ maxDuration:arg_maxDurationSeconds
+ completion:^(NSString *_Nullable output, FlutterError *_Nullable error) {
+ callback(wrapResult(output, error));
+ }];
+ }];
+ } else {
+ [channel setMessageHandler:nil];
+ }
+ }
+}
diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
new file mode 100644
index 0000000..3d1413c
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart
@@ -0,0 +1,209 @@
+// 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 'dart:async';
+
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+
+import 'src/messages.g.dart';
+
+// Converts an [ImageSource] to the corresponding Pigeon API enum value.
+SourceType _convertSource(ImageSource source) {
+ switch (source) {
+ case ImageSource.camera:
+ return SourceType.camera;
+ case ImageSource.gallery:
+ return SourceType.gallery;
+ default:
+ throw UnimplementedError('Unknown source: $source');
+ }
+}
+
+// Converts a [CameraDevice] to the corresponding Pigeon API enum value.
+SourceCamera _convertCamera(CameraDevice camera) {
+ switch (camera) {
+ case CameraDevice.front:
+ return SourceCamera.front;
+ case CameraDevice.rear:
+ return SourceCamera.rear;
+ default:
+ throw UnimplementedError('Unknown camera: $camera');
+ }
+}
+
+/// An implementation of [ImagePickerPlatform] for iOS.
+class ImagePickerIOS extends ImagePickerPlatform {
+ final ImagePickerApi _hostApi = ImagePickerApi();
+
+ /// Registers this class as the default platform implementation.
+ static void registerWith() {
+ ImagePickerPlatform.instance = ImagePickerIOS();
+ }
+
+ @override
+ Future<PickedFile?> pickImage({
+ required ImageSource source,
+ double? maxWidth,
+ double? maxHeight,
+ int? imageQuality,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ }) async {
+ final String? path = await _pickImageAsPath(
+ source: source,
+ maxWidth: maxWidth,
+ maxHeight: maxHeight,
+ imageQuality: imageQuality,
+ preferredCameraDevice: preferredCameraDevice,
+ );
+ return path != null ? PickedFile(path) : null;
+ }
+
+ @override
+ Future<List<PickedFile>?> pickMultiImage({
+ double? maxWidth,
+ double? maxHeight,
+ int? imageQuality,
+ }) async {
+ final List<dynamic>? paths = await _pickMultiImageAsPath(
+ maxWidth: maxWidth,
+ maxHeight: maxHeight,
+ imageQuality: imageQuality,
+ );
+ if (paths == null) {
+ return null;
+ }
+
+ return paths.map((dynamic path) => PickedFile(path as String)).toList();
+ }
+
+ Future<List<String>?> _pickMultiImageAsPath({
+ double? maxWidth,
+ double? maxHeight,
+ int? imageQuality,
+ }) async {
+ if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
+ throw ArgumentError.value(
+ imageQuality, 'imageQuality', 'must be between 0 and 100');
+ }
+
+ if (maxWidth != null && maxWidth < 0) {
+ throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
+ }
+
+ if (maxHeight != null && maxHeight < 0) {
+ throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
+ }
+
+ // TODO(stuartmorgan): Remove the cast once Pigeon supports non-nullable
+ // generics, https://github.com/flutter/flutter/issues/97848
+ return (await _hostApi.pickMultiImage(
+ MaxSize(width: maxWidth, height: maxHeight), imageQuality))
+ ?.cast<String>();
+ }
+
+ Future<String?> _pickImageAsPath({
+ required ImageSource source,
+ double? maxWidth,
+ double? maxHeight,
+ int? imageQuality,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ }) {
+ if (imageQuality != null && (imageQuality < 0 || imageQuality > 100)) {
+ throw ArgumentError.value(
+ imageQuality, 'imageQuality', 'must be between 0 and 100');
+ }
+
+ if (maxWidth != null && maxWidth < 0) {
+ throw ArgumentError.value(maxWidth, 'maxWidth', 'cannot be negative');
+ }
+
+ if (maxHeight != null && maxHeight < 0) {
+ throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative');
+ }
+
+ return _hostApi.pickImage(
+ SourceSpecification(
+ type: _convertSource(source),
+ camera: _convertCamera(preferredCameraDevice)),
+ MaxSize(width: maxWidth, height: maxHeight),
+ imageQuality,
+ );
+ }
+
+ @override
+ Future<PickedFile?> pickVideo({
+ required ImageSource source,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ Duration? maxDuration,
+ }) async {
+ final String? path = await _pickVideoAsPath(
+ source: source,
+ maxDuration: maxDuration,
+ preferredCameraDevice: preferredCameraDevice,
+ );
+ return path != null ? PickedFile(path) : null;
+ }
+
+ Future<String?> _pickVideoAsPath({
+ required ImageSource source,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ Duration? maxDuration,
+ }) {
+ return _hostApi.pickVideo(
+ SourceSpecification(
+ type: _convertSource(source),
+ camera: _convertCamera(preferredCameraDevice)),
+ maxDuration?.inSeconds);
+ }
+
+ @override
+ Future<XFile?> getImage({
+ required ImageSource source,
+ double? maxWidth,
+ double? maxHeight,
+ int? imageQuality,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ }) async {
+ final String? path = await _pickImageAsPath(
+ source: source,
+ maxWidth: maxWidth,
+ maxHeight: maxHeight,
+ imageQuality: imageQuality,
+ preferredCameraDevice: preferredCameraDevice,
+ );
+ return path != null ? XFile(path) : null;
+ }
+
+ @override
+ Future<List<XFile>?> getMultiImage({
+ double? maxWidth,
+ double? maxHeight,
+ int? imageQuality,
+ }) async {
+ final List<String>? paths = await _pickMultiImageAsPath(
+ maxWidth: maxWidth,
+ maxHeight: maxHeight,
+ imageQuality: imageQuality,
+ );
+ if (paths == null) {
+ return null;
+ }
+
+ return paths.map((String path) => XFile(path)).toList();
+ }
+
+ @override
+ Future<XFile?> getVideo({
+ required ImageSource source,
+ CameraDevice preferredCameraDevice = CameraDevice.rear,
+ Duration? maxDuration,
+ }) async {
+ final String? path = await _pickVideoAsPath(
+ source: source,
+ maxDuration: maxDuration,
+ preferredCameraDevice: preferredCameraDevice,
+ );
+ return path != null ? XFile(path) : null;
+ }
+}
diff --git a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
new file mode 100644
index 0000000..0c5859e
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart
@@ -0,0 +1,194 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
+// @dart = 2.12
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+
+import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
+import 'package:flutter/services.dart';
+
+enum SourceCamera {
+ rear,
+ front,
+}
+
+enum SourceType {
+ camera,
+ gallery,
+}
+
+class MaxSize {
+ MaxSize({
+ this.width,
+ this.height,
+ });
+
+ double? width;
+ double? height;
+
+ Object encode() {
+ final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+ pigeonMap['width'] = width;
+ pigeonMap['height'] = height;
+ return pigeonMap;
+ }
+
+ static MaxSize decode(Object message) {
+ final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+ return MaxSize(
+ width: pigeonMap['width'] as double?,
+ height: pigeonMap['height'] as double?,
+ );
+ }
+}
+
+class SourceSpecification {
+ SourceSpecification({
+ required this.type,
+ this.camera,
+ });
+
+ SourceType type;
+ SourceCamera? camera;
+
+ Object encode() {
+ final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
+ pigeonMap['type'] = type.index;
+ pigeonMap['camera'] = camera?.index;
+ return pigeonMap;
+ }
+
+ static SourceSpecification decode(Object message) {
+ final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
+ return SourceSpecification(
+ type: SourceType.values[pigeonMap['type']! as int],
+ camera: pigeonMap['camera'] != null
+ ? SourceCamera.values[pigeonMap['camera']! as int]
+ : null,
+ );
+ }
+}
+
+class _ImagePickerApiCodec extends StandardMessageCodec {
+ const _ImagePickerApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is MaxSize) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else if (value is SourceSpecification) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return MaxSize.decode(readValue(buffer)!);
+
+ case 129:
+ return SourceSpecification.decode(readValue(buffer)!);
+
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
+class ImagePickerApi {
+ /// Constructor for [ImagePickerApi]. The [binaryMessenger] named argument is
+ /// available for dependency injection. If it is left null, the default
+ /// BinaryMessenger will be used which routes to the host platform.
+ ImagePickerApi({BinaryMessenger? binaryMessenger})
+ : _binaryMessenger = binaryMessenger;
+
+ final BinaryMessenger? _binaryMessenger;
+
+ static const MessageCodec<Object?> codec = _ImagePickerApiCodec();
+
+ Future<String?> pickImage(SourceSpecification arg_source, MaxSize arg_maxSize,
+ int? arg_imageQuality) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ImagePickerApi.pickImage', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object?>[arg_source, arg_maxSize, arg_imageQuality])
+ as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return (replyMap['result'] as String?);
+ }
+ }
+
+ Future<List<String?>?> pickMultiImage(
+ MaxSize arg_maxSize, int? arg_imageQuality) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ImagePickerApi.pickMultiImage', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object?>[arg_maxSize, arg_imageQuality])
+ as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return (replyMap['result'] as List<Object?>?)?.cast<String?>();
+ }
+ }
+
+ Future<String?> pickVideo(
+ SourceSpecification arg_source, int? arg_maxDurationSeconds) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ImagePickerApi.pickVideo', codec,
+ binaryMessenger: _binaryMessenger);
+ final Map<Object?, Object?>? replyMap =
+ await channel.send(<Object?>[arg_source, arg_maxDurationSeconds])
+ as Map<Object?, Object?>?;
+ if (replyMap == null) {
+ throw PlatformException(
+ code: 'channel-error',
+ message: 'Unable to establish connection on channel.',
+ );
+ } else if (replyMap['error'] != null) {
+ final Map<Object?, Object?> error =
+ (replyMap['error'] as Map<Object?, Object?>?)!;
+ throw PlatformException(
+ code: (error['code'] as String?)!,
+ message: error['message'] as String?,
+ details: error['details'],
+ );
+ } else {
+ return (replyMap['result'] as String?);
+ }
+ }
+}
diff --git a/packages/image_picker/image_picker_ios/pigeons/copyright.txt b/packages/image_picker/image_picker_ios/pigeons/copyright.txt
new file mode 100644
index 0000000..1236b63
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/pigeons/copyright.txt
@@ -0,0 +1,3 @@
+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.
diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart
new file mode 100644
index 0000000..94ac034
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart
@@ -0,0 +1,47 @@
+// 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 'package:pigeon/pigeon.dart';
+
+@ConfigurePigeon(PigeonOptions(
+ dartOut: 'lib/src/messages.g.dart',
+ dartTestOut: 'test/test_api.dart',
+ objcHeaderOut: 'ios/Classes/messages.g.h',
+ objcSourceOut: 'ios/Classes/messages.g.m',
+ objcOptions: ObjcOptions(
+ prefix: 'FLT',
+ ),
+ copyrightHeader: 'pigeons/copyright.txt',
+))
+class MaxSize {
+ MaxSize(this.width, this.height);
+ double? width;
+ double? height;
+}
+
+// Corresponds to `CameraDevice` from the platform interface package.
+enum SourceCamera { rear, front }
+
+// Corresponds to `ImageSource` from the platform interface package.
+enum SourceType { camera, gallery }
+
+class SourceSpecification {
+ SourceSpecification(this.type, this.camera);
+ SourceType type;
+ SourceCamera? camera;
+}
+
+@HostApi(dartHostTestHandler: 'TestHostImagePickerApi')
+abstract class ImagePickerApi {
+ @async
+ @ObjCSelector('pickImageWithSource:maxSize:quality:')
+ String? pickImage(
+ SourceSpecification source, MaxSize maxSize, int? imageQuality);
+ @async
+ @ObjCSelector('pickMultiImageWithMaxSize:quality:')
+ List<String>? pickMultiImage(MaxSize maxSize, int? imageQuality);
+ @async
+ @ObjCSelector('pickVideoWithSource:maxDuration:')
+ String? pickVideo(SourceSpecification source, int? maxDurationSeconds);
+}
diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml
index 2587c9a..a9cd052 100755
--- a/packages/image_picker/image_picker_ios/pubspec.yaml
+++ b/packages/image_picker/image_picker_ios/pubspec.yaml
@@ -2,17 +2,18 @@
description: iOS implementation of the video_picker plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/image_picker/image_picker_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22
-version: 0.8.4+11
+version: 0.8.5
environment:
sdk: ">=2.14.0 <3.0.0"
- flutter: ">=2.5.0"
+ flutter: ">=2.8.0"
flutter:
plugin:
implements: image_picker
platforms:
ios:
+ dartPluginClass: ImagePickerIOS
pluginClass: FLTImagePickerPlugin
dependencies:
@@ -24,3 +25,4 @@
flutter_test:
sdk: flutter
mockito: ^5.0.0
+ pigeon: ^3.0.2
diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
new file mode 100644
index 0000000..09517f1
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart
@@ -0,0 +1,937 @@
+// 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 'package:flutter/foundation.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:image_picker_ios/image_picker_ios.dart';
+import 'package:image_picker_ios/src/messages.g.dart';
+import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
+
+import 'test_api.dart';
+
+@immutable
+class _LoggedMethodCall {
+ const _LoggedMethodCall(this.name, {required this.arguments});
+ final String name;
+ final Map<String, Object?> arguments;
+
+ @override
+ bool operator ==(Object other) {
+ return other is _LoggedMethodCall &&
+ name == other.name &&
+ mapEquals(arguments, other.arguments);
+ }
+
+ @override
+ int get hashCode => Object.hash(name, arguments);
+
+ @override
+ String toString() {
+ return 'MethodCall: $name $arguments';
+ }
+}
+
+class _ApiLogger implements TestHostImagePickerApi {
+ // The value to return from future calls.
+ dynamic returnValue = '';
+ final List<_LoggedMethodCall> calls = <_LoggedMethodCall>[];
+
+ @override
+ Future<String?> pickImage(
+ SourceSpecification source, MaxSize maxSize, int? imageQuality) async {
+ // Flatten arguments for easy comparison.
+ calls.add(_LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': source.type,
+ 'cameraDevice': source.camera,
+ 'maxWidth': maxSize.width,
+ 'maxHeight': maxSize.height,
+ 'imageQuality': imageQuality,
+ }));
+ return returnValue as String?;
+ }
+
+ @override
+ Future<List<String?>?> pickMultiImage(
+ MaxSize maxSize, int? imageQuality) async {
+ calls.add(_LoggedMethodCall('pickMultiImage', arguments: <String, dynamic>{
+ 'maxWidth': maxSize.width,
+ 'maxHeight': maxSize.height,
+ 'imageQuality': imageQuality,
+ }));
+ return returnValue as List<String?>?;
+ }
+
+ @override
+ Future<String?> pickVideo(
+ SourceSpecification source, int? maxDurationSeconds) async {
+ calls.add(_LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': source.type,
+ 'cameraDevice': source.camera,
+ 'maxDuration': maxDurationSeconds,
+ }));
+ return returnValue as String?;
+ }
+}
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ final ImagePickerIOS picker = ImagePickerIOS();
+ late _ApiLogger log;
+
+ setUp(() {
+ log = _ApiLogger();
+ TestHostImagePickerApi.setup(log);
+ });
+
+ test('registration', () async {
+ ImagePickerIOS.registerWith();
+ expect(ImagePickerPlatform.instance, isA<ImagePickerIOS>());
+ });
+
+ group('#pickImage', () {
+ test('passes the image source argument correctly', () async {
+ await picker.pickImage(source: ImageSource.camera);
+ await picker.pickImage(source: ImageSource.gallery);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.gallery,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ ],
+ );
+ });
+
+ test('passes the width and height arguments correctly', () async {
+ await picker.pickImage(source: ImageSource.camera);
+ await picker.pickImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ );
+ await picker.pickImage(
+ source: ImageSource.camera,
+ maxHeight: 10.0,
+ );
+ await picker.pickImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ );
+ await picker.pickImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ imageQuality: 70,
+ );
+ await picker.pickImage(
+ source: ImageSource.camera,
+ maxHeight: 10.0,
+ imageQuality: 70,
+ );
+ await picker.pickImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ imageQuality: 70,
+ );
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': 70,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': 70,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': 70,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ ],
+ );
+ });
+
+ test('does not accept a invalid imageQuality argument', () {
+ expect(
+ () => picker.pickImage(imageQuality: -1, source: ImageSource.gallery),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.pickImage(imageQuality: 101, source: ImageSource.gallery),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.pickImage(imageQuality: -1, source: ImageSource.camera),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.pickImage(imageQuality: 101, source: ImageSource.camera),
+ throwsArgumentError,
+ );
+ });
+
+ test('does not accept a negative width or height argument', () {
+ expect(
+ () => picker.pickImage(source: ImageSource.camera, maxWidth: -1.0),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.pickImage(source: ImageSource.camera, maxHeight: -1.0),
+ throwsArgumentError,
+ );
+ });
+
+ test('handles a null image path response gracefully', () async {
+ log.returnValue = null;
+
+ expect(await picker.pickImage(source: ImageSource.gallery), isNull);
+ expect(await picker.pickImage(source: ImageSource.camera), isNull);
+ });
+
+ test('camera position defaults to back', () async {
+ await picker.pickImage(source: ImageSource.camera);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ ],
+ );
+ });
+
+ test('camera position can set to front', () async {
+ await picker.pickImage(
+ source: ImageSource.camera,
+ preferredCameraDevice: CameraDevice.front);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.front,
+ }),
+ ],
+ );
+ });
+ });
+
+ group('#pickMultiImage', () {
+ test('calls the method correctly', () async {
+ log.returnValue = <String>['0', '1'];
+ await picker.pickMultiImage();
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ }),
+ ],
+ );
+ });
+
+ test('passes the width and height arguments correctly', () async {
+ log.returnValue = <String>['0', '1'];
+ await picker.pickMultiImage();
+ await picker.pickMultiImage(
+ maxWidth: 10.0,
+ );
+ await picker.pickMultiImage(
+ maxHeight: 10.0,
+ );
+ await picker.pickMultiImage(
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ );
+ await picker.pickMultiImage(
+ maxWidth: 10.0,
+ imageQuality: 70,
+ );
+ await picker.pickMultiImage(
+ maxHeight: 10.0,
+ imageQuality: 70,
+ );
+ await picker.pickMultiImage(
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ imageQuality: 70,
+ );
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': 70,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': 70,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': 70,
+ }),
+ ],
+ );
+ });
+
+ test('does not accept a negative width or height argument', () {
+ expect(
+ () => picker.pickMultiImage(maxWidth: -1.0),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.pickMultiImage(maxHeight: -1.0),
+ throwsArgumentError,
+ );
+ });
+
+ test('does not accept a invalid imageQuality argument', () {
+ expect(
+ () => picker.pickMultiImage(imageQuality: -1),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.pickMultiImage(imageQuality: 101),
+ throwsArgumentError,
+ );
+ });
+
+ test('handles a null image path response gracefully', () async {
+ log.returnValue = null;
+
+ expect(await picker.pickMultiImage(), isNull);
+ });
+ });
+
+ group('#pickVideo', () {
+ test('passes the image source argument correctly', () async {
+ await picker.pickVideo(source: ImageSource.camera);
+ await picker.pickVideo(source: ImageSource.gallery);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'cameraDevice': SourceCamera.rear,
+ 'maxDuration': null,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.gallery,
+ 'cameraDevice': SourceCamera.rear,
+ 'maxDuration': null,
+ }),
+ ],
+ );
+ });
+
+ test('passes the duration argument correctly', () async {
+ await picker.pickVideo(source: ImageSource.camera);
+ await picker.pickVideo(
+ source: ImageSource.camera,
+ maxDuration: const Duration(seconds: 10),
+ );
+ await picker.pickVideo(
+ source: ImageSource.camera,
+ maxDuration: const Duration(minutes: 1),
+ );
+ await picker.pickVideo(
+ source: ImageSource.camera,
+ maxDuration: const Duration(hours: 1),
+ );
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': null,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': 10,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': 60,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': 3600,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ ],
+ );
+ });
+
+ test('handles a null video path response gracefully', () async {
+ log.returnValue = null;
+
+ expect(await picker.pickVideo(source: ImageSource.gallery), isNull);
+ expect(await picker.pickVideo(source: ImageSource.camera), isNull);
+ });
+
+ test('camera position defaults to back', () async {
+ await picker.pickVideo(source: ImageSource.camera);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'cameraDevice': SourceCamera.rear,
+ 'maxDuration': null,
+ }),
+ ],
+ );
+ });
+
+ test('camera position can set to front', () async {
+ await picker.pickVideo(
+ source: ImageSource.camera,
+ preferredCameraDevice: CameraDevice.front,
+ );
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': null,
+ 'cameraDevice': SourceCamera.front,
+ }),
+ ],
+ );
+ });
+ });
+
+ group('#getImage', () {
+ test('passes the image source argument correctly', () async {
+ await picker.getImage(source: ImageSource.camera);
+ await picker.getImage(source: ImageSource.gallery);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.gallery,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ ],
+ );
+ });
+
+ test('passes the width and height arguments correctly', () async {
+ await picker.getImage(source: ImageSource.camera);
+ await picker.getImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ );
+ await picker.getImage(
+ source: ImageSource.camera,
+ maxHeight: 10.0,
+ );
+ await picker.getImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ );
+ await picker.getImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ imageQuality: 70,
+ );
+ await picker.getImage(
+ source: ImageSource.camera,
+ maxHeight: 10.0,
+ imageQuality: 70,
+ );
+ await picker.getImage(
+ source: ImageSource.camera,
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ imageQuality: 70,
+ );
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': 70,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': 70,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': 70,
+ 'cameraDevice': SourceCamera.rear
+ }),
+ ],
+ );
+ });
+
+ test('does not accept a invalid imageQuality argument', () {
+ expect(
+ () => picker.getImage(imageQuality: -1, source: ImageSource.gallery),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getImage(imageQuality: 101, source: ImageSource.gallery),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getImage(imageQuality: -1, source: ImageSource.camera),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getImage(imageQuality: 101, source: ImageSource.camera),
+ throwsArgumentError,
+ );
+ });
+
+ test('does not accept a negative width or height argument', () {
+ expect(
+ () => picker.getImage(source: ImageSource.camera, maxWidth: -1.0),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getImage(source: ImageSource.camera, maxHeight: -1.0),
+ throwsArgumentError,
+ );
+ });
+
+ test('handles a null image path response gracefully', () async {
+ log.returnValue = null;
+
+ expect(await picker.getImage(source: ImageSource.gallery), isNull);
+ expect(await picker.getImage(source: ImageSource.camera), isNull);
+ });
+
+ test('camera position defaults to back', () async {
+ await picker.getImage(source: ImageSource.camera);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ ],
+ );
+ });
+
+ test('camera position can set to front', () async {
+ await picker.getImage(
+ source: ImageSource.camera,
+ preferredCameraDevice: CameraDevice.front);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickImage', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ 'cameraDevice': SourceCamera.front,
+ }),
+ ],
+ );
+ });
+ });
+
+ group('#getMultiImage', () {
+ test('calls the method correctly', () async {
+ log.returnValue = <String>['0', '1'];
+ await picker.getMultiImage();
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ }),
+ ],
+ );
+ });
+
+ test('passes the width and height arguments correctly', () async {
+ log.returnValue = <String>['0', '1'];
+ await picker.getMultiImage();
+ await picker.getMultiImage(
+ maxWidth: 10.0,
+ );
+ await picker.getMultiImage(
+ maxHeight: 10.0,
+ );
+ await picker.getMultiImage(
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ );
+ await picker.getMultiImage(
+ maxWidth: 10.0,
+ imageQuality: 70,
+ );
+ await picker.getMultiImage(
+ maxHeight: 10.0,
+ imageQuality: 70,
+ );
+ await picker.getMultiImage(
+ maxWidth: 10.0,
+ maxHeight: 20.0,
+ imageQuality: 70,
+ );
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': null,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': null,
+ 'imageQuality': 70,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': null,
+ 'maxHeight': 10.0,
+ 'imageQuality': 70,
+ }),
+ const _LoggedMethodCall('pickMultiImage',
+ arguments: <String, dynamic>{
+ 'maxWidth': 10.0,
+ 'maxHeight': 20.0,
+ 'imageQuality': 70,
+ }),
+ ],
+ );
+ });
+
+ test('does not accept a negative width or height argument', () {
+ log.returnValue = <String>['0', '1'];
+ expect(
+ () => picker.getMultiImage(maxWidth: -1.0),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getMultiImage(maxHeight: -1.0),
+ throwsArgumentError,
+ );
+ });
+
+ test('does not accept a invalid imageQuality argument', () {
+ log.returnValue = <String>['0', '1'];
+ expect(
+ () => picker.getMultiImage(imageQuality: -1),
+ throwsArgumentError,
+ );
+
+ expect(
+ () => picker.getMultiImage(imageQuality: 101),
+ throwsArgumentError,
+ );
+ });
+
+ test('handles a null image path response gracefully', () async {
+ log.returnValue = null;
+
+ expect(await picker.getMultiImage(), isNull);
+ expect(await picker.getMultiImage(), isNull);
+ });
+ });
+
+ group('#getVideo', () {
+ test('passes the image source argument correctly', () async {
+ await picker.getVideo(source: ImageSource.camera);
+ await picker.getVideo(source: ImageSource.gallery);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'cameraDevice': SourceCamera.rear,
+ 'maxDuration': null,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.gallery,
+ 'cameraDevice': SourceCamera.rear,
+ 'maxDuration': null,
+ }),
+ ],
+ );
+ });
+
+ test('passes the duration argument correctly', () async {
+ await picker.getVideo(source: ImageSource.camera);
+ await picker.getVideo(
+ source: ImageSource.camera,
+ maxDuration: const Duration(seconds: 10),
+ );
+ await picker.getVideo(
+ source: ImageSource.camera,
+ maxDuration: const Duration(minutes: 1),
+ );
+ await picker.getVideo(
+ source: ImageSource.camera,
+ maxDuration: const Duration(hours: 1),
+ );
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': null,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': 10,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': 60,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': 3600,
+ 'cameraDevice': SourceCamera.rear,
+ }),
+ ],
+ );
+ });
+
+ test('handles a null video path response gracefully', () async {
+ log.returnValue = null;
+
+ expect(await picker.getVideo(source: ImageSource.gallery), isNull);
+ expect(await picker.getVideo(source: ImageSource.camera), isNull);
+ });
+
+ test('camera position defaults to back', () async {
+ await picker.getVideo(source: ImageSource.camera);
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'cameraDevice': SourceCamera.rear,
+ 'maxDuration': null,
+ }),
+ ],
+ );
+ });
+
+ test('camera position can set to front', () async {
+ await picker.getVideo(
+ source: ImageSource.camera,
+ preferredCameraDevice: CameraDevice.front,
+ );
+
+ expect(
+ log.calls,
+ <_LoggedMethodCall>[
+ const _LoggedMethodCall('pickVideo', arguments: <String, dynamic>{
+ 'source': SourceType.camera,
+ 'maxDuration': null,
+ 'cameraDevice': SourceCamera.front,
+ }),
+ ],
+ );
+ });
+ });
+}
diff --git a/packages/image_picker/image_picker_ios/test/test_api.dart b/packages/image_picker/image_picker_ios/test/test_api.dart
new file mode 100644
index 0000000..1f76e87
--- /dev/null
+++ b/packages/image_picker/image_picker_ios/test/test_api.dart
@@ -0,0 +1,127 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Autogenerated from Pigeon (v3.0.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis
+// ignore_for_file: avoid_relative_lib_imports
+// @dart = 2.12
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+// Manually changed due to https://github.com/flutter/flutter/issues/97744
+import 'package:image_picker_ios/src/messages.g.dart';
+
+class _TestHostImagePickerApiCodec extends StandardMessageCodec {
+ const _TestHostImagePickerApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is MaxSize) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else if (value is SourceSpecification) {
+ buffer.putUint8(129);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return MaxSize.decode(readValue(buffer)!);
+
+ case 129:
+ return SourceSpecification.decode(readValue(buffer)!);
+
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
+abstract class TestHostImagePickerApi {
+ static const MessageCodec<Object?> codec = _TestHostImagePickerApiCodec();
+
+ Future<String?> pickImage(
+ SourceSpecification source, MaxSize maxSize, int? imageQuality);
+ Future<List<String?>?> pickMultiImage(MaxSize maxSize, int? imageQuality);
+ Future<String?> pickVideo(
+ SourceSpecification source, int? maxDurationSeconds);
+ static void setup(TestHostImagePickerApi? api,
+ {BinaryMessenger? binaryMessenger}) {
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ImagePickerApi.pickImage', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final SourceSpecification? arg_source =
+ (args[0] as SourceSpecification?);
+ assert(arg_source != null,
+ 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null SourceSpecification.');
+ final MaxSize? arg_maxSize = (args[1] as MaxSize?);
+ assert(arg_maxSize != null,
+ 'Argument for dev.flutter.pigeon.ImagePickerApi.pickImage was null, expected non-null MaxSize.');
+ final int? arg_imageQuality = (args[2] as int?);
+ final String? output =
+ await api.pickImage(arg_source!, arg_maxSize!, arg_imageQuality);
+ return <Object?, Object?>{'result': output};
+ });
+ }
+ }
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ImagePickerApi.pickMultiImage', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final MaxSize? arg_maxSize = (args[0] as MaxSize?);
+ assert(arg_maxSize != null,
+ 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMultiImage was null, expected non-null MaxSize.');
+ final int? arg_imageQuality = (args[1] as int?);
+ final List<String?>? output =
+ await api.pickMultiImage(arg_maxSize!, arg_imageQuality);
+ return <Object?, Object?>{'result': output};
+ });
+ }
+ }
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.ImagePickerApi.pickVideo', codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ channel.setMockMessageHandler(null);
+ } else {
+ channel.setMockMessageHandler((Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideo was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final SourceSpecification? arg_source =
+ (args[0] as SourceSpecification?);
+ assert(arg_source != null,
+ 'Argument for dev.flutter.pigeon.ImagePickerApi.pickVideo was null, expected non-null SourceSpecification.');
+ final int? arg_maxDurationSeconds = (args[1] as int?);
+ final String? output =
+ await api.pickVideo(arg_source!, arg_maxDurationSeconds);
+ return <Object?, Object?>{'result': output};
+ });
+ }
+ }
+ }
+}