From 6b54992dd775fe6023879375271852217c8b4b22 Mon Sep 17 00:00:00 2001 From: Christopher Boyd Date: Fri, 27 Aug 2021 15:51:54 -0400 Subject: [PATCH] [image_picker] add forceFullMetadata for iOS --- .../image_picker/image_picker/CHANGELOG.md | 8 ++ .../image_picker/example/lib/main.dart | 115 ++++++++++-------- .../ios/Classes/FLTImagePickerPlugin.m | 30 ++++- .../image_picker/lib/image_picker.dart | 14 +++ .../image_picker/image_picker/pubspec.yaml | 2 +- .../test/image_picker_deprecated_test.dart | 29 +++-- .../image_picker/test/image_picker_test.dart | 29 +++-- 7 files changed, 153 insertions(+), 74 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index a9255976c526..380e222d05cb 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.8.4 + +* Add `forceFullMetadata` option to `pickImage`. + * To keep this non-breaking `forceFullMetadata` defaults to `true`, so the plugin tries + to get the full image metadata which may require extra permission requests on certain platforms. + * If `forceFullMetadata` is set to `false`, the plugin fetches the image in a way that reduces + permission requests from the platform (e.g. on iOS the plugin won’t ask for the `NSPhotoLibraryUsageDescription` permission). + ## 0.8.3+3 * Fix pickImage not returning a value on iOS when dismissing PHPicker sheet by swiping. diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index 2d5fd9aee4a7..0d00c20fba71 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -88,8 +88,8 @@ class _MyHomePageState extends State { source: source, maxDuration: const Duration(seconds: 10)); await _playVideo(file); } else if (isMultiImage) { - await _displayPickImageDialog(context!, - (double? maxWidth, double? maxHeight, int? quality) async { + await _displayPickImageDialog(context!, (double? maxWidth, + double? maxHeight, int? quality, bool forceFullMetadata) async { try { final pickedFileList = await _picker.pickMultiImage( maxWidth: maxWidth, @@ -106,14 +106,15 @@ class _MyHomePageState extends State { } }); } else { - await _displayPickImageDialog(context!, - (double? maxWidth, double? maxHeight, int? quality) async { + await _displayPickImageDialog(context!, (double? maxWidth, + double? maxHeight, int? quality, bool forceFullMetadata) async { try { final pickedFile = await _picker.pickImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, + forceFullMetadata: forceFullMetadata, ); setState(() { _imageFile = pickedFile; @@ -358,60 +359,74 @@ class _MyHomePageState extends State { return showDialog( context: context, builder: (context) { - return AlertDialog( - title: Text('Add optional parameters'), - content: Column( - children: [ - TextField( - controller: maxWidthController, - keyboardType: TextInputType.numberWithOptions(decimal: true), - decoration: - InputDecoration(hintText: "Enter maxWidth if desired"), - ), - TextField( - controller: maxHeightController, - keyboardType: TextInputType.numberWithOptions(decimal: true), - decoration: - InputDecoration(hintText: "Enter maxHeight if desired"), - ), - TextField( - controller: qualityController, - keyboardType: TextInputType.number, - decoration: - InputDecoration(hintText: "Enter quality if desired"), - ), - ], - ), - actions: [ - TextButton( - child: const Text('CANCEL'), - onPressed: () { - Navigator.of(context).pop(); - }, + bool forceFullMetadata = true; + return StatefulBuilder(builder: (context, setState) { + return AlertDialog( + title: Text('Add optional parameters'), + content: Column( + children: [ + TextField( + controller: maxWidthController, + keyboardType: + TextInputType.numberWithOptions(decimal: true), + decoration: + InputDecoration(hintText: "Enter maxWidth if desired"), + ), + TextField( + controller: maxHeightController, + keyboardType: + TextInputType.numberWithOptions(decimal: true), + decoration: + InputDecoration(hintText: "Enter maxHeight if desired"), + ), + TextField( + controller: qualityController, + keyboardType: TextInputType.number, + decoration: + InputDecoration(hintText: "Enter quality if desired"), + ), + CheckboxListTile( + value: forceFullMetadata, + onChanged: (bool? value) { + setState(() { + forceFullMetadata = value ?? false; + }); + }, + title: Text("Force full metadata"), + ) + ], ), - TextButton( - child: const Text('PICK'), + actions: [ + TextButton( + child: const Text('CANCEL'), onPressed: () { - double? width = maxWidthController.text.isNotEmpty - ? double.parse(maxWidthController.text) - : null; - double? height = maxHeightController.text.isNotEmpty - ? double.parse(maxHeightController.text) - : null; - int? quality = qualityController.text.isNotEmpty - ? int.parse(qualityController.text) - : null; - onPick(width, height, quality); Navigator.of(context).pop(); - }), - ], - ); + }, + ), + TextButton( + child: const Text('PICK'), + onPressed: () { + double? width = maxWidthController.text.isNotEmpty + ? double.parse(maxWidthController.text) + : null; + double? height = maxHeightController.text.isNotEmpty + ? double.parse(maxHeightController.text) + : null; + int? quality = qualityController.text.isNotEmpty + ? int.parse(qualityController.text) + : null; + onPick(width, height, quality, forceFullMetadata); + Navigator.of(context).pop(); + }), + ], + ); + }); }); } } typedef void OnPickImageCallback( - double? maxWidth, double? maxHeight, int? quality); + double? maxWidth, double? maxHeight, int? quality, bool forceFullMetadata); class AspectRatioVideo extends StatefulWidget { AspectRatioVideo(this.controller); diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index cf3103195482..207178d01bf7 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -97,7 +97,12 @@ - (void)pickImageWithPHPicker:(int)maxImagesAllowed API_AVAILABLE(ios(14)) { self.maxImagesAllowed = maxImagesAllowed; - [self checkPhotoAuthorizationForAccessLevel]; + BOOL usePhaAsset = [[_arguments objectForKey:@"forceFullMetadata"] boolValue]; + if (usePhaAsset) { + [self checkPhotoAuthorizationForAccessLevel]; + return; + } + [self showPhotoLibrary:PHPickerClassType]; } - (void)pickImageWithUIImagePicker { @@ -107,6 +112,7 @@ - (void)pickImageWithUIImagePicker { _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; int imageSource = [[_arguments objectForKey:@"source"] intValue]; + BOOL usePhaAsset = [[_arguments objectForKey:@"forceFullMetadata"] boolValue]; self.maxImagesAllowed = 1; @@ -115,7 +121,11 @@ - (void)pickImageWithUIImagePicker { [self checkCameraAuthorization]; break; case SOURCE_GALLERY: - [self checkPhotoAuthorization]; + if (usePhaAsset) { + [self checkPhotoAuthorization]; + break; + } + [self showPhotoLibrary:UIImagePickerClassType]; break; default: self.result([FlutterError errorWithCode:@"invalid_source" @@ -132,13 +142,14 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result details:nil]); self.result = nil; } + BOOL usePhaAsset = [[_arguments objectForKey:@"forceFullMetadata"] boolValue]; 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 + if (usePhaAsset && imageSource == SOURCE_GALLERY) { // Capture is not possible with PHPicker if (@available(iOS 14, *)) { // PHPicker is used [self pickImageWithPHPicker:1]; @@ -171,6 +182,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result _arguments = call.arguments; int imageSource = [[_arguments objectForKey:@"source"] intValue]; + BOOL usePhaAsset = [[_arguments objectForKey:@"forceFullMetadata"] boolValue]; if ([[_arguments objectForKey:@"maxDuration"] isKindOfClass:[NSNumber class]]) { NSTimeInterval max = [[_arguments objectForKey:@"maxDuration"] doubleValue]; _imagePickerController.videoMaximumDuration = max; @@ -181,7 +193,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self checkCameraAuthorization]; break; case SOURCE_GALLERY: - [self checkPhotoAuthorization]; + if (usePhaAsset) { + [self checkPhotoAuthorization]; + break; + } + [self showPhotoLibrary:UIImagePickerClassType]; break; default: result([FlutterError errorWithCode:@"invalid_source" @@ -484,8 +500,12 @@ - (void)imagePickerController:(UIImagePickerController *)picker NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; NSNumber *imageQuality = [_arguments objectForKey:@"imageQuality"]; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; + BOOL usePhaAsset = [[_arguments objectForKey:@"forceFullMetadata"] boolValue]; - PHAsset *originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info]; + PHAsset *originalAsset; + if (usePhaAsset) { + originalAsset = [FLTImagePickerPhotoAssetUtil getAssetFromImagePickerInfo:info]; + } if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { image = [FLTImagePickerImageUtil scaledImage:image diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index 5bc99d7f0bb2..c9aa38432c41 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -44,6 +44,11 @@ class ImagePicker { /// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked, /// a warning message will be logged. /// + /// `forceFullMetadata` defaults to `true`, so the plugin tries to get the full image metadata which may require + /// extra permission requests on certain platforms. + /// If `forceFullMetadata` is set to `false`, the plugin fetches the image in a way that reduces permission requests + /// from the platform (e.g. on iOS the plugin won’t ask for the `NSPhotoLibraryUsageDescription` permission). + /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if @@ -65,6 +70,7 @@ class ImagePicker { double? maxWidth, double? maxHeight, int? imageQuality, + bool forceFullMetadata = true, CameraDevice preferredCameraDevice = CameraDevice.rear, }) { return platform.pickImage( @@ -72,6 +78,7 @@ class ImagePicker { maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, + forceFullMetadata: forceFullMetadata, preferredCameraDevice: preferredCameraDevice, ); } @@ -185,6 +192,11 @@ class ImagePicker { /// image types such as JPEG and on Android PNG and WebP, too. If compression is not supported for the image that is picked, /// a warning message will be logged. /// + /// `forceFullMetadata` defaults to `true`, so the plugin tries to get the full image metadata which may require + /// extra permission requests on certain platforms. + /// If `forceFullMetadata` is set to `false`, the plugin fetches the image in a way that reduces permission requests + /// from the platform (e.g. on iOS the plugin won’t ask for the `NSPhotoLibraryUsageDescription` permission). + /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. Note that Android has no documented parameter for an intent to specify if @@ -205,6 +217,7 @@ class ImagePicker { double? maxWidth, double? maxHeight, int? imageQuality, + bool forceFullMetadata = true, CameraDevice preferredCameraDevice = CameraDevice.rear, }) { return platform.getImage( @@ -212,6 +225,7 @@ class ImagePicker { maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, + forceFullMetadata: forceFullMetadata, preferredCameraDevice: preferredCameraDevice, ); } diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 4becca930261..f43c4f6b2abc 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/master/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.3+3 +version: 0.8.4 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/packages/image_picker/image_picker/test/image_picker_deprecated_test.dart b/packages/image_picker/image_picker/test/image_picker_deprecated_test.dart index f295e3d02f66..21294f99526c 100644 --- a/packages/image_picker/image_picker/test/image_picker_deprecated_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_deprecated_test.dart @@ -57,14 +57,16 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), ], ); @@ -103,49 +105,56 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), ], ); @@ -182,6 +191,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, + 'forceFullMetadata': true, }), ], ); @@ -201,6 +211,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, + 'forceFullMetadata': true, }), ], ); diff --git a/packages/image_picker/image_picker/test/image_picker_test.dart b/packages/image_picker/image_picker/test/image_picker_test.dart index 960dfe6917ea..7e960c5b4b69 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -52,14 +52,16 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 1, 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), ], ); @@ -98,49 +100,56 @@ void main() { 'maxWidth': null, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), isMethodCall('pickImage', arguments: { 'source': 0, 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, - 'cameraDevice': 0 + 'cameraDevice': 0, + 'forceFullMetadata': true, }), ], ); @@ -177,6 +186,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 0, + 'forceFullMetadata': true, }), ], ); @@ -196,6 +206,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'cameraDevice': 1, + 'forceFullMetadata': true, }), ], );