From cf182094b44af6d9444bf17474a490469e57af8f Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 10 Mar 2021 12:23:31 +0100 Subject: [PATCH 01/79] Added pickMultiImage to image_picker_platform_interface --- .../CHANGELOG.md | 4 ++ .../method_channel_image_picker.dart | 46 +++++++++++++++++++ .../image_picker_platform.dart | 30 +++++++++++- .../pubspec.yaml | 2 +- 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md index 598f83b4b09b..266654eff6fc 100644 --- a/packages/image_picker/image_picker_platform_interface/CHANGELOG.md +++ b/packages/image_picker/image_picker_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.0 + +* Added pickMultiImage call + ## 2.0.1 * Update platform_plugin_interface version requirement. diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart index 8535f9dfb20e..31847a188d84 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart @@ -36,6 +36,52 @@ class MethodChannelImagePicker extends ImagePickerPlatform { return path != null ? PickedFile(path) : null; } + @override + Future?> pickMultiImage({ + double? maxWidth, + double? maxHeight, + int? imageQuality, + }) async { + List? paths = await _pickMultiImagePath( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + ); + List files = []; + for (final path in paths!) { + files.add(PickedFile(path)); + } + return files; + } + + Future _pickMultiImagePath({ + double? maxWidth, + double? maxHeight, + int? imageQuality, + }) { + 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 _channel.invokeMethod>( + 'pickMultiImage', + { + 'maxWidth': maxWidth, + 'maxHeight': maxHeight, + 'imageQuality': imageQuality, + }, + ); + } + Future _pickImagePath({ required ImageSource source, double? maxWidth, diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart index 44b85f17f3db..3fabb870affa 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/platform_interface/image_picker_platform.dart @@ -54,9 +54,9 @@ abstract class ImagePickerPlatform extends PlatformInterface { /// /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 /// where 100 is the original/max quality. If `imageQuality` is null, the image with - /// the original quality will be returned. Compression is only supportted for certain + /// the original quality will be returned. Compression is only supported for certain /// image types such as JPEG. If compression is not supported for the image that is picked, - /// an warning message will be logged. + /// a warning message will be logged. /// /// 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. @@ -78,6 +78,32 @@ abstract class ImagePickerPlatform extends PlatformInterface { throw UnimplementedError('pickImage() has not been implemented.'); } + /// Returns a [List] with the images that were picked. + /// + /// The images come from the [ImageSource.gallery]. + /// + /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used + /// in addition to a size modification, of which the usage is explained below. + /// + /// If specified, the image will be at most `maxWidth` wide and + /// `maxHeight` tall. Otherwise the image will be returned at it's + /// original width and height. + /// + /// The `imageQuality` argument modifies the quality of the images, ranging from 0-100 + /// where 100 is the original/max quality. If `imageQuality` is null, the images with + /// the original quality will be returned. Compression is only supported for certain + /// image types such as JPEG. If compression is not supported for the image that is picked, + /// a warning message will be logged. + /// + /// If no images were picked, the return value is null. + Future?> pickMultiImage({ + double? maxWidth, + double? maxHeight, + int? imageQuality, + }) { + throw UnimplementedError('pickMultiImage() has not been implemented.'); + } + /// Returns a [PickedFile] containing the video that was picked. /// /// The [source] argument controls where the video comes from. This can diff --git a/packages/image_picker/image_picker_platform_interface/pubspec.yaml b/packages/image_picker/image_picker_platform_interface/pubspec.yaml index 3443f158dc56..446cf632f9b4 100644 --- a/packages/image_picker/image_picker_platform_interface/pubspec.yaml +++ b/packages/image_picker/image_picker_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the image_picker plugin. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.0.1 +version: 2.1.1 dependencies: flutter: From 41af3db6c503cb7dca019d6f35c5c5c5ee18cd07 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Fri, 12 Mar 2021 13:45:38 +0100 Subject: [PATCH 02/79] initial implementation Android --- .../imagepicker/ImagePickerDelegate.java | 97 +++++++++++++++++-- .../imagepicker/ImagePickerPlugin.java | 4 + .../image_picker/example/lib/main.dart | 8 +- .../image_picker/lib/image_picker.dart | 12 +++ .../image_picker/image_picker/pubspec.yaml | 3 +- .../method_channel_image_picker.dart | 6 +- 6 files changed, 115 insertions(+), 15 deletions(-) diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index ff7f1534a586..ee01b5e46ae5 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -14,6 +14,8 @@ import android.net.Uri; import android.os.Build; import android.provider.MediaStore; + +import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import androidx.core.app.ActivityCompat; import androidx.core.content.FileProvider; @@ -22,6 +24,8 @@ import io.flutter.plugin.common.PluginRegistry; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; @@ -78,6 +82,8 @@ public class ImagePickerDelegate @VisibleForTesting static final int REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA = 2343; @VisibleForTesting static final int REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION = 2344; @VisibleForTesting static final int REQUEST_CAMERA_IMAGE_PERMISSION = 2345; + @VisibleForTesting static final int REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY = 2346; + @VisibleForTesting static final int REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION = 2347; @VisibleForTesting static final int REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY = 2352; @VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353; @VisibleForTesting static final int REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION = 2354; @@ -331,6 +337,21 @@ public void chooseImageFromGallery(MethodCall methodCall, MethodChannel.Result r launchPickImageFromGalleryIntent(); } + public void chooseMultiImageFromGallery(MethodCall methodCall, MethodChannel.Result result) { + if (!setPendingMethodCallAndResult(methodCall, result)) { + finishWithAlreadyActiveError(result); + return; + } + + if (!permissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) { + permissionManager.askForPermission( + Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION); + return; + } + + launchMultiPickImageFromGalleryIntent(); + } + private void launchPickImageFromGalleryIntent() { Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); pickImageIntent.setType("image/*"); @@ -338,6 +359,17 @@ private void launchPickImageFromGalleryIntent() { activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY); } + + private void launchMultiPickImageFromGalleryIntent() { + Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + pickImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + pickImageIntent.setType("image/*"); + + activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY); + } + public void takeImageWithCamera(MethodCall methodCall, MethodChannel.Result result) { if (!setPendingMethodCallAndResult(methodCall, result)) { finishWithAlreadyActiveError(result); @@ -429,6 +461,11 @@ public boolean onRequestPermissionsResult( launchPickImageFromGalleryIntent(); } break; + case REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION: + if (permissionGranted) { + launchMultiPickImageFromGalleryIntent(); + } + break; case REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION: if (permissionGranted) { launchPickVideoFromGalleryIntent(); @@ -470,6 +507,9 @@ public boolean onActivityResult(int requestCode, int resultCode, Intent data) { case REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY: handleChooseImageResult(resultCode, data); break; + case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY: + handleChooseMultiImageResult(resultCode, data); + break; case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA: handleCaptureImageResult(resultCode); break; @@ -497,6 +537,21 @@ private void handleChooseImageResult(int resultCode, Intent data) { finishWithSuccess(null); } + private void handleChooseMultiImageResult(int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK && data != null) { + ArrayList paths = new ArrayList<>(); + for (int i = 0; i < data.getClipData().getItemCount(); i++) { + paths.add(fileUtils.getPathFromUri(activity, data.getClipData().getItemAt(i).getUri())); + + } + handleMultiImageResult(paths, false); + return; + } + + // User cancelled choosing a picture. + finishWithSuccess(null); + } + private void handleChooseVideoResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { String path = fileUtils.getPathFromUri(activity, data.getData()); @@ -546,17 +601,24 @@ public void onPathReady(String path) { finishWithSuccess(null); } - private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) { + private void handleMultiImageResult(ArrayList paths, boolean shouldDeleteOriginalIfScaled) { if (methodCall != null) { - Double maxWidth = methodCall.argument("maxWidth"); - Double maxHeight = methodCall.argument("maxHeight"); - Integer imageQuality = methodCall.argument("imageQuality"); - - String finalImagePath = - imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality); + for(int i =0; i < paths.size(); i++) { + String finalImagePath = resizeImage(paths.get(i)); - finishWithSuccess(finalImagePath); + //delete original file if scaled + if (finalImagePath != null && !finalImagePath.equals(paths.get(i)) && shouldDeleteOriginalIfScaled) { + new File(paths.get(i)).delete(); + } + paths.set(i, finalImagePath); + } + finishWithListSuccess(paths); + } + } + private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) { + if (methodCall != null) { + String finalImagePath = resizeImage(path); //delete original file if scaled if (finalImagePath != null && !finalImagePath.equals(path) && shouldDeleteOriginalIfScaled) { new File(path).delete(); @@ -566,6 +628,14 @@ private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled } } + private String resizeImage(String path) { + Double maxWidth = methodCall.argument("maxWidth"); + Double maxHeight = methodCall.argument("maxHeight"); + Integer imageQuality = methodCall.argument("imageQuality"); + + return imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality); + } + private void handleVideoResult(String path) { finishWithSuccess(path); } @@ -594,6 +664,17 @@ private void finishWithSuccess(String imagePath) { clearMethodCallAndResult(); } + private void finishWithListSuccess(ArrayList imagePaths) { + if (pendingResult == null) { + for (String imagePath : imagePaths){ + cache.saveResult(imagePath, null, null); + } + return; + } + pendingResult.success(imagePaths); + clearMethodCallAndResult(); + } + private void finishWithAlreadyActiveError(MethodChannel.Result result) { result.error("already_active", "Image picker is already active", null); } diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index 74556f9cd6cc..0127a9024de3 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -92,6 +92,7 @@ public void onActivityStopped(Activity activity) { } static final String METHOD_CALL_IMAGE = "pickImage"; + static final String METHOD_CALL_MULTI_IMAGE = "pickMultiImage"; static final String METHOD_CALL_VIDEO = "pickVideo"; private static final String METHOD_CALL_RETRIEVE = "retrieve"; private static final int CAMERA_DEVICE_FRONT = 1; @@ -303,6 +304,9 @@ public void onMethodCall(MethodCall call, MethodChannel.Result rawResult) { throw new IllegalArgumentException("Invalid image source: " + imageSource); } break; + case METHOD_CALL_MULTI_IMAGE: + delegate.chooseMultiImageFromGallery(call, result); + break; case METHOD_CALL_VIDEO: imageSource = call.argument("source"); switch (imageSource) { diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index 54e3a1ae4cd2..a9095b199113 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -87,17 +87,19 @@ class _MyHomePageState extends State { await _displayPickImageDialog(context!, (double? maxWidth, double? maxHeight, int? quality) async { try { - final pickedFile = await _picker.getImage( - source: source, + final pickedFile = await _picker.getMultiImage( + // source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { - _imageFile = pickedFile; + debugPrint('This is a list test' + pickedFile.toString()); + // _imageFile = pickedFile[0]; }); } catch (e) { setState(() { + debugPrint('Mistakes have been made :('); _pickImageError = e; }); } diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index 22315100c961..e0775d5cdc74 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -70,6 +70,18 @@ class ImagePicker { ); } + Future?> getMultiImage({ + double? maxWidth, + double? maxHeight, + int? imageQuality, + }) { + return platform.pickMultiImage( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: imageQuality, + ); + } + /// Returns a [PickedFile] object wrapping the video that was picked. /// /// The returned [PickedFile] is intended to be used within a single APP session. Do not save the file path and use it across sessions. diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 562528466861..67075fe2b969 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -17,7 +17,8 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.0 - image_picker_platform_interface: ^2.0.0 + image_picker_platform_interface: #^2.0.1 + path: ../image_picker_platform_interface dev_dependencies: flutter_test: diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart index 31847a188d84..c696d8fba459 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart @@ -42,13 +42,13 @@ class MethodChannelImagePicker extends ImagePickerPlatform { double? maxHeight, int? imageQuality, }) async { - List? paths = await _pickMultiImagePath( + List paths = await _pickMultiImagePath( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ); List files = []; - for (final path in paths!) { + for (final path in paths) { files.add(PickedFile(path)); } return files; @@ -72,7 +72,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } - return _channel.invokeMethod>( + return _channel.invokeMethod>( 'pickMultiImage', { 'maxWidth': maxWidth, From 7b934b025564fa51a7a9e72d83971b2db1939824 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Fri, 12 Mar 2021 15:29:03 +0100 Subject: [PATCH 03/79] Added some Android unit tests --- .../imagepicker/ImagePickerDelegateTest.java | 60 +++++++++++++++++++ .../imagepicker/ImagePickerPluginTest.java | 11 +++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index aa9b00521f53..64dbf554bdb1 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -10,11 +10,14 @@ import static org.mockito.Mockito.when; import android.Manifest; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; + import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.io.File; @@ -96,6 +99,17 @@ public void chooseImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyAc verifyNoMoreInteractions(mockResult); } + @Test + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public void chooseMultiImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyActiveError() { + ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); + + delegate.chooseMultiImageFromGallery(mockMethodCall, mockResult); + + verifyFinishedWithAlreadyActiveError(); + verifyNoMoreInteractions(mockResult); + } + @Test public void chooseImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) @@ -110,6 +124,21 @@ public void chooseImageFromGallery_WhenHasNoExternalStoragePermission_RequestsFo ImagePickerDelegate.REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION); } + @Test + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public void chooseMultiImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) + .thenReturn(false); + + ImagePickerDelegate delegate = createDelegate(); + delegate.chooseMultiImageFromGallery(mockMethodCall, mockResult); + + verify(mockPermissionManager) + .askForPermission( + Manifest.permission.READ_EXTERNAL_STORAGE, + ImagePickerDelegate.REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION); + } + @Test public void chooseImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { @@ -124,6 +153,21 @@ public void chooseImageFromGallery_WhenHasNoExternalStoragePermission_RequestsFo any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY)); } + @Test + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public void + chooseMultiImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { + when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) + .thenReturn(true); + + ImagePickerDelegate delegate = createDelegate(); + delegate.chooseMultiImageFromGallery(mockMethodCall, mockResult); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); + } + @Test public void takeImageWithCamera_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); @@ -217,6 +261,22 @@ public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermis any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY)); } + @Test + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public void + onRequestChooseMultiImagePermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseMultiImageFromGalleryIntent() { + ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); + + delegate.onRequestPermissionsResult( + ImagePickerDelegate.REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION, + new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, + new int[] {PackageManager.PERMISSION_GRANTED}); + + verify(mockActivity) + .startActivityForResult( + any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); + } + @Test public void onRequestChooseVideoPermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseVideoFromGalleryIntent() { diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java index de1623e93db4..1a1be895891c 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java @@ -24,6 +24,7 @@ public class ImagePickerPluginTest { private static final int SOURCE_CAMERA = 0; private static final int SOURCE_GALLERY = 1; private static final String PICK_IMAGE = "pickImage"; + private static final String PICK_MULTI_IMAGE = "pickMultiImage"; private static final String PICK_VIDEO = "pickVideo"; @Rule public ExpectedException exception = ExpectedException.none(); @@ -84,6 +85,14 @@ public void onMethodCall_WhenSourceIsGallery_InvokesChooseImageFromGallery() { verifyZeroInteractions(mockResult); } + @Test + public void onMethodCall_InvokesChooseMultiImageFromGallery() { + MethodCall call = buildMethodCall(PICK_MULTI_IMAGE, null); + plugin.onMethodCall(call, mockResult); + verify(mockImagePickerDelegate).chooseMultiImageFromGallery(eq(call), any()); + verifyZeroInteractions(mockResult); + } + @Test public void onMethodCall_WhenSourceIsCamera_InvokesTakeImageWithCamera() { MethodCall call = buildMethodCall(PICK_IMAGE, SOURCE_CAMERA); @@ -145,7 +154,7 @@ public void onConstructor_WhenContextTypeIsActivity_ShouldNotCrash() { "No exception thrown when ImagePickerPlugin() ran with context instanceof Activity", true); } - private MethodCall buildMethodCall(String method, final int source) { + private MethodCall buildMethodCall(String method, final Integer source) { final Map arguments = new HashMap<>(); arguments.put("source", source); From 82385a53313be4ef5310273165db30626f7a066c Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Fri, 12 Mar 2021 16:06:03 +0100 Subject: [PATCH 04/79] Fixed failing tests --- .../io/flutter/plugins/imagepicker/ImagePickerDelegate.java | 1 + .../flutter/plugins/imagepicker/ImagePickerDelegateTest.java | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index ee01b5e46ae5..051f7c3f8bf3 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -623,6 +623,7 @@ private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled if (finalImagePath != null && !finalImagePath.equals(path) && shouldDeleteOriginalIfScaled) { new File(path).delete(); } + finishWithSuccess(finalImagePath); } else { finishWithSuccess(path); } diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 64dbf554bdb1..d962ec71b838 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -100,7 +100,6 @@ public void chooseImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyAc } @Test - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void chooseMultiImageFromGallery_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); @@ -125,7 +124,6 @@ public void chooseImageFromGallery_WhenHasNoExternalStoragePermission_RequestsFo } @Test - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void chooseMultiImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) .thenReturn(false); @@ -154,7 +152,6 @@ public void chooseMultiImageFromGallery_WhenHasNoExternalStoragePermission_Reque } @Test - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void chooseMultiImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) @@ -262,7 +259,6 @@ public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermis } @Test - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void onRequestChooseMultiImagePermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseMultiImageFromGalleryIntent() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); From 74c9fad33499f0cad00e46c9e83f0785bb17241b Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 17 Mar 2021 10:06:56 +0100 Subject: [PATCH 05/79] Added tests --- .../method_channel_image_picker.dart | 8 +- .../new_method_channel_image_picker_test.dart | 108 +++++++++++++++++- 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart index c696d8fba459..ca7da8f8dc1e 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/method_channel/method_channel_image_picker.dart @@ -42,11 +42,13 @@ class MethodChannelImagePicker extends ImagePickerPlatform { double? maxHeight, int? imageQuality, }) async { - List paths = await _pickMultiImagePath( + List? paths = await _pickMultiImagePath( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, ); + if (paths == null) return null; + List files = []; for (final path in paths) { files.add(PickedFile(path)); @@ -54,7 +56,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform { return files; } - Future _pickMultiImagePath({ + Future?> _pickMultiImagePath({ double? maxWidth, double? maxHeight, int? imageQuality, @@ -72,7 +74,7 @@ class MethodChannelImagePicker extends ImagePickerPlatform { throw ArgumentError.value(maxHeight, 'maxHeight', 'cannot be negative'); } - return _channel.invokeMethod>( + return _channel.invokeMethod?>( 'pickMultiImage', { 'maxWidth': maxWidth, diff --git a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart index df35f8fd96b8..8c3023f82f69 100644 --- a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart +++ b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart @@ -16,7 +16,8 @@ void main() { final List log = []; - setUp(() { + + setUpAll(() { picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); return ''; @@ -196,6 +197,111 @@ void main() { }); }); + group('#pickMultiImage', () { + test('calls the method correctly', () async { + await picker.pickMultiImage(); + + expect( + log, + [ + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + }), + ], + ); + }); + + test('passes the width and height arguments correctly', () async { + 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, + [ + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': 70, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': 70, + }), + isMethodCall('pickMultiImage', arguments: { + '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('handles a null image path response gracefully', () async { + picker.channel + .setMockMethodCallHandler((MethodCall methodCall) => null); + + expect(await picker.pickMultiImage(), isNull); + expect(await picker.pickMultiImage(), isNull); + }); + }); + group('#pickVideoPath', () { test('passes the image source argument correctly', () async { await picker.pickVideo(source: ImageSource.camera); From f027e9a82530504183b3f7d78bf20d5ddd388a9d Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 17 Mar 2021 10:31:55 +0100 Subject: [PATCH 06/79] fixed platform_interface tests --- .../test/new_method_channel_image_picker_test.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart index 8c3023f82f69..a4de47df73bc 100644 --- a/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart +++ b/packages/image_picker/image_picker_platform_interface/test/new_method_channel_image_picker_test.dart @@ -15,12 +15,12 @@ void main() { MethodChannelImagePicker picker = MethodChannelImagePicker(); final List log = []; + dynamic returnValue = ''; - - setUpAll(() { + setUp(() { picker.channel.setMockMethodCallHandler((MethodCall methodCall) async { log.add(methodCall); - return ''; + return returnValue; }); log.clear(); @@ -199,6 +199,7 @@ void main() { group('#pickMultiImage', () { test('calls the method correctly', () async { + returnValue = ['0', '1']; await picker.pickMultiImage(); expect( @@ -214,6 +215,7 @@ void main() { }); test('passes the width and height arguments correctly', () async { + returnValue = ['0', '1']; await picker.pickMultiImage(); await picker.pickMultiImage( maxWidth: 10.0, @@ -282,6 +284,7 @@ void main() { }); test('does not accept a negative width or height argument', () { + returnValue = ['0', '1']; expect( () => picker.pickMultiImage(maxWidth: -1.0), throwsArgumentError, From 31eae2d16b7a11f2e5925be5f2c8127c7b26be0f Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Wed, 24 Mar 2021 17:17:28 +0100 Subject: [PATCH 07/79] WIP: iOS implementation --- .../ios/Classes/FLTImagePickerPlugin.m | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 8d260f31b055..7246d207758c 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -7,6 +7,7 @@ #import #import #import +#import #import #import "FLTImagePickerImageUtil.h" @@ -58,6 +59,40 @@ - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { return topController; } +- (void)pickImage:(bool)single { + if(@available(iOS 14, *)){ + PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init]; + if (single) config.selectionLimit = 1000; + config.filter = [PHPickerFilter imagesFilter]; + + PHPickerViewController *pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config]; + //No clue what this is supposed to do, it was in the guide but doesnt work +// pickerViewController.delegate = self; + [[self viewControllerWithWindow:nil] presentViewController:pickerViewController + animated:YES + completion:nil]; + } +} + +-(void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)){ + [picker dismissViewControllerAnimated:YES completion:nil]; + + for (PHPickerResult *result in results) + { + // Get UIImage + [result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id _Nullable object, NSError * _Nullable error) + { + if ([object isKindOfClass:[UIImage class]]) + { + dispatch_async(dispatch_get_main_queue(), ^{ + NSLog(@"Selected image: %@", (UIImage*)object); + }); + } + }]; + } +} + + - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if (self.result) { self.result([FlutterError errorWithCode:@"multiple_request" @@ -67,8 +102,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } if ([@"pickImage" isEqualToString:call.method]) { - _imagePickerController = [[UIImagePickerController alloc] init]; - _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + if(@available(iOS 14, *)) { + [self pickImage: false]; + } else { + _imagePickerController = [[UIImagePickerController alloc] init]; + _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; @@ -93,8 +131,12 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result message:@"Invalid image source." details:nil]); break; - } - } else if ([@"pickVideo" isEqualToString:call.method]) { + }} + } else if ([@"pickMultiImage" isEqualToString:call.method]) { + if(@available(iOS 14, *)) { + [self pickImage: true]; + } + } else if ([@"pickVideo" isEqualToString:call.method]) { _imagePickerController = [[UIImagePickerController alloc] init]; _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; From bbb094bde4d900a99918cb902e1ea13e015acb73 Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Wed, 31 Mar 2021 16:47:49 +0200 Subject: [PATCH 08/79] Finished iOS implementation; Formatted code --- .../imagepicker/ImagePickerDelegate.java | 45 +++--- .../imagepicker/ImagePickerPlugin.java | 2 +- .../imagepicker/ImagePickerDelegateTest.java | 22 +-- .../ios/Classes/FLTImagePickerPlugin.m | 142 ++++++++++-------- 4 files changed, 117 insertions(+), 94 deletions(-) diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 051f7c3f8bf3..d972d78f5366 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -14,8 +14,6 @@ import android.net.Uri; import android.os.Build; import android.provider.MediaStore; - -import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import androidx.core.app.ActivityCompat; import androidx.core.content.FileProvider; @@ -25,7 +23,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; @@ -345,7 +342,8 @@ public void chooseMultiImageFromGallery(MethodCall methodCall, MethodChannel.Res if (!permissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) { permissionManager.askForPermission( - Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION); + Manifest.permission.READ_EXTERNAL_STORAGE, + REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION); return; } @@ -359,7 +357,6 @@ private void launchPickImageFromGalleryIntent() { activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY); } - private void launchMultiPickImageFromGalleryIntent() { Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { @@ -463,7 +460,7 @@ public boolean onRequestPermissionsResult( break; case REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION: if (permissionGranted) { - launchMultiPickImageFromGalleryIntent(); + launchMultiPickImageFromGalleryIntent(); } break; case REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION: @@ -542,7 +539,6 @@ private void handleChooseMultiImageResult(int resultCode, Intent data) { ArrayList paths = new ArrayList<>(); for (int i = 0; i < data.getClipData().getItemCount(); i++) { paths.add(fileUtils.getPathFromUri(activity, data.getClipData().getItemAt(i).getUri())); - } handleMultiImageResult(paths, false); return; @@ -601,18 +597,21 @@ public void onPathReady(String path) { finishWithSuccess(null); } - private void handleMultiImageResult(ArrayList paths, boolean shouldDeleteOriginalIfScaled) { + private void handleMultiImageResult( + ArrayList paths, boolean shouldDeleteOriginalIfScaled) { if (methodCall != null) { - for(int i =0; i < paths.size(); i++) { - String finalImagePath = resizeImage(paths.get(i)); - - //delete original file if scaled - if (finalImagePath != null && !finalImagePath.equals(paths.get(i)) && shouldDeleteOriginalIfScaled) { - new File(paths.get(i)).delete(); - } - paths.set(i, finalImagePath); - } - finishWithListSuccess(paths); + for (int i = 0; i < paths.size(); i++) { + String finalImagePath = resizeImage(paths.get(i)); + + //delete original file if scaled + if (finalImagePath != null + && !finalImagePath.equals(paths.get(i)) + && shouldDeleteOriginalIfScaled) { + new File(paths.get(i)).delete(); + } + paths.set(i, finalImagePath); + } + finishWithListSuccess(paths); } } @@ -630,11 +629,11 @@ private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled } private String resizeImage(String path) { - Double maxWidth = methodCall.argument("maxWidth"); - Double maxHeight = methodCall.argument("maxHeight"); - Integer imageQuality = methodCall.argument("imageQuality"); + Double maxWidth = methodCall.argument("maxWidth"); + Double maxHeight = methodCall.argument("maxHeight"); + Integer imageQuality = methodCall.argument("imageQuality"); - return imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality); + return imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality); } private void handleVideoResult(String path) { @@ -667,7 +666,7 @@ private void finishWithSuccess(String imagePath) { private void finishWithListSuccess(ArrayList imagePaths) { if (pendingResult == null) { - for (String imagePath : imagePaths){ + for (String imagePath : imagePaths) { cache.saveResult(imagePath, null, null); } return; diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index 0127a9024de3..5d47e039e0a1 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -306,7 +306,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result rawResult) { break; case METHOD_CALL_MULTI_IMAGE: delegate.chooseMultiImageFromGallery(call, result); - break; + break; case METHOD_CALL_VIDEO: imageSource = call.argument("source"); switch (imageSource) { diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index d962ec71b838..6b0393dfec30 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -10,14 +10,11 @@ import static org.mockito.Mockito.when; import android.Manifest; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; - import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.io.File; @@ -124,7 +121,8 @@ public void chooseImageFromGallery_WhenHasNoExternalStoragePermission_RequestsFo } @Test - public void chooseMultiImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() { + public void + chooseMultiImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) .thenReturn(false); @@ -162,7 +160,8 @@ public void chooseMultiImageFromGallery_WhenHasNoExternalStoragePermission_Reque verify(mockActivity) .startActivityForResult( - any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); + any(Intent.class), + eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); } @Test @@ -260,17 +259,18 @@ public void takeImageWithCamera_WhenCameraPermissionNotPresent_RequestsForPermis @Test public void - onRequestChooseMultiImagePermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseMultiImageFromGalleryIntent() { + onRequestChooseMultiImagePermissionsResult_WhenReadExternalStorageGranted_LaunchesChooseMultiImageFromGalleryIntent() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); delegate.onRequestPermissionsResult( - ImagePickerDelegate.REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION, - new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, - new int[] {PackageManager.PERMISSION_GRANTED}); + ImagePickerDelegate.REQUEST_EXTERNAL_MULTI_IMAGE_STORAGE_PERMISSION, + new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, + new int[] {PackageManager.PERMISSION_GRANTED}); verify(mockActivity) - .startActivityForResult( - any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); + .startActivityForResult( + any(Intent.class), + eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); } @Test diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 7246d207758c..80fb82c5aca1 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -14,7 +14,9 @@ #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" -@interface FLTImagePickerPlugin () +@interface FLTImagePickerPlugin () @property(copy, nonatomic) FlutterResult result; @@ -60,39 +62,54 @@ - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { } - (void)pickImage:(bool)single { - if(@available(iOS 14, *)){ - PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init]; - if (single) config.selectionLimit = 1000; - config.filter = [PHPickerFilter imagesFilter]; - - PHPickerViewController *pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config]; - //No clue what this is supposed to do, it was in the guide but doesnt work -// pickerViewController.delegate = self; - [[self viewControllerWithWindow:nil] presentViewController:pickerViewController - animated:YES - completion:nil]; - } + if (@available(iOS 14, *)) { + PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init]; + if (!single) config.selectionLimit = 1000; + config.filter = [PHPickerFilter imagesFilter]; + + PHPickerViewController *pickerViewController = + [[PHPickerViewController alloc] initWithConfiguration:config]; + // No clue what this is supposed to do, it was in the guide but doesnt work + pickerViewController.delegate = self; + [[self viewControllerWithWindow:nil] presentViewController:pickerViewController + animated:YES + completion:nil]; + } } --(void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)){ - [picker dismissViewControllerAnimated:YES completion:nil]; - - for (PHPickerResult *result in results) - { - // Get UIImage - [result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id _Nullable object, NSError * _Nullable error) - { - if ([object isKindOfClass:[UIImage class]]) - { - dispatch_async(dispatch_get_main_queue(), ^{ - NSLog(@"Selected image: %@", (UIImage*)object); - }); - } - }]; - } +- (void)picker:(PHPickerViewController *)picker + didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)) { + [picker dismissViewControllerAnimated:YES completion:nil]; + NSMutableArray *pathList = [NSMutableArray new]; + for (PHPickerResult *result in results) { + [result.itemProvider + loadObjectOfClass:[UIImage class] + completionHandler:^(__kindof id _Nullable object, + NSError *_Nullable error) { + if ([object isKindOfClass:[UIImage class]]) { + if (object != nil) { + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *filename = + [NSString stringWithFormat:@"image%lu.png", (unsigned long)pathList.count]; + NSString *path = [documentsDirectory stringByAppendingPathComponent:filename]; + NSData *data = UIImagePNGRepresentation(object); + [data writeToFile:path atomically:YES]; + [pathList addObject:path]; + if (pathList.count == results.count) { + if (results.count == 1) { + self.result(pathList[0]); + } else { + self.result(pathList); + } + } + } + } + }]; + } } - - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if (self.result) { self.result([FlutterError errorWithCode:@"multiple_request" @@ -102,41 +119,48 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } if ([@"pickImage" isEqualToString:call.method]) { - if(@available(iOS 14, *)) { - [self pickImage: false]; - } else { - _imagePickerController = [[UIImagePickerController alloc] init]; + if (@available(iOS 14, *)) { + self.result = result; + _arguments = call.arguments; + [self pickImage:true]; + } else { + _imagePickerController = [[UIImagePickerController alloc] init]; _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; - _imagePickerController.delegate = self; - _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; + _imagePickerController.delegate = self; + _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; - self.result = result; - _arguments = call.arguments; + self.result = result; + _arguments = call.arguments; - int imageSource = [[_arguments objectForKey:@"source"] intValue]; + int imageSource = [[_arguments objectForKey:@"source"] intValue]; - switch (imageSource) { - case SOURCE_CAMERA: { - NSInteger cameraDevice = [[_arguments objectForKey:@"cameraDevice"] intValue]; - _device = (cameraDevice == 1) ? UIImagePickerControllerCameraDeviceFront - : UIImagePickerControllerCameraDeviceRear; - [self checkCameraAuthorization]; - break; + switch (imageSource) { + case SOURCE_CAMERA: { + NSInteger cameraDevice = [[_arguments objectForKey:@"cameraDevice"] intValue]; + _device = (cameraDevice == 1) ? UIImagePickerControllerCameraDeviceFront + : UIImagePickerControllerCameraDeviceRear; + [self checkCameraAuthorization]; + break; + } + case SOURCE_GALLERY: + [self checkPhotoAuthorization]; + break; + default: + result([FlutterError errorWithCode:@"invalid_source" + message:@"Invalid image source." + details:nil]); + break; } - case SOURCE_GALLERY: - [self checkPhotoAuthorization]; - break; - default: - result([FlutterError errorWithCode:@"invalid_source" - message:@"Invalid image source." - details:nil]); - break; - }} + } } else if ([@"pickMultiImage" isEqualToString:call.method]) { - if(@available(iOS 14, *)) { - [self pickImage: true]; - } - } else if ([@"pickVideo" isEqualToString:call.method]) { + if (@available(iOS 14, *)) { + NSLog(@"pickImage has been called on iOS14+"); + self.result = result; + _arguments = call.arguments; + NSLog(@"Result and arguments have been set"); + [self pickImage:false]; + } + } else if ([@"pickVideo" isEqualToString:call.method]) { _imagePickerController = [[UIImagePickerController alloc] init]; _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; From e8cf955ee1426f366f0e8704f5ad02d810772dd1 Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Wed, 31 Mar 2021 16:51:33 +0200 Subject: [PATCH 09/79] Reverted changes in example --- packages/image_picker/image_picker/example/lib/main.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index a9095b199113..54e3a1ae4cd2 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -87,19 +87,17 @@ class _MyHomePageState extends State { await _displayPickImageDialog(context!, (double? maxWidth, double? maxHeight, int? quality) async { try { - final pickedFile = await _picker.getMultiImage( - // source: source, + final pickedFile = await _picker.getImage( + source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, ); setState(() { - debugPrint('This is a list test' + pickedFile.toString()); - // _imageFile = pickedFile[0]; + _imageFile = pickedFile; }); } catch (e) { setState(() { - debugPrint('Mistakes have been made :('); _pickImageError = e; }); } From a5436753e2a1deb7074f72650aad5c269d8cc815 Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Fri, 2 Apr 2021 14:49:25 +0200 Subject: [PATCH 10/79] Added image compression to iOS implementation; Removed some NSLogs; Added log for _arguments objectForKey:@"maxWidth"]; + NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; + NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; + + if(maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { + path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data + image:object + maxWidth:maxWidth + maxHeight:maxHeight + imageQuality:imageQuality]; + } else { + [data writeToFile:path atomically:YES]; + } [pathList addObject:path]; if (pathList.count == results.count) { if (results.count == 1) { @@ -154,11 +165,11 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } } else if ([@"pickMultiImage" isEqualToString:call.method]) { if (@available(iOS 14, *)) { - NSLog(@"pickImage has been called on iOS14+"); self.result = result; _arguments = call.arguments; - NSLog(@"Result and arguments have been set"); [self pickImage:false]; + } else { + NSLog(@"pickMultiImage is not supported on versions below iOS14"); } } else if ([@"pickVideo" isEqualToString:call.method]) { _imagePickerController = [[UIImagePickerController alloc] init]; diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index e0775d5cdc74..c1c85a08da1f 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -70,6 +70,23 @@ class ImagePicker { ); } + /// Returns a [List] object wrapping the images that were picked. + /// + /// The returned [List] is intended to be used within a single APP session. Do not save the file path and use it across sessions. + /// + /// Where iOS supports HEIC images, Android 8 and below doesn't. Android 9 and above only support HEIC images if used + /// in addition to a size modification, of which the usage is explained below. + /// + /// This method is not supported in iOS versions lower than 14. + /// + /// If specified, the image will be at most `maxWidth` wide and + /// `maxHeight` tall. Otherwise the image will be returned at it's + /// original width and height. + /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 + /// where 100 is the original/max quality. If `imageQuality` is null, the image with + /// the original quality will be returned. Compression is only supported for certain + /// 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. Future?> getMultiImage({ double? maxWidth, double? maxHeight, From 0844445b56d25dd8e0e5232445dcf494a154816a Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Fri, 2 Apr 2021 14:52:33 +0200 Subject: [PATCH 11/79] Updated docs, bumped version to 0.7.3 --- packages/image_picker/image_picker/CHANGELOG.md | 6 +++++- packages/image_picker/image_picker/lib/image_picker.dart | 8 ++++---- packages/image_picker/image_picker/pubspec.yaml | 5 ++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 7be13a574a77..a186c3747e51 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,6 +1,10 @@ +## 0.7.3 + +* Implemented pickMultiImage() for Android and iOS14+. + ## 0.7.2 -* Run CocoaPods iOS tests in RunnerUITests target +* Run CocoaPods iOS tests in RunnerUITests target. ## 0.7.1 diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index c1c85a08da1f..6f3b18e167f3 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -79,11 +79,11 @@ class ImagePicker { /// /// This method is not supported in iOS versions lower than 14. /// - /// If specified, the image will be at most `maxWidth` wide and - /// `maxHeight` tall. Otherwise the image will be returned at it's + /// If specified, the images will be at most `maxWidth` wide and + /// `maxHeight` tall. Otherwise the images will be returned at it's /// original width and height. - /// The `imageQuality` argument modifies the quality of the image, ranging from 0-100 - /// where 100 is the original/max quality. If `imageQuality` is null, the image with + /// The `imageQuality` argument modifies the quality of the images, ranging from 0-100 + /// where 100 is the original/max quality. If `imageQuality` is null, the images with /// the original quality will be returned. Compression is only supported for certain /// 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. diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 67075fe2b969..3b6ab4405f89 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker -version: 0.7.2 +version: 0.7.3 flutter: plugin: @@ -17,8 +17,7 @@ dependencies: flutter: sdk: flutter flutter_plugin_android_lifecycle: ^2.0.0 - image_picker_platform_interface: #^2.0.1 - path: ../image_picker_platform_interface + image_picker_platform_interface: ^2.1.0 dev_dependencies: flutter_test: From 3fd1a0174583d35a0953c26385135d77537f8203 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 14 Apr 2021 13:09:39 +0200 Subject: [PATCH 12/79] Updated version --- packages/image_picker/image_picker/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index e2b5c6aabcbf..2dce386c3c47 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker -version: 0.7.4 +version: 0.7.5 flutter: plugin: From 75b34bee599b354e5b6af2db644cd3dde5f5c6d6 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Wed, 14 Apr 2021 13:10:42 +0200 Subject: [PATCH 13/79] Fixed formatting --- .../ios/Classes/FLTImagePickerPlugin.m | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 306bee7b14d2..7a3d1526e7cc 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -94,19 +94,19 @@ - (void)picker:(PHPickerViewController *)picker [NSString stringWithFormat:@"image%lu.png", (unsigned long)pathList.count]; NSString *path = [documentsDirectory stringByAppendingPathComponent:filename]; NSData *data = UIImagePNGRepresentation(object); - NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; - NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; - NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; + NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; + NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; + NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; - if(maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data - image:object - maxWidth:maxWidth - maxHeight:maxHeight - imageQuality:imageQuality]; - } else { - [data writeToFile:path atomically:YES]; - } + if(maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { + path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data + image:object + maxWidth:maxWidth + maxHeight:maxHeight + imageQuality:imageQuality]; + } else { + [data writeToFile:path atomically:YES]; + } [pathList addObject:path]; if (pathList.count == results.count) { if (results.count == 1) { @@ -169,7 +169,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result _arguments = call.arguments; [self pickImage:false]; } else { - NSLog(@"pickMultiImage is not supported on versions below iOS14"); + NSLog(@"pickMultiImage is not supported on versions below iOS14"); } } else if ([@"pickVideo" isEqualToString:call.method]) { _imagePickerController = [[UIImagePickerController alloc] init]; From 2619cf497b27f7d0a5a759f6f70def02cd396bfb Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Wed, 14 Apr 2021 13:56:05 +0200 Subject: [PATCH 14/79] Fixed formatting --- .../ios/Classes/FLTImagePickerPlugin.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 7a3d1526e7cc..67cbef3b02c6 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -97,15 +97,15 @@ - (void)picker:(PHPickerViewController *)picker NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; - - if(maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data - image:object - maxWidth:maxWidth - maxHeight:maxHeight - imageQuality:imageQuality]; + + if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { + path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data + image:object + maxWidth:maxWidth + maxHeight:maxHeight + imageQuality:imageQuality]; } else { - [data writeToFile:path atomically:YES]; + [data writeToFile:path atomically:YES]; } [pathList addObject:path]; if (pathList.count == results.count) { From 4362c6e98098f8a8c39b2ea45d20b1ab2ebd385b Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Thu, 22 Apr 2021 14:41:01 +0200 Subject: [PATCH 15/79] Improved iOS implementation --- .../ios/Classes/FLTImagePickerPlugin.m | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 67cbef3b02c6..0ca4bbd5550d 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -19,6 +19,7 @@ @interface FLTImagePickerPlugin () @property(copy, nonatomic) FlutterResult result; +@property(nonatomic) bool single; @end @@ -64,7 +65,8 @@ - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { - (void)pickImage:(bool)single { if (@available(iOS 14, *)) { PHPickerConfiguration *config = [[PHPickerConfiguration alloc] init]; - if (!single) config.selectionLimit = 1000; + self->_single = single; + if (!single) config.selectionLimit = 0; config.filter = [PHPickerFilter imagesFilter]; PHPickerViewController *pickerViewController = @@ -81,43 +83,40 @@ - (void)picker:(PHPickerViewController *)picker [picker dismissViewControllerAnimated:YES completion:nil]; NSMutableArray *pathList = [NSMutableArray new]; for (PHPickerResult *result in results) { - [result.itemProvider - loadObjectOfClass:[UIImage class] - completionHandler:^(__kindof id _Nullable object, - NSError *_Nullable error) { - if ([object isKindOfClass:[UIImage class]]) { - if (object != nil) { - NSArray *paths = - NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - NSString *filename = - [NSString stringWithFormat:@"image%lu.png", (unsigned long)pathList.count]; - NSString *path = [documentsDirectory stringByAppendingPathComponent:filename]; - NSData *data = UIImagePNGRepresentation(object); - NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; - NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; - NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; - - if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data - image:object - maxWidth:maxWidth - maxHeight:maxHeight - imageQuality:imageQuality]; - } else { - [data writeToFile:path atomically:YES]; - } - [pathList addObject:path]; - if (pathList.count == results.count) { - if (results.count == 1) { - self.result(pathList[0]); - } else { - self.result(pathList); - } - } - } + [result.itemProvider loadDataRepresentationForTypeIdentifier:@"public.image" + completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) { + NSLog(@"data: %lu", (unsigned long)data.length); + if (data != nil) { + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + + NSString *filename = + [NSString stringWithFormat:@"%@.png", result.itemProvider.suggestedName]; + NSString *path = [documentsDirectory stringByAppendingPathComponent:filename]; + NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; + NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; + NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; + + if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { + path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data + image:[UIImage imageWithData:data] + maxWidth:maxWidth + maxHeight:maxHeight + imageQuality:imageQuality]; + } else { + [data writeToFile:path atomically:YES]; + } + [pathList addObject:path]; + if (pathList.count == results.count) { + if (self->_single) { + self.result(pathList[0]); + } else { + self.result(pathList); } - }]; + } + } + }]; } } From 9b1171f9576ab90899cd9aebdcd0dd0b5dfbbcba Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Thu, 22 Apr 2021 14:55:49 +0200 Subject: [PATCH 16/79] Improved iOS implementation --- .../ios/Classes/FLTImagePickerPlugin.m | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 0ca4bbd5550d..8bea8e235c89 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -83,40 +83,47 @@ - (void)picker:(PHPickerViewController *)picker [picker dismissViewControllerAnimated:YES completion:nil]; NSMutableArray *pathList = [NSMutableArray new]; for (PHPickerResult *result in results) { - [result.itemProvider loadDataRepresentationForTypeIdentifier:@"public.image" - completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) { - NSLog(@"data: %lu", (unsigned long)data.length); - if (data != nil) { - NSArray *paths = - NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - - NSString *filename = - [NSString stringWithFormat:@"%@.png", result.itemProvider.suggestedName]; - NSString *path = [documentsDirectory stringByAppendingPathComponent:filename]; - NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; - NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; - NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; - - if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - path = [FLTImagePickerPhotoAssetUtil saveImageWithOriginalImageData:data - image:[UIImage imageWithData:data] - maxWidth:maxWidth - maxHeight:maxHeight - imageQuality:imageQuality]; - } else { - [data writeToFile:path atomically:YES]; - } - [pathList addObject:path]; - if (pathList.count == results.count) { - if (self->_single) { - self.result(pathList[0]); - } else { - self.result(pathList); - } - } - } - }]; + [result.itemProvider + loadDataRepresentationForTypeIdentifier:@"public.image" + completionHandler:^(NSData *_Nullable data, + NSError *_Nullable error) { + NSLog(@"data: %lu", (unsigned long)data.length); + if (data != nil) { + NSArray *paths = NSSearchPathForDirectoriesInDomains( + NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + + NSString *filename = + [NSString stringWithFormat:@"%@.png", + result.itemProvider.suggestedName]; + NSString *path = + [documentsDirectory stringByAppendingPathComponent:filename]; + NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; + NSNumber *maxHeight = + [self->_arguments objectForKey:@"maxHeight"]; + NSNumber *imageQuality = + [self->_arguments objectForKey:@"imageQuality"]; + if (maxWidth != (id)[NSNull null] || + maxHeight != (id)[NSNull null]) { + path = [FLTImagePickerPhotoAssetUtil + saveImageWithOriginalImageData:data + image:[UIImage imageWithData:data] + maxWidth:maxWidth + maxHeight:maxHeight + imageQuality:imageQuality]; + } else { + [data writeToFile:path atomically:YES]; + } + [pathList addObject:path]; + if (pathList.count == results.count) { + if (self.single) { + self.result(pathList[0]); + } else { + self.result(pathList); + } + } + } + }]; } } From e6932660b8f3a5077930bc4f2f77ac24e95ed888 Mon Sep 17 00:00:00 2001 From: Daniel Roek Date: Thu, 22 Apr 2021 15:38:01 +0200 Subject: [PATCH 17/79] Changed test to second image, because first sample image is HEIC --- .../example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m b/packages/image_picker/image_picker/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m index a3ca1a1f8892..4b2163d00577 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m @@ -165,7 +165,7 @@ - (void)launchPickerAndPick { // Find an image and tap on it. (IOS 14 UI, images are showing directly) XCUIElement* aImage; if (@available(iOS 14, *)) { - aImage = self.app.scrollViews.firstMatch.images.firstMatch; + aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1]; } else { XCUIElement* allPhotosCell = [self.app.cells elementMatchingPredicate:[NSPredicate predicateWithFormat:@"label == %@", @"All Photos"]]; From c09e604f7d8c3c0c38c6ef04d978cc98cb15b371 Mon Sep 17 00:00:00 2001 From: "daniel.roek" Date: Thu, 22 Apr 2021 15:54:05 +0200 Subject: [PATCH 18/79] Updated documentation --- packages/image_picker/image_picker/CHANGELOG.md | 2 +- packages/image_picker/image_picker/README.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 0552a79714f0..a1197d09d93a 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -9,7 +9,7 @@ ## 0.7.3 -* Endorse image_picker_for_web +* Endorse image_picker_for_web. ## 0.7.2+1 diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index ca8ad763c553..178ead9168a6 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -11,6 +11,9 @@ First, add `image_picker` as a [dependency in your pubspec.yaml file](https://fl ### iOS +Starting with version **0.7.5** the iOS implementation uses PHPicker to pick (multiple) images on iOS14 or higher. The multiple images implementation does NOT support iOS versions lower than 14. +As a result of implementing PHPicker it becomes impossible to pick HEIC images on the iOS simulator in iOS14+. This is a known issue. Please test this on a real device, or test with non-HEIC images until Apple solves this issue. + Add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: * `NSPhotoLibraryUsageDescription` - describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor. From d3315c80b1c07887c9e7698c2535806055129bc5 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 25 May 2021 12:18:08 +0200 Subject: [PATCH 19/79] Refactor handleChooseMultiImageResult method When only one image is chosen getClipData is returned null and it cause a crash. --- .../plugins/imagepicker/ImagePickerDelegate.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 7079888189dc..72e851eaf948 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -536,9 +536,15 @@ private void handleChooseImageResult(int resultCode, Intent data) { private void handleChooseMultiImageResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { - ArrayList paths = new ArrayList<>(); - for (int i = 0; i < data.getClipData().getItemCount(); i++) { - paths.add(fileUtils.getPathFromUri(activity, data.getClipData().getItemAt(i).getUri())); + ArrayList paths; + if (data.getClipData() != null) { + paths = new ArrayList<>(); + for (int i = 0; i < data.getClipData().getItemCount(); i++) { + paths.add(fileUtils.getPathFromUri(activity, data.getClipData().getItemAt(i).getUri())); + } + } else { + paths = new ArrayList<>(); + paths.add(fileUtils.getPathFromUri(activity, data.getData())); } handleMultiImageResult(paths, false); return; From 85d605c20144d8dce65e969d9747e5ff16960989 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 25 May 2021 16:09:48 +0200 Subject: [PATCH 20/79] Refactor picker to implement multi image option --- .../ios/Classes/FLTImagePickerPlugin.m | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index e76a6eadaf7f..ef718a0c0f58 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -20,6 +20,7 @@ @interface FLTImagePickerPlugin () @property(copy, nonatomic) FlutterResult result; + @property(nonatomic) bool single; @property(copy, nonatomic) NSDictionary *arguments; @@ -79,6 +80,8 @@ - (void)pickImageWithPHPicker:(bool)single API_AVAILABLE(ios(14)) { _pickerViewController = [[PHPickerViewController alloc] initWithConfiguration:config]; _pickerViewController.delegate = self; + self.single = single; + [self checkPhotoAuthorizationForAccessLevel]; } @@ -364,6 +367,7 @@ - (void)picker:(PHPickerViewController *)picker NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; NSNumber *imageQuality = [_arguments objectForKey:@"imageQuality"]; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; + NSMutableArray *pathList = [NSMutableArray new]; for (PHPickerResult *result in results) { [result.itemProvider @@ -382,12 +386,15 @@ - (void)picker:(PHPickerViewController *)picker maxHeight:maxHeight isMetadataAvailable:originalAsset != nil]; } - + __block NSString *savedPath; if (!originalAsset) { // Image picked without an original asset (e.g. User took a photo directly) - [self saveImageWithPickerInfo:nil - image:localImage - imageQuality:desiredImageQuality]; + savedPath = + [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil + image:localImage + imageQuality:desiredImageQuality]; + [pathList addObject:savedPath]; + } else { [[PHImageManager defaultManager] requestImageDataForAsset:originalAsset @@ -396,13 +403,21 @@ - (void)picker:(PHPickerViewController *)picker NSData *_Nullable imageData, NSString *_Nullable dataUTI, UIImageOrientation orientation, NSDictionary *_Nullable info) { // maxWidth and maxHeight are used only for GIF images. - [self saveImageWithOriginalImageData:imageData - image:localImage - maxWidth:maxWidth - maxHeight:maxHeight - imageQuality:desiredImageQuality]; + savedPath = [FLTImagePickerPhotoAssetUtil + saveImageWithOriginalImageData:imageData + image:localImage + maxWidth:maxWidth + maxHeight:maxHeight + imageQuality:desiredImageQuality]; + [pathList addObject:savedPath]; + if (pathList.count == results.count) { + [self handlePath:savedPath listCount:pathList]; + } }]; } + if (pathList.count == results.count) { + [self handlePath:savedPath listCount:pathList]; + } }); } }]; From aaf5fd85c5cb2a53ae997ecee827e2f15f96e97c Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 25 May 2021 16:10:36 +0200 Subject: [PATCH 21/79] Add handleMultiSavedPaths method to return images --- .../ios/Classes/FLTImagePickerPlugin.m | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index ef718a0c0f58..1866c686dc54 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -547,4 +547,19 @@ - (void)handleSavedPath:(NSString *)path { _arguments = nil; } +- (void)handleMultiSavedPaths:(NSMutableArray *)pathList { + if (!self.result) { + return; + } + if (pathList.count > 0) { + self.result(pathList); + } else { + self.result([FlutterError errorWithCode:@"create_error" + message:@"Temporary files could not be created" + details:nil]); + } + self.result = nil; + _arguments = nil; +} + @end From 7cf933821ece5f07cbc42f89e95c0d1f2cfa4ead Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 25 May 2021 16:11:08 +0200 Subject: [PATCH 22/79] Add handlePath to reuse the code --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 1866c686dc54..1a2ad962f062 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -424,6 +424,14 @@ - (void)picker:(PHPickerViewController *)picker } } +- (void)handlePath:(NSString *)savedPath listCount:(NSMutableArray *)pathList { + if (self.single) { + [self handleSavedPath:savedPath]; + } else { + [self handleMultiSavedPaths:pathList]; + } +} + - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL]; From 7d9b7a596c7085b27587b0ad871ec3ebcf3fb32b Mon Sep 17 00:00:00 2001 From: yusufdag Date: Wed, 26 May 2021 13:44:00 +0200 Subject: [PATCH 23/79] Add unit tests for multi image on Flutter --- .../image_picker/test/image_picker_test.dart | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) 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 f56d47ff262b..aca4e138fed2 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -347,6 +347,119 @@ void main() { }); }); }); + + group('$ImagePicker', () { + const MethodChannel channel = + MethodChannel('plugins.flutter.io/image_picker'); + + final List log = []; + + final picker = ImagePicker(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return []; + }); + + log.clear(); + }); + + test('ImagePicker platform instance overrides the actual platform used', + () { + final ImagePickerPlatform savedPlatform = ImagePickerPlatform.instance; + final MockPlatform mockPlatform = MockPlatform(); + ImagePickerPlatform.instance = mockPlatform; + expect(ImagePicker.platform, mockPlatform); + ImagePickerPlatform.instance = savedPlatform; + }); + + group('#pickMultiImage', () { + test('passes the width and height arguments correctly', () async { + 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, + [ + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': 70, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': 70, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': 70, + }), + ], + ); + }); + + test('does not accept a negative width or height argument', () { + expect( + picker.getMultiImage(maxWidth: -1.0), + throwsArgumentError, + ); + + expect( + picker.getMultiImage(maxHeight: -1.0), + throwsArgumentError, + ); + }); + + test('handles a null image path response gracefully', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) => null); + + expect(await picker.getMultiImage(), isNull); + expect(await picker.getMultiImage(), isNull); + }); + }); + }); } class MockPlatform extends Mock From bba960908f4262de4889e1b12eef7ef1abd8c1a0 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Wed, 26 May 2021 13:54:38 +0200 Subject: [PATCH 24/79] Change to group unit tests better way --- .../image_picker/test/image_picker_test.dart | 770 +++++++++--------- 1 file changed, 378 insertions(+), 392 deletions(-) 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 aca4e138fed2..d83b403d1d45 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -20,15 +20,6 @@ void main() { final picker = ImagePicker(); - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - return ''; - }); - - log.clear(); - }); - test('ImagePicker platform instance overrides the actual platform used', () { final ImagePickerPlatform savedPlatform = ImagePickerPlatform.instance; @@ -38,425 +29,420 @@ void main() { ImagePickerPlatform.instance = savedPlatform; }); - group('#pickImage', () { - test('passes the image source argument correctly', () async { - await picker.getImage(source: ImageSource.camera); - await picker.getImage(source: ImageSource.gallery); - - expect( - log, - [ - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': null, - 'maxHeight': null, - 'imageQuality': null, - 'cameraDevice': 0 - }), - isMethodCall('pickImage', arguments: { - 'source': 1, - 'maxWidth': null, - 'maxHeight': null, - 'imageQuality': null, - 'cameraDevice': 0 - }), - ], - ); + group('#Single image/video', () { + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return ''; + }); + + log.clear(); }); - 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( + group('#pickImage', () { + test('passes the image source argument correctly', () async { + await picker.getImage(source: ImageSource.camera); + await picker.getImage(source: ImageSource.gallery); + + expect( + log, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 1, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + ], + ); + }); + + 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, - imageQuality: 70); - - expect( - log, - [ - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': null, - 'maxHeight': null, - 'imageQuality': null, - 'cameraDevice': 0 - }), - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': 10.0, - 'maxHeight': null, - 'imageQuality': null, - 'cameraDevice': 0 - }), - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': null, - 'maxHeight': 10.0, - 'imageQuality': null, - 'cameraDevice': 0 - }), - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': 10.0, - 'maxHeight': 20.0, - 'imageQuality': null, - 'cameraDevice': 0 - }), - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': 10.0, - 'maxHeight': null, - 'imageQuality': 70, - 'cameraDevice': 0 - }), - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': null, - 'maxHeight': 10.0, - 'imageQuality': 70, - 'cameraDevice': 0 - }), - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': 10.0, - 'maxHeight': 20.0, - 'imageQuality': 70, - 'cameraDevice': 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, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': null, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': 70, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': 70, + 'cameraDevice': 0 + }), + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': 70, + 'cameraDevice': 0 + }), + ], + ); + }); - test('does not accept a negative width or height argument', () { - expect( - picker.getImage(source: ImageSource.camera, maxWidth: -1.0), - 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, - ); - }); + expect( + picker.getImage(source: ImageSource.camera, maxHeight: -1.0), + throwsArgumentError, + ); + }); - test('handles a null image path response gracefully', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) => null); + test('handles a null image path response gracefully', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) => null); - expect(await picker.getImage(source: ImageSource.gallery), isNull); - expect(await picker.getImage(source: ImageSource.camera), isNull); - }); + 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, - [ - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': null, - 'maxHeight': null, - 'imageQuality': null, - 'cameraDevice': 0, - }), - ], - ); - }); + test('camera position defaults to back', () async { + await picker.getImage(source: ImageSource.camera); + + expect( + log, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 0, + }), + ], + ); + }); - test('camera position can set to front', () async { - await picker.getImage( - source: ImageSource.camera, - preferredCameraDevice: CameraDevice.front); - - expect( - log, - [ - isMethodCall('pickImage', arguments: { - 'source': 0, - 'maxWidth': null, - 'maxHeight': null, - 'imageQuality': null, - 'cameraDevice': 1, - }), - ], - ); + test('camera position can set to front', () async { + await picker.getImage( + source: ImageSource.camera, + preferredCameraDevice: CameraDevice.front); + + expect( + log, + [ + isMethodCall('pickImage', arguments: { + 'source': 0, + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + 'cameraDevice': 1, + }), + ], + ); + }); }); - }); - group('#pickVideo', () { - test('passes the image source argument correctly', () async { - await picker.getVideo(source: ImageSource.camera); - await picker.getVideo(source: ImageSource.gallery); - - expect( - log, - [ - isMethodCall('pickVideo', arguments: { - 'source': 0, - 'cameraDevice': 0, - 'maxDuration': null, - }), - isMethodCall('pickVideo', arguments: { - 'source': 1, - 'cameraDevice': 0, - 'maxDuration': null, - }), - ], - ); - }); + group('#pickVideo', () { + test('passes the image source argument correctly', () async { + await picker.getVideo(source: ImageSource.camera); + await picker.getVideo(source: ImageSource.gallery); + + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'cameraDevice': 0, + 'maxDuration': null, + }), + isMethodCall('pickVideo', arguments: { + 'source': 1, + 'cameraDevice': 0, + '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, - [ - isMethodCall('pickVideo', arguments: { - 'source': 0, - 'maxDuration': null, - 'cameraDevice': 0, - }), - isMethodCall('pickVideo', arguments: { - 'source': 0, - 'maxDuration': 10, - 'cameraDevice': 0, - }), - isMethodCall('pickVideo', arguments: { - 'source': 0, - 'maxDuration': 60, - 'cameraDevice': 0, - }), - isMethodCall('pickVideo', arguments: { - 'source': 0, - 'maxDuration': 3600, - 'cameraDevice': 0, - }), - ], - ); - }); + 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, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': null, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 10, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 60, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 3600, + 'cameraDevice': 0, + }), + ], + ); + }); - test('handles a null video path response gracefully', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) => null); + test('handles a null video path response gracefully', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) => null); - expect(await picker.getVideo(source: ImageSource.gallery), isNull); - expect(await picker.getVideo(source: ImageSource.camera), isNull); - }); + 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, - [ - isMethodCall('pickVideo', arguments: { - 'source': 0, - 'cameraDevice': 0, - 'maxDuration': null, - }), - ], - ); - }); + test('camera position defaults to back', () async { + await picker.getVideo(source: ImageSource.camera); + + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'cameraDevice': 0, + 'maxDuration': null, + }), + ], + ); + }); - test('camera position can set to front', () async { - await picker.getVideo( - source: ImageSource.camera, - preferredCameraDevice: CameraDevice.front); - - expect( - log, - [ - isMethodCall('pickVideo', arguments: { - 'source': 0, - 'maxDuration': null, - 'cameraDevice': 1, - }), - ], - ); + test('camera position can set to front', () async { + await picker.getVideo( + source: ImageSource.camera, + preferredCameraDevice: CameraDevice.front); + + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': null, + 'cameraDevice': 1, + }), + ], + ); + }); }); - }); - group('#retrieveLostData', () { - test('retrieveLostData get success response', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return { - 'type': 'image', - 'path': '/example/path', - }; + group('#retrieveLostData', () { + test('retrieveLostData get success response', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return { + 'type': 'image', + 'path': '/example/path', + }; + }); + final LostData response = await picker.getLostData(); + expect(response.type, RetrieveType.image); + expect(response.file!.path, '/example/path'); }); - final LostData response = await picker.getLostData(); - expect(response.type, RetrieveType.image); - expect(response.file!.path, '/example/path'); - }); - test('retrieveLostData get error response', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return { - 'type': 'video', - 'errorCode': 'test_error_code', - 'errorMessage': 'test_error_message', - }; + test('retrieveLostData get error response', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return { + 'type': 'video', + 'errorCode': 'test_error_code', + 'errorMessage': 'test_error_message', + }; + }); + final LostData response = await picker.getLostData(); + expect(response.type, RetrieveType.video); + expect(response.exception!.code, 'test_error_code'); + expect(response.exception!.message, 'test_error_message'); }); - final LostData response = await picker.getLostData(); - expect(response.type, RetrieveType.video); - expect(response.exception!.code, 'test_error_code'); - expect(response.exception!.message, 'test_error_message'); - }); - test('retrieveLostData get null response', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return null; + test('retrieveLostData get null response', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return null; + }); + expect((await picker.getLostData()).isEmpty, true); }); - expect((await picker.getLostData()).isEmpty, true); - }); - test('retrieveLostData get both path and error should throw', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return { - 'type': 'video', - 'errorCode': 'test_error_code', - 'errorMessage': 'test_error_message', - 'path': '/example/path', - }; + test('retrieveLostData get both path and error should throw', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return { + 'type': 'video', + 'errorCode': 'test_error_code', + 'errorMessage': 'test_error_message', + 'path': '/example/path', + }; + }); + expect(picker.getLostData(), throwsAssertionError); }); - expect(picker.getLostData(), throwsAssertionError); }); }); - }); - group('$ImagePicker', () { - const MethodChannel channel = - MethodChannel('plugins.flutter.io/image_picker'); - - final List log = []; - - final picker = ImagePicker(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - return []; + group('Multi images', () { + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return []; + }); + log.clear(); }); - log.clear(); - }); - - test('ImagePicker platform instance overrides the actual platform used', - () { - final ImagePickerPlatform savedPlatform = ImagePickerPlatform.instance; - final MockPlatform mockPlatform = MockPlatform(); - ImagePickerPlatform.instance = mockPlatform; - expect(ImagePicker.platform, mockPlatform); - ImagePickerPlatform.instance = savedPlatform; - }); - - group('#pickMultiImage', () { - test('passes the width and height arguments correctly', () async { - 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, - [ - isMethodCall('pickMultiImage', arguments: { - 'maxWidth': null, - 'maxHeight': null, - 'imageQuality': null, - }), - isMethodCall('pickMultiImage', arguments: { - 'maxWidth': 10.0, - 'maxHeight': null, - 'imageQuality': null, - }), - isMethodCall('pickMultiImage', arguments: { - 'maxWidth': null, - 'maxHeight': 10.0, - 'imageQuality': null, - }), - isMethodCall('pickMultiImage', arguments: { - 'maxWidth': 10.0, - 'maxHeight': 20.0, - 'imageQuality': null, - }), - isMethodCall('pickMultiImage', arguments: { - 'maxWidth': 10.0, - 'maxHeight': null, - 'imageQuality': 70, - }), - isMethodCall('pickMultiImage', arguments: { - 'maxWidth': null, - 'maxHeight': 10.0, - 'imageQuality': 70, - }), - isMethodCall('pickMultiImage', arguments: { - 'maxWidth': 10.0, - 'maxHeight': 20.0, - 'imageQuality': 70, - }), - ], - ); - }); + group('#pickMultiImage', () { + test('passes the width and height arguments correctly', () async { + 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, + [ + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': null, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': null, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': null, + 'imageQuality': 70, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': null, + 'maxHeight': 10.0, + 'imageQuality': 70, + }), + isMethodCall('pickMultiImage', arguments: { + 'maxWidth': 10.0, + 'maxHeight': 20.0, + 'imageQuality': 70, + }), + ], + ); + }); - test('does not accept a negative width or height argument', () { - expect( - picker.getMultiImage(maxWidth: -1.0), - throwsArgumentError, - ); + test('does not accept a negative width or height argument', () { + expect( + picker.getMultiImage(maxWidth: -1.0), + throwsArgumentError, + ); - expect( - picker.getMultiImage(maxHeight: -1.0), - throwsArgumentError, - ); - }); + expect( + picker.getMultiImage(maxHeight: -1.0), + throwsArgumentError, + ); + }); - test('handles a null image path response gracefully', () async { - channel.setMockMethodCallHandler((MethodCall methodCall) => null); + test('handles a null image path response gracefully', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) => null); - expect(await picker.getMultiImage(), isNull); - expect(await picker.getMultiImage(), isNull); + expect(await picker.getMultiImage(), isNull); + expect(await picker.getMultiImage(), isNull); + }); }); }); }); From 2382d9e4fc4c7858aa3fff33698ec78c55b99755 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 27 May 2021 14:51:38 +0200 Subject: [PATCH 25/79] Add additional parameters to handlePath method --- .../ios/Classes/FLTImagePickerPlugin.m | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 1a2ad962f062..fe55fed0c4e6 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -410,25 +410,32 @@ - (void)picker:(PHPickerViewController *)picker maxHeight:maxHeight imageQuality:desiredImageQuality]; [pathList addObject:savedPath]; - if (pathList.count == results.count) { - [self handlePath:savedPath listCount:pathList]; - } + [self handlePath:savedPath + pathList:pathList + resultCount:results.count + listCount:pathList.count]; }]; } - if (pathList.count == results.count) { - [self handlePath:savedPath listCount:pathList]; - } + [self handlePath:savedPath + pathList:pathList + resultCount:results.count + listCount:pathList.count]; }); } }]; } } -- (void)handlePath:(NSString *)savedPath listCount:(NSMutableArray *)pathList { - if (self.single) { - [self handleSavedPath:savedPath]; - } else { - [self handleMultiSavedPaths:pathList]; +- (void)handlePath:(NSString *)savedPath + pathList:(NSMutableArray *)pathList + resultCount:(NSUInteger)resultCount + listCount:(NSUInteger)listCount { + if (listCount == resultCount) { + if (self.single) { + [self handleSavedPath:savedPath]; + } else { + [self handleMultiSavedPaths:pathList]; + } } } From 0e910aca8876e0374d535d716e97f3e24e6e9d95 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 27 May 2021 14:51:58 +0200 Subject: [PATCH 26/79] Fix the comment --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index fe55fed0c4e6..74daec8ab068 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -388,7 +388,7 @@ - (void)picker:(PHPickerViewController *)picker } __block NSString *savedPath; if (!originalAsset) { - // Image picked without an original asset (e.g. User took a photo directly) + // Image picked without an original asset (e.g. User pick image without permission) savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil image:localImage From ca3be77c8e0c715d6793b7bdc157a0f812113b49 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 31 May 2021 18:20:05 +0200 Subject: [PATCH 27/79] Refactor picker to use semaphore --- .../ios/Classes/FLTImagePickerPlugin.m | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 74daec8ab068..99cf1d0c328e 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -375,6 +375,7 @@ - (void)picker:(PHPickerViewController *)picker completionHandler:^(__kindof id _Nullable image, NSError *_Nullable error) { if ([image isKindOfClass:[UIImage class]]) { + dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); __block UIImage *localImage = image; dispatch_async(dispatch_get_main_queue(), ^{ PHAsset *originalAsset = @@ -395,6 +396,8 @@ - (void)picker:(PHPickerViewController *)picker imageQuality:desiredImageQuality]; [pathList addObject:savedPath]; + dispatch_semaphore_signal(resultSemaphore); + } else { [[PHImageManager defaultManager] requestImageDataForAsset:originalAsset @@ -410,17 +413,14 @@ - (void)picker:(PHPickerViewController *)picker maxHeight:maxHeight imageQuality:desiredImageQuality]; [pathList addObject:savedPath]; - [self handlePath:savedPath - pathList:pathList - resultCount:results.count - listCount:pathList.count]; + + dispatch_semaphore_signal(resultSemaphore); }]; } - [self handlePath:savedPath - pathList:pathList - resultCount:results.count - listCount:pathList.count]; }); + dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); + + [self handlePath:pathList resultCount:results.count]; } }]; } From b69234aba96dea7271f4eb9af6ab3f23d744f377 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 31 May 2021 18:22:24 +0200 Subject: [PATCH 28/79] Add declaration comment for handlePath method --- .../ios/Classes/FLTImagePickerPlugin.m | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 99cf1d0c328e..48439d8e0eaf 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -426,13 +426,17 @@ - (void)picker:(PHPickerViewController *)picker } } -- (void)handlePath:(NSString *)savedPath - pathList:(NSMutableArray *)pathList - resultCount:(NSUInteger)resultCount - listCount:(NSUInteger)listCount { - if (listCount == resultCount) { +/** + * Accepts NSMutableArray to get list of paths that are saved and NSUInteger + * as a total length of the results. + * + * Calls handleSavedPath if method call is equal to pickImage or handleMultiSavedPaths + * if method call is equal to pickMultiImage. + */ +- (void)handlePath:(NSMutableArray *)pathList resultCount:(NSUInteger)resultCount { + if (pathList.count == resultCount) { if (self.single) { - [self handleSavedPath:savedPath]; + [self handleSavedPath:pathList.firstObject]; } else { [self handleMultiSavedPaths:pathList]; } From d2be72d5323c75c72913f6c09b216dc419336d37 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 31 May 2021 18:23:05 +0200 Subject: [PATCH 29/79] Add declaration comment for handleMultiSavedPaths --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 48439d8e0eaf..b476d4cd0e2d 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -566,11 +566,17 @@ - (void)handleSavedPath:(NSString *)path { _arguments = nil; } +/** + * Accepts NSMutableArray as an argument and call result with the array + * if the array is not nil and length of the array is bigger than zero. + * + * Otherwise result is called with FlutterError. + */ - (void)handleMultiSavedPaths:(NSMutableArray *)pathList { if (!self.result) { return; } - if (pathList.count > 0) { + if (pathList && pathList.count > 0) { self.result(pathList); } else { self.result([FlutterError errorWithCode:@"create_error" From 9c96f252f8300ead32689acfc6b3c1b782a3cbe7 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 31 May 2021 18:23:59 +0200 Subject: [PATCH 30/79] Add testPluginMultiImagePathIsNull unit test --- .../ios/Tests/ImagePickerPluginTests.m | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index 04ba4b98e241..c3dc7c47b890 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -23,6 +23,7 @@ - (UIViewController *)presentedViewController { @interface FLTImagePickerPlugin (Test) @property(copy, nonatomic) FlutterResult result; - (void)handleSavedPath:(NSString *)path; +- (void)handleMultiSavedPaths:(NSMutableArray *)pathList; - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; @end @@ -149,4 +150,20 @@ - (void)testViewController { XCTAssertEqual([plugin viewControllerWithWindow:window], vc2); } +- (void)testPluginMultiImagePathIsNull { + 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 handleMultiSavedPaths:nil]; + + dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(pickImageResult.code, @"create_error"); +} @end From 3a0cb11fe0dddb3b45dec45f16e2daefcd8dbf25 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 31 May 2021 18:24:28 +0200 Subject: [PATCH 31/79] Add testPluginMultiImagePathHasZeroItem unit test --- .../ios/Tests/ImagePickerPluginTests.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index c3dc7c47b890..d72001cb53fc 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -166,4 +166,22 @@ - (void)testPluginMultiImagePathIsNull { XCTAssertEqual(pickImageResult.code, @"create_error"); } + +- (void)testPluginMultiImagePathHasZeroItem { + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + NSMutableArray *pathList = [NSMutableArray 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 handleMultiSavedPaths:pathList]; + + dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(pickImageResult.code, @"create_error"); +} @end From d39e39c36d722a118a9f45ddb3f375332d146e33 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 31 May 2021 18:24:51 +0200 Subject: [PATCH 32/79] Add testPluginMultiImagePathHasItem unit test --- .../ios/Tests/ImagePickerPluginTests.m | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index d72001cb53fc..4597f114cf17 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -184,4 +184,26 @@ - (void)testPluginMultiImagePathHasZeroItem { XCTAssertEqual(pickImageResult.code, @"create_error"); } + +- (void)testPluginMultiImagePathHasItem { + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + NSString *savedPath = @"test"; + NSMutableArray *pathList = [NSMutableArray new]; + + [pathList addObject:savedPath]; + + dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); + __block id pickImageResult = nil; + + plugin.result = ^(id _Nullable r) { + pickImageResult = r; + dispatch_semaphore_signal(resultSemaphore); + }; + [plugin handleMultiSavedPaths:pathList]; + + dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); + + XCTAssertEqual(pickImageResult, pathList); +} + @end From 527491d0f1eaa170a88193c152690b4ce0b3cf4b Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 31 May 2021 18:43:11 +0200 Subject: [PATCH 33/79] Update CHANGELOG file and version --- packages/image_picker/image_picker/CHANGELOG.md | 3 +++ packages/image_picker/image_picker/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index d6ad4eddae93..424d30e00925 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.7.6 +* Implement functionality to pick multiple images in iOS14+ and Android. + ## 0.7.5+3 * Localize `UIAlertController` strings. diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index d19048c4f3c2..0f43887a95f5 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker -version: 0.7.5+3 +version: 0.7.6 flutter: plugin: From dc40ea326a782859c11c2c2f8e1407dba70e3fe5 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Wed, 2 Jun 2021 09:41:42 +0200 Subject: [PATCH 34/79] Update code format --- .../io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 52c1c02bb92c..f53a47efdd1f 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -112,6 +112,7 @@ public void chooseMultiImageFromGallery_WhenPendingResultExists_FinishesWithAlre verifyFinishedWithAlreadyActiveError(); verifyNoMoreInteractions(mockResult); } + public void chooseMultiImageFromGallery_WhenHasNoExternalStoragePermission_RequestsForPermission() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) From ae3f811943c78c86858a451e89cf3ee291b2d38e Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 3 Jun 2021 13:56:03 +0200 Subject: [PATCH 35/79] Remove the unnecessary test --- .../imagepicker/ImagePickerDelegateTest.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index c90effb1d7e6..fa2e386d0a9a 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -120,21 +120,6 @@ public void chooseImageFromGallery_LaunchesChooseFromGalleryIntent() { any(Intent.class), eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY)); } - @Test - public void - chooseMultiImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { - when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) - .thenReturn(true); - - ImagePickerDelegate delegate = createDelegate(); - delegate.chooseMultiImageFromGallery(mockMethodCall, mockResult); - - verify(mockActivity) - .startActivityForResult( - any(Intent.class), - eq(ImagePickerDelegate.REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY)); - } - @Test public void takeImageWithCamera_WhenPendingResultExists_FinishesWithAlreadyActiveError() { ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); From 3b07dc25206c756af9d9ff49925467384db65cc0 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 3 Jun 2021 14:30:26 +0200 Subject: [PATCH 36/79] Update the comments --- .../ios/Classes/FLTImagePickerPlugin.m | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 9233064b54ea..95377b163d00 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -427,11 +427,16 @@ - (void)picker:(PHPickerViewController *)picker } /** - * Accepts NSMutableArray to get list of paths that are saved and NSUInteger - * as a total length of the results. + * Calls the correct method * - * Calls handleSavedPath if method call is equal to pickImage or handleMultiSavedPaths - * if method call is equal to pickMultiImage. + * If the @c pathList count and the @c resultCount are equal then checks + * single which is true if the pickImage method call is invoked and false if the + * pickMultiImage method call is invoked. If single is true then calls handleSavedPath + * with the firstObject of the @c pathList. If single is false then calls + * handleMultiSavedPaths with the @c pathList. + * + * @param @pathList that is used to get its count. + * @param @resultCount that is used to compared with @c pathList. */ - (void)handlePath:(NSMutableArray *)pathList resultCount:(NSUInteger)resultCount { if (pathList.count == resultCount) { @@ -567,10 +572,14 @@ - (void)handleSavedPath:(NSString *)path { } /** - * Accepts NSMutableArray as an argument and call result with the array - * if the array is not nil and length of the array is bigger than zero. + * Applies NSMutableArray on the FLutterResult. + * + * If the @c pathList is not nill and count of the @c pathList is + * greater than 0 then call FlutterResult with the @c pathList. If + * the @c pathList is nill or count of the @c pathList is not + * greater than 0 then call FlutterResult with FlutterError. * - * Otherwise result is called with FlutterError. + * @param @pathList that should be applied to FlutterResult. */ - (void)handleMultiSavedPaths:(NSMutableArray *)pathList { if (!self.result) { From 6b657f7e15601acd2d660f87f699f5a68b9211cb Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 3 Jun 2021 14:46:29 +0200 Subject: [PATCH 37/79] Add buildMethodCall that has only method parameter --- .../io/flutter/plugins/imagepicker/ImagePickerPluginTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java index 170b3ca9594c..2fa45cd7e134 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java @@ -182,4 +182,8 @@ private MethodCall buildMethodCall(String method, final int source) { return new MethodCall(method, arguments); } + + private MethodCall buildMethodCall(String method) { + return new MethodCall(method, null); + } } From 55953921377f7298013d5c13b11f92eb644c4d2a Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 3 Jun 2021 14:47:29 +0200 Subject: [PATCH 38/79] Refactor the test --- .../io/flutter/plugins/imagepicker/ImagePickerPluginTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java index 2fa45cd7e134..422b8be74f7c 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java @@ -95,7 +95,7 @@ public void onMethodCall_WhenSourceIsGallery_InvokesChooseImageFromGallery() { @Test public void onMethodCall_InvokesChooseMultiImageFromGallery() { - MethodCall call = buildMethodCall(PICK_MULTI_IMAGE, null); + MethodCall call = buildMethodCall(PICK_MULTI_IMAGE); plugin.onMethodCall(call, mockResult); verify(mockImagePickerDelegate).chooseMultiImageFromGallery(eq(call), any()); verifyZeroInteractions(mockResult); From 6c82b1365995de076a0ed8570df2595e4e3c65bd Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 3 Jun 2021 17:13:49 +0200 Subject: [PATCH 39/79] Fix the test name --- .../image_picker/ios/Tests/ImagePickerPluginTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index 4597f114cf17..2de65cfddeb5 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -150,7 +150,7 @@ - (void)testViewController { XCTAssertEqual([plugin viewControllerWithWindow:window], vc2); } -- (void)testPluginMultiImagePathIsNull { +- (void)testPluginMultiImagePathIsNil { FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); From 6c6ca76f01d63469df2d7274bcd5f4d48f586dcc Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 4 Jun 2021 14:28:05 +0200 Subject: [PATCH 40/79] Update the docs --- packages/image_picker/image_picker/CHANGELOG.md | 1 + packages/image_picker/image_picker/README.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 795ac32acaa5..0a91cc746669 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,4 +1,5 @@ ## 0.8.1 + * Implement functionality to pick multiple images in iOS14+ and Android. ## 0.8.0+1 diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index dd75100300a2..410538cb2647 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -11,8 +11,8 @@ First, add `image_picker` as a [dependency in your pubspec.yaml file](https://fl ### iOS -Starting with version **0.7.5** the iOS implementation uses PHPicker to pick (multiple) images on iOS14 or higher. The multiple images implementation does NOT support iOS versions lower than 14. -As a result of implementing PHPicker it becomes impossible to pick HEIC images on the iOS simulator in iOS14+. This is a known issue. Please test this on a real device, or test with non-HEIC images until Apple solves this issue. +Starting with version **0.8.1** the iOS implementation uses PHPicker to pick (multiple) images on iOS 14 or higher. +As a result of implementing PHPicker it becomes impossible to pick HEIC images on the iOS simulator in iOS 14+. This is a known issue. Please test this on a real device, or test with non-HEIC images until Apple solves this issue.[63426347 - Apple known issue](https://www.google.com/search?q=63426347+apple&sxsrf=ALeKk01YnTMid5S0PYvhL8GbgXJ40ZS[…]t=gws-wiz&ved=0ahUKEwjKh8XH_5HwAhWL_rsIHUmHDN8Q4dUDCA8&uact=5) Add the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: @@ -22,6 +22,8 @@ Add the following keys to your _Info.plist_ file, located in `/ios ### Android +Starting with version **0.8.1** the Android implementation support to pick (multiple) images on Android 4.3 or higher. + No configuration required - the plugin should work out of the box. It is no longer required to add `android:requestLegacyExternalStorage="true"` as an attribute to the `` tag in AndroidManifest.xml, as `image_picker` has been updated to make use of scoped storage. From aeac5357f2cc5474b3ece79e897899730172a57c Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 4 Jun 2021 16:58:53 +0200 Subject: [PATCH 41/79] Update the handleChooseMultiImageResult method --- .../io/flutter/plugins/imagepicker/ImagePickerDelegate.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 2738ef1228ea..1227cbd8db5b 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -395,14 +395,12 @@ private void handleChooseImageResult(int resultCode, Intent data) { private void handleChooseMultiImageResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) { - ArrayList paths; + ArrayList paths = new ArrayList<>(); if (data.getClipData() != null) { - paths = new ArrayList<>(); for (int i = 0; i < data.getClipData().getItemCount(); i++) { paths.add(fileUtils.getPathFromUri(activity, data.getClipData().getItemAt(i).getUri())); } } else { - paths = new ArrayList<>(); paths.add(fileUtils.getPathFromUri(activity, data.getData())); } handleMultiImageResult(paths, false); From 02a1d075356129b5b8e2c6cafc8a0d26c203e9b9 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 4 Jun 2021 16:59:11 +0200 Subject: [PATCH 42/79] Update the method name --- .../io/flutter/plugins/imagepicker/ImagePickerDelegate.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 1227cbd8db5b..ef33094aff2d 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -464,7 +464,7 @@ private void handleMultiImageResult( ArrayList paths, boolean shouldDeleteOriginalIfScaled) { if (methodCall != null) { for (int i = 0; i < paths.size(); i++) { - String finalImagePath = resizeImage(paths.get(i)); + String finalImagePath = getResizedImagePath(paths.get(i)); //delete original file if scaled if (finalImagePath != null @@ -480,7 +480,7 @@ private void handleMultiImageResult( private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) { if (methodCall != null) { - String finalImagePath = resizeImage(path); + String finalImagePath = getResizedImagePath(path); //delete original file if scaled if (finalImagePath != null && !finalImagePath.equals(path) && shouldDeleteOriginalIfScaled) { new File(path).delete(); @@ -491,7 +491,7 @@ private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled } } - private String resizeImage(String path) { + private String getResizedImagePath(String path) { Double maxWidth = methodCall.argument("maxWidth"); Double maxHeight = methodCall.argument("maxHeight"); Integer imageQuality = methodCall.argument("imageQuality"); From 7f147dcd2b55fdb38a79ed446c644b036384e587 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 4 Jun 2021 17:00:06 +0200 Subject: [PATCH 43/79] Update the parameter name --- .../plugins/imagepicker/ImagePickerDelegate.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index ef33094aff2d..589ce73d12fe 100644 --- a/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -393,15 +393,15 @@ private void handleChooseImageResult(int resultCode, Intent data) { finishWithSuccess(null); } - private void handleChooseMultiImageResult(int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && data != null) { + private void handleChooseMultiImageResult(int resultCode, Intent intent) { + if (resultCode == Activity.RESULT_OK && intent != null) { ArrayList paths = new ArrayList<>(); - if (data.getClipData() != null) { - for (int i = 0; i < data.getClipData().getItemCount(); i++) { - paths.add(fileUtils.getPathFromUri(activity, data.getClipData().getItemAt(i).getUri())); + if (intent.getClipData() != null) { + for (int i = 0; i < intent.getClipData().getItemCount(); i++) { + paths.add(fileUtils.getPathFromUri(activity, intent.getClipData().getItemAt(i).getUri())); } } else { - paths.add(fileUtils.getPathFromUri(activity, data.getData())); + paths.add(fileUtils.getPathFromUri(activity, intent.getData())); } handleMultiImageResult(paths, false); return; From a1ec45a595b40cb97beacba2cb9d92b530446c13 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 4 Jun 2021 17:01:04 +0200 Subject: [PATCH 44/79] Update the property name and type --- .../ios/Classes/FLTImagePickerPlugin.m | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 95377b163d00..d0cd37b09c09 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -21,7 +21,7 @@ @interface FLTImagePickerPlugin () Date: Fri, 4 Jun 2021 17:01:35 +0200 Subject: [PATCH 45/79] Update the method comment --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index d0cd37b09c09..6a35718c211f 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -425,13 +425,7 @@ - (void)picker:(PHPickerViewController *)picker } /** - * Calls the correct method - * - * If the @c pathList count and the @c resultCount are equal then checks - * single which is true if the pickImage method call is invoked and false if the - * pickMultiImage method call is invoked. If single is true then calls handleSavedPath - * with the firstObject of the @c pathList. If single is false then calls - * handleMultiSavedPaths with the @c pathList. + * Handle the result based on the count of pathList. * * @param @pathList that is used to get its count. * @param @resultCount that is used to compared with @c pathList. From a598d789863d3ec3ce6819d69280a3aa796c08cf Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 4 Jun 2021 17:01:55 +0200 Subject: [PATCH 46/79] Update the method comments --- packages/image_picker/image_picker/lib/image_picker.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index df67a08b8a32..f4dee93ee1d6 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -54,6 +54,8 @@ class ImagePicker { /// /// In Android, the MainActivity can be destroyed for various reasons. If that happens, the result will be lost /// in this call. You can then call [getLostData] when your app relaunches to retrieve the lost data. + /// + /// See also [getMultiImage] to allow users to select multiple images at once. Future getImage({ required ImageSource source, double? maxWidth, @@ -87,6 +89,8 @@ class ImagePicker { /// the original quality will be returned. Compression is only supported for certain /// 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. + /// + /// See also [getImage] to allow users to only pick a single image. Future?> getMultiImage({ double? maxWidth, double? maxHeight, From e99655479d645ca28f98d314ff17322bb7cf2b55 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 7 Jun 2021 16:24:56 +0200 Subject: [PATCH 47/79] Fix picker to use semaphore better --- .../ios/Classes/FLTImagePickerPlugin.m | 104 +++++++++--------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 6a35718c211f..7b64479e941e 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -360,68 +360,76 @@ - (NSNumber *)getDesiredImageQuality:(NSNumber *)imageQuality { - (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray *)results API_AVAILABLE(ios(14)) { [picker dismissViewControllerAnimated:YES completion:nil]; - + if (results.count == 0) { + self.result(nil); + self.result = nil; + _arguments = nil; + return; + } NSNumber *maxWidth = [_arguments objectForKey:@"maxWidth"]; NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; NSNumber *imageQuality = [_arguments objectForKey:@"imageQuality"]; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; NSMutableArray *pathList = [NSMutableArray new]; - + __block NSUInteger resultsInProgress = results.count; + __block dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); for (PHPickerResult *result in results) { [result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id _Nullable image, NSError *_Nullable error) { if ([image isKindOfClass:[UIImage class]]) { - dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); __block UIImage *localImage = image; - dispatch_async(dispatch_get_main_queue(), ^{ - PHAsset *originalAsset = - [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:result]; + PHAsset *originalAsset = + [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:result]; + + if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { + localImage = [FLTImagePickerImageUtil scaledImage:localImage + maxWidth:maxWidth + maxHeight:maxHeight + isMetadataAvailable:originalAsset != nil]; + } + __block NSString *savedPath; + if (!originalAsset) { + // Image picked without an original asset (e.g. User pick image without permission) + savedPath = + [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil + image:localImage + imageQuality:desiredImageQuality]; + [pathList addObject:savedPath]; + if (--resultsInProgress == 0) { + dispatch_semaphore_signal(resultSemaphore); + } - if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - localImage = [FLTImagePickerImageUtil scaledImage:localImage + } else { + [[PHImageManager defaultManager] + requestImageDataForAsset:originalAsset + options:nil + resultHandler:^( + NSData *_Nullable imageData, NSString *_Nullable dataUTI, + UIImageOrientation orientation, NSDictionary *_Nullable info) { + // maxWidth and maxHeight are used only for GIF images. + savedPath = [FLTImagePickerPhotoAssetUtil + saveImageWithOriginalImageData:imageData + image:localImage maxWidth:maxWidth maxHeight:maxHeight - isMetadataAvailable:originalAsset != nil]; - } - __block NSString *savedPath; - if (!originalAsset) { - // Image picked without an original asset (e.g. User pick image without permission) - savedPath = - [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil - image:localImage - imageQuality:desiredImageQuality]; - [pathList addObject:savedPath]; - - dispatch_semaphore_signal(resultSemaphore); - - } else { - [[PHImageManager defaultManager] - requestImageDataForAsset:originalAsset - options:nil - resultHandler:^( - NSData *_Nullable imageData, NSString *_Nullable dataUTI, - UIImageOrientation orientation, NSDictionary *_Nullable info) { - // maxWidth and maxHeight are used only for GIF images. - savedPath = [FLTImagePickerPhotoAssetUtil - saveImageWithOriginalImageData:imageData - image:localImage - maxWidth:maxWidth - maxHeight:maxHeight - imageQuality:desiredImageQuality]; - [pathList addObject:savedPath]; + imageQuality:desiredImageQuality]; + [pathList addObject:savedPath]; + if (--resultsInProgress == 0) { dispatch_semaphore_signal(resultSemaphore); - }]; - } - }); - dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); - - [self handlePath:pathList resultCount:results.count]; + } + }]; + } } }]; } + while (dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_NOW)) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]]; + } + [self handlePath:pathList]; } /** @@ -430,13 +438,11 @@ - (void)picker:(PHPickerViewController *)picker * @param @pathList that is used to get its count. * @param @resultCount that is used to compared with @c pathList. */ -- (void)handlePath:(NSMutableArray *)pathList resultCount:(NSUInteger)resultCount { - if (pathList.count == resultCount) { - if (self.maxImagesAllowed) { - [self handleSavedPath:pathList.firstObject]; - } else { - [self handleMultiSavedPaths:pathList]; - } +- (void)handlePath:(NSMutableArray *)pathList { + if (self.maxImagesAllowed) { + [self handleSavedPath:pathList.firstObject]; + } else { + [self handleMultiSavedPaths:pathList]; } } From 3b8c1dd8c773ea2090be86d1fea0bc67f43bfeb5 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Mon, 7 Jun 2021 17:02:21 +0200 Subject: [PATCH 48/79] Make sure the order of pathList is the same as the order of results --- .../ios/Classes/FLTImagePickerPlugin.m | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 7b64479e941e..1b8eb463d3b8 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -370,10 +370,16 @@ - (void)picker:(PHPickerViewController *)picker NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; NSNumber *imageQuality = [_arguments objectForKey:@"imageQuality"]; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; - NSMutableArray *pathList = [NSMutableArray new]; + NSMutableArray *pathList = [[NSMutableArray alloc] initWithCapacity:results.count]; + for (int i = 0; i < results.count; [pathList addObject:[NSNull null]], i++) + ; + __block NSUInteger resultsInProgress = results.count; __block dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); - for (PHPickerResult *result in results) { + + for (int i = 0; i < results.count; i++) { + PHPickerResult *result = results[i]; + [result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id _Nullable image, @@ -396,7 +402,7 @@ - (void)picker:(PHPickerViewController *)picker [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil image:localImage imageQuality:desiredImageQuality]; - [pathList addObject:savedPath]; + pathList[i] = savedPath; if (--resultsInProgress == 0) { dispatch_semaphore_signal(resultSemaphore); } @@ -415,7 +421,7 @@ - (void)picker:(PHPickerViewController *)picker maxWidth:maxWidth maxHeight:maxHeight imageQuality:desiredImageQuality]; - [pathList addObject:savedPath]; + pathList[i] = savedPath; if (--resultsInProgress == 0) { dispatch_semaphore_signal(resultSemaphore); From 7c09ae6c6e2b938ff95b946d4f9437936584cb28 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 8 Jun 2021 09:18:53 +0200 Subject: [PATCH 49/79] Separate creation of array in function --- .../ios/Classes/FLTImagePickerPlugin.m | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 1b8eb463d3b8..a1241713e8db 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -370,10 +370,8 @@ - (void)picker:(PHPickerViewController *)picker NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; NSNumber *imageQuality = [_arguments objectForKey:@"imageQuality"]; NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; - NSMutableArray *pathList = [[NSMutableArray alloc] initWithCapacity:results.count]; - for (int i = 0; i < results.count; [pathList addObject:[NSNull null]], i++) - ; + __block NSMutableArray *pathList = [self createNSMutableArrayWithSize:results.count]; __block NSUInteger resultsInProgress = results.count; __block dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); @@ -438,6 +436,22 @@ - (void)picker:(PHPickerViewController *)picker [self handlePath:pathList]; } +/** + * Creates an NSMutableArray of a certain size filled with NSNull objects. + * + * The difference with initWithCapacity is that initWithCapacity still gives an empty array making + * it impossible to add objects on an index larger than the size. + * + * @param @size The length of the required array + * @return @NSMutableArray An array of a specified size + */ +- (NSMutableArray *)createNSMutableArrayWithSize:(NSUInteger)size { + NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:size]; + for (int i = 0; i < size; [mutableArray addObject:[NSNull null]], i++) + ; + return mutableArray; +} + /** * Handle the result based on the count of pathList. * From ed7ef7e60b058d987c26925ac636a2a3f96e61a9 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 8 Jun 2021 10:52:44 +0200 Subject: [PATCH 50/79] Fix unit tests --- .../example/ios/RunnerTests/ImagePickerPluginTests.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index 2de65cfddeb5..f9f0d0bf6ecf 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -164,7 +164,7 @@ - (void)testPluginMultiImagePathIsNil { dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); - XCTAssertEqual(pickImageResult.code, @"create_error"); + XCTAssertEqualObjects(pickImageResult.code, @"create_error"); } - (void)testPluginMultiImagePathHasZeroItem { @@ -182,7 +182,7 @@ - (void)testPluginMultiImagePathHasZeroItem { dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); - XCTAssertEqual(pickImageResult.code, @"create_error"); + XCTAssertEqualObjects(pickImageResult.code, @"create_error"); } - (void)testPluginMultiImagePathHasItem { From a2621e6012512cc018677a97996f58b815572fe7 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 8 Jun 2021 10:53:31 +0200 Subject: [PATCH 51/79] Update method comment --- .../image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 9eaad30a5cd9..091b5d431e26 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -456,7 +456,6 @@ - (NSMutableArray *)createNSMutableArrayWithSize:(NSUInteger)size { * Handle the result based on the count of pathList. * * @param @pathList that is used to get its count. - * @param @resultCount that is used to compared with @c pathList. */ - (void)handlePath:(NSMutableArray *)pathList { if (self.maxImagesAllowed) { From 25d9a46d4c5da7adf3338b35fa4973d275db6e2f Mon Sep 17 00:00:00 2001 From: yusufdag Date: Wed, 9 Jun 2021 09:41:24 +0200 Subject: [PATCH 52/79] Fix format --- .../io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 58d85febf17e..f8be66833b17 100644 --- a/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -112,6 +112,7 @@ public void chooseMultiImageFromGallery_WhenPendingResultExists_FinishesWithAlre verifyFinishedWithAlreadyActiveError(); verifyNoMoreInteractions(mockResult); } + public void chooseImageFromGallery_WhenHasExternalStoragePermission_LaunchesChooseFromGalleryIntent() { when(mockPermissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) From 6f4e7c7b9661391ab8a68a0458f0ea9ceff8d5e7 Mon Sep 17 00:00:00 2001 From: Yusuf <82844127+ydag@users.noreply.github.com> Date: Mon, 14 Jun 2021 09:38:00 +0200 Subject: [PATCH 53/79] Update CHANGELOG.md Co-authored-by: Chris Yang --- packages/image_picker/image_picker/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 3df92b471a4e..6f9aab2c6de3 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.8.1 -* Implement functionality to pick multiple images in iOS14+ and Android. +* Add a new method `getMultiImage` to allow picking multiple images on iOS 14 or higher and Android 4.3 or higher. +* Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, see: https://github.com/... ## 0.8.0+3 From 33b74a05613a362522924e28357cffc736fc0021 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 14 Jun 2021 09:46:39 +0200 Subject: [PATCH 54/79] Fix to correct variable type --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 091b5d431e26..e83a7b608eea 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -69,7 +69,7 @@ - (UIViewController *)viewControllerWithWindow:(UIWindow *)window { return topController; } -- (void)pickImageWithPHPicker:(bool)maxImagesAllowed API_AVAILABLE(ios(14)) { +- (void)pickImageWithPHPicker:(int)maxImagesAllowed API_AVAILABLE(ios(14)) { PHPickerConfiguration *config = [[PHPickerConfiguration alloc] initWithPhotoLibrary:PHPhotoLibrary.sharedPhotoLibrary]; config.selectionLimit = maxImagesAllowed; // Setting to zero allow us to pick unlimited photos From 8c0c5cadefcb03d8b28698544a129c89e225ec9e Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 14 Jun 2021 11:00:15 +0200 Subject: [PATCH 55/79] Refactor to return List from all the places --- .../ios/Classes/FLTImagePickerPlugin.m | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index e83a7b608eea..daa57eb5e9dc 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -91,6 +91,8 @@ - (void)pickImageWithUIImagePicker { int imageSource = [[_arguments objectForKey:@"source"] intValue]; + self.maxImagesAllowed = 1; + switch (imageSource) { case SOURCE_CAMERA: { NSInteger cameraDevice = [[_arguments objectForKey:@"cameraDevice"] intValue]; @@ -401,9 +403,6 @@ - (void)picker:(PHPickerViewController *)picker image:localImage imageQuality:desiredImageQuality]; pathList[i] = savedPath; - if (--resultsInProgress == 0) { - dispatch_semaphore_signal(resultSemaphore); - } } else { [[PHImageManager defaultManager] @@ -420,12 +419,11 @@ - (void)picker:(PHPickerViewController *)picker maxHeight:maxHeight imageQuality:desiredImageQuality]; pathList[i] = savedPath; - - if (--resultsInProgress == 0) { - dispatch_semaphore_signal(resultSemaphore); - } }]; } + if (--resultsInProgress == 0) { + dispatch_semaphore_signal(resultSemaphore); + } } }]; } @@ -433,7 +431,7 @@ - (void)picker:(PHPickerViewController *)picker [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]]; } - [self handlePath:pathList]; + [self handleMultiSavedPaths:pathList]; } /** @@ -452,19 +450,6 @@ - (NSMutableArray *)createNSMutableArrayWithSize:(NSUInteger)size { return mutableArray; } -/** - * Handle the result based on the count of pathList. - * - * @param @pathList that is used to get its count. - */ -- (void)handlePath:(NSMutableArray *)pathList { - if (self.maxImagesAllowed) { - [self handleSavedPath:pathList.firstObject]; - } else { - [self handleMultiSavedPaths:pathList]; - } -} - - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL]; @@ -561,7 +546,10 @@ - (void)saveImageWithOriginalImageData:(NSData *)originalImageData maxWidth:maxWidth maxHeight:maxHeight imageQuality:imageQuality]; - [self handleSavedPath:savedPath]; + NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; + [mutableArray addObject:savedPath]; + + [self handleMultiSavedPaths:mutableArray]; } - (void)saveImageWithPickerInfo:(NSDictionary *)info @@ -570,22 +558,10 @@ - (void)saveImageWithPickerInfo:(NSDictionary *)info NSString *savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:info image:image imageQuality:imageQuality]; - [self handleSavedPath:savedPath]; -} + NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; + [mutableArray addObject:savedPath]; -- (void)handleSavedPath:(NSString *)path { - if (!self.result) { - return; - } - if (path) { - self.result(path); - } else { - self.result([FlutterError errorWithCode:@"create_error" - message:@"Temporary file could not be created" - details:nil]); - } - self.result = nil; - _arguments = nil; + [self handleMultiSavedPaths:mutableArray]; } /** @@ -602,11 +578,13 @@ - (void)handleMultiSavedPaths:(NSMutableArray *)pathList { if (!self.result) { return; } - if (pathList && pathList.count > 0) { + if (pathList && self.maxImagesAllowed) { + self.result(pathList.firstObject); + } else if (pathList.count > 0) { self.result(pathList); } else { self.result([FlutterError errorWithCode:@"create_error" - message:@"Temporary files could not be created" + message:@"Temporary file(s) could not be created" details:nil]); } self.result = nil; From 152bd33df1cabaf6adfaed0d4fc7ad8cc6c553ac Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 14 Jun 2021 11:01:23 +0200 Subject: [PATCH 56/79] Remove unnecessary test --- .../ios/RunnerTests/ImagePickerPluginTests.m | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index f9f0d0bf6ecf..568e3524cfbe 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -22,7 +22,6 @@ - (UIViewController *)presentedViewController { @interface FLTImagePickerPlugin (Test) @property(copy, nonatomic) FlutterResult result; -- (void)handleSavedPath:(NSString *)path; - (void)handleMultiSavedPaths:(NSMutableArray *)pathList; - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; @end @@ -123,21 +122,6 @@ - (void)testPickingVideoWithDuration { XCTAssertEqual([plugin getImagePickerController].videoMaximumDuration, 95); } -- (void)testPluginPickImageSelectMultipleTimes { - FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; - FlutterMethodCall *call = - [FlutterMethodCall methodCallWithMethodName:@"pickImage" - arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; - [plugin handleMethodCall:call - result:^(id _Nullable r){ - }]; - plugin.result = ^(id result) { - - }; - [plugin handleSavedPath:@"test"]; - [plugin handleSavedPath:@"test"]; -} - - (void)testViewController { UIWindow *window = [UIWindow new]; MockViewController *vc1 = [MockViewController new]; From f3d24414d2c5c23777ddbbae7b6a04f3f846126a Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 14 Jun 2021 14:49:55 +0200 Subject: [PATCH 57/79] Fix the if statement for maxImagesAllowed --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index daa57eb5e9dc..f2003e832483 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -578,7 +578,7 @@ - (void)handleMultiSavedPaths:(NSMutableArray *)pathList { if (!self.result) { return; } - if (pathList && self.maxImagesAllowed) { + if (pathList && (self.maxImagesAllowed == 1)) { self.result(pathList.firstObject); } else if (pathList.count > 0) { self.result(pathList); From 44c06f127df000c13bed1e6ea27d4a65f085031a Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 14 Jun 2021 14:51:52 +0200 Subject: [PATCH 58/79] Update the method comment and name --- .../ios/RunnerTests/ImagePickerPluginTests.m | 8 ++++---- .../ios/Classes/FLTImagePickerPlugin.m | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index 568e3524cfbe..b2a4373ed15e 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -22,7 +22,7 @@ - (UIViewController *)presentedViewController { @interface FLTImagePickerPlugin (Test) @property(copy, nonatomic) FlutterResult result; -- (void)handleMultiSavedPaths:(NSMutableArray *)pathList; +- (void)handleSavedPathList:(NSMutableArray *)pathList; - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker; @end @@ -144,7 +144,7 @@ - (void)testPluginMultiImagePathIsNil { pickImageResult = r; dispatch_semaphore_signal(resultSemaphore); }; - [plugin handleMultiSavedPaths:nil]; + [plugin handleSavedPathList:nil]; dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); @@ -162,7 +162,7 @@ - (void)testPluginMultiImagePathHasZeroItem { pickImageResult = r; dispatch_semaphore_signal(resultSemaphore); }; - [plugin handleMultiSavedPaths:pathList]; + [plugin handleSavedPathList:pathList]; dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); @@ -183,7 +183,7 @@ - (void)testPluginMultiImagePathHasItem { pickImageResult = r; dispatch_semaphore_signal(resultSemaphore); }; - [plugin handleMultiSavedPaths:pathList]; + [plugin handleSavedPathList:pathList]; dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_FOREVER); diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index f2003e832483..02e69fc04ac9 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -431,7 +431,7 @@ - (void)picker:(PHPickerViewController *)picker [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]]; } - [self handleMultiSavedPaths:pathList]; + [self handleSavedPathList:pathList]; } /** @@ -549,7 +549,7 @@ - (void)saveImageWithOriginalImageData:(NSData *)originalImageData NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; [mutableArray addObject:savedPath]; - [self handleMultiSavedPaths:mutableArray]; + [self handleSavedPathList:mutableArray]; } - (void)saveImageWithPickerInfo:(NSDictionary *)info @@ -561,20 +561,23 @@ - (void)saveImageWithPickerInfo:(NSDictionary *)info NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; [mutableArray addObject:savedPath]; - [self handleMultiSavedPaths:mutableArray]; + [self handleSavedPathList:mutableArray]; } /** * Applies NSMutableArray on the FLutterResult. * - * If the @c pathList is not nill and count of the @c pathList is - * greater than 0 then call FlutterResult with the @c pathList. If - * the @c pathList is nill or count of the @c pathList is not - * greater than 0 then call FlutterResult with FlutterError. + * NSString must be returned by FlutterResult if the single image + * mode is active. It is checked by @c maxImagesAllowed and + * returns the first object of the @c pathlist. + * + * NSMutableArray must be returned by FlutterResult if the multi-image + * mode is active. After the @c pathlist count is checked then it returns + * the @c pathlist. * * @param @pathList that should be applied to FlutterResult. */ -- (void)handleMultiSavedPaths:(NSMutableArray *)pathList { +- (void)handleSavedPathList:(NSMutableArray *)pathList { if (!self.result) { return; } From 3a0aca40839f7fffde722f2403e5267b8144609d Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 14 Jun 2021 20:13:35 +0200 Subject: [PATCH 59/79] Revert the if statement changes --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 02e69fc04ac9..8c85214de021 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -403,6 +403,9 @@ - (void)picker:(PHPickerViewController *)picker image:localImage imageQuality:desiredImageQuality]; pathList[i] = savedPath; + if (--resultsInProgress == 0) { + dispatch_semaphore_signal(resultSemaphore); + } } else { [[PHImageManager defaultManager] @@ -419,11 +422,11 @@ - (void)picker:(PHPickerViewController *)picker maxHeight:maxHeight imageQuality:desiredImageQuality]; pathList[i] = savedPath; + if (--resultsInProgress == 0) { + dispatch_semaphore_signal(resultSemaphore); + } }]; } - if (--resultsInProgress == 0) { - dispatch_semaphore_signal(resultSemaphore); - } } }]; } From 9d0b257e77df07597d0e718475227a40e671aa9a Mon Sep 17 00:00:00 2001 From: yusufdag Date: Wed, 16 Jun 2021 13:49:35 +0200 Subject: [PATCH 60/79] Add new classes to subclass NSOperation `FLTPHPickerImageHandlingOperation` class is added to implement `NSOperation` and `NSOperationQueue`. The old semaphore implementation is changed to make the code more readable and clean. --- .../FLTPHPickerImageHandlingOperation.h | 21 +++ .../FLTPHPickerImageHandlingOperation.m | 132 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.h create mode 100644 packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.m diff --git a/packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.h b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.h new file mode 100644 index 000000000000..c84471e845af --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.h @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#import "FLTImagePickerImageUtil.h" +#import "FLTImagePickerMetaDataUtil.h" +#import "FLTImagePickerPhotoAssetUtil.h" + +@interface FLTPHPickerImageHandlingOperation : NSOperation + +- (instancetype)initWithResult:(PHPickerResult *)result + pathlist:(NSMutableArray *)pathList + maxHeight:(NSNumber *)maxHeight + maxWidth:(NSNumber *)maxWidth + desiredImageQuality:(NSNumber *)desiredImageQuality + index:(NSInteger)index API_AVAILABLE(ios(14)); + +@end diff --git a/packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.m b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.m new file mode 100644 index 000000000000..a102869a5798 --- /dev/null +++ b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerImageHandlingOperation.m @@ -0,0 +1,132 @@ +// 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 "FLTPHPickerImageHandlingOperation.h" + +API_AVAILABLE(ios(14)) +@interface FLTPHPickerImageHandlingOperation () + +@property(strong, nonatomic) PHPickerResult *result; +@property(weak, nonatomic) NSMutableArray *pathList; +@property(assign, nonatomic) NSNumber *maxHeight; +@property(assign, nonatomic) NSNumber *maxWidth; +@property(assign, nonatomic) NSNumber *desiredImageQuality; +@property(assign, nonatomic) NSInteger index; + +@end + +@implementation FLTPHPickerImageHandlingOperation { + BOOL executing; + BOOL finished; +} + +- (instancetype)initWithResult:(PHPickerResult *)result + pathlist:(NSMutableArray *)pathList + maxHeight:(NSNumber *)maxHeight + maxWidth:(NSNumber *)maxWidth + desiredImageQuality:(NSNumber *)desiredImageQuality + index:(NSInteger)index API_AVAILABLE(ios(14)) { + if (self = [super init]) { + if (result) { + self.result = result; + self.pathList = pathList; + self.maxHeight = maxHeight; + self.maxWidth = maxWidth; + self.desiredImageQuality = desiredImageQuality; + self.index = index; + executing = NO; + finished = NO; + } else { + return nil; + } + return self; + } else { + return nil; + } +} + +- (BOOL)isConcurrent { + return YES; +} + +- (BOOL)isExecuting { + return executing; +} + +- (BOOL)isFinished { + return finished; +} + +- (void)setFinished:(BOOL)isFinished { + [self willChangeValueForKey:@"isFinished"]; + self->finished = isFinished; + [self didChangeValueForKey:@"isFinished"]; +} + +- (void)setExecuting:(BOOL)isExecuting { + [self willChangeValueForKey:@"isExecuting"]; + self->executing = isExecuting; + [self didChangeValueForKey:@"isExecuting"]; +} + +- (void)completeOperation { + [self setExecuting:NO]; + [self setFinished:YES]; +} + +- (void)start { + if ([self isCancelled]) { + [self setFinished:YES]; + return; + } + [self setExecuting:YES]; + if (@available(iOS 14, *)) { + [self.result.itemProvider + loadObjectOfClass:[UIImage class] + completionHandler:^(__kindof id _Nullable image, + NSError *_Nullable error) { + if ([image isKindOfClass:[UIImage class]]) { + __block UIImage *localImage = image; + PHAsset *originalAsset = + [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:self.result]; + + if (self.maxWidth != (id)[NSNull null] || self.maxHeight != (id)[NSNull null]) { + localImage = [FLTImagePickerImageUtil scaledImage:localImage + maxWidth:self.maxWidth + maxHeight:self.maxHeight + isMetadataAvailable:originalAsset != nil]; + } + __block NSString *savedPath; + if (!originalAsset) { + // Image picked without an original asset (e.g. User pick image without permission) + savedPath = + [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil + image:localImage + imageQuality:self.desiredImageQuality]; + self.pathList[self.index] = savedPath; + [self completeOperation]; + } else { + [[PHImageManager defaultManager] + requestImageDataForAsset:originalAsset + options:nil + resultHandler:^( + NSData *_Nullable imageData, NSString *_Nullable dataUTI, + UIImageOrientation orientation, NSDictionary *_Nullable info) { + // maxWidth and maxHeight are used only for GIF images. + savedPath = [FLTImagePickerPhotoAssetUtil + saveImageWithOriginalImageData:imageData + image:localImage + maxWidth:self.maxWidth + maxHeight:self.maxHeight + imageQuality:self.desiredImageQuality]; + self.pathList[self.index] = savedPath; + [self completeOperation]; + }]; + } + } + }]; + } +} + +@end From d9d8525f0604ad71760fadd36b6feeef671e4f30 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Wed, 16 Jun 2021 13:54:10 +0200 Subject: [PATCH 61/79] Refactor picker method to use NSOperation --- .../ios/Classes/FLTImagePickerPlugin.m | 106 ++++++------------ 1 file changed, 33 insertions(+), 73 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 8c85214de021..624986a1f03f 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -14,6 +14,7 @@ #import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" +#import "FLTPHPickerImageHandlingOperation.h" @interface FLTImagePickerPlugin () *)results API_AVAILABLE(ios(14)) { [picker dismissViewControllerAnimated:YES completion:nil]; - if (results.count == 0) { - self.result(nil); - self.result = nil; - _arguments = nil; - return; - } - NSNumber *maxWidth = [_arguments objectForKey:@"maxWidth"]; - NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; - NSNumber *imageQuality = [_arguments objectForKey:@"imageQuality"]; - NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; - - __block NSMutableArray *pathList = [self createNSMutableArrayWithSize:results.count]; - __block NSUInteger resultsInProgress = results.count; - __block dispatch_semaphore_t resultSemaphore = dispatch_semaphore_create(0); - - for (int i = 0; i < results.count; i++) { - PHPickerResult *result = results[i]; - - [result.itemProvider - loadObjectOfClass:[UIImage class] - completionHandler:^(__kindof id _Nullable image, - NSError *_Nullable error) { - if ([image isKindOfClass:[UIImage class]]) { - __block UIImage *localImage = image; - PHAsset *originalAsset = - [FLTImagePickerPhotoAssetUtil getAssetFromPHPickerResult:result]; - - if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - localImage = [FLTImagePickerImageUtil scaledImage:localImage - maxWidth:maxWidth - maxHeight:maxHeight - isMetadataAvailable:originalAsset != nil]; - } - __block NSString *savedPath; - if (!originalAsset) { - // Image picked without an original asset (e.g. User pick image without permission) - savedPath = - [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil - image:localImage - imageQuality:desiredImageQuality]; - pathList[i] = savedPath; - if (--resultsInProgress == 0) { - dispatch_semaphore_signal(resultSemaphore); - } - - } else { - [[PHImageManager defaultManager] - requestImageDataForAsset:originalAsset - options:nil - resultHandler:^( - NSData *_Nullable imageData, NSString *_Nullable dataUTI, - UIImageOrientation orientation, NSDictionary *_Nullable info) { - // maxWidth and maxHeight are used only for GIF images. - savedPath = [FLTImagePickerPhotoAssetUtil - saveImageWithOriginalImageData:imageData - image:localImage - maxWidth:maxWidth - maxHeight:maxHeight - imageQuality:desiredImageQuality]; - pathList[i] = savedPath; - if (--resultsInProgress == 0) { - dispatch_semaphore_signal(resultSemaphore); - } - }]; - } - } - }]; - } - while (dispatch_semaphore_wait(resultSemaphore, DISPATCH_TIME_NOW)) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]]; - } - [self handleSavedPathList:pathList]; + dispatch_queue_t backgroundQueue = + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); + dispatch_async(backgroundQueue, ^{ + if (results.count == 0) { + self.result(nil); + self.result = nil; + self->_arguments = nil; + return; + } + NSNumber *maxWidth = [self->_arguments objectForKey:@"maxWidth"]; + NSNumber *maxHeight = [self->_arguments objectForKey:@"maxHeight"]; + NSNumber *imageQuality = [self->_arguments objectForKey:@"imageQuality"]; + NSNumber *desiredImageQuality = [self getDesiredImageQuality:imageQuality]; + NSOperationQueue *operationQueue = [NSOperationQueue new]; + NSMutableArray *pathList = [self createNSMutableArrayWithSize:results.count]; + + for (int i = 0; i < results.count; i++) { + PHPickerResult *result = results[i]; + FLTPHPickerImageHandlingOperation *operation = + [[FLTPHPickerImageHandlingOperation alloc] initWithResult:result + pathlist:pathList + maxHeight:maxHeight + maxWidth:maxWidth + desiredImageQuality:desiredImageQuality + index:i]; + [operationQueue addOperation:operation]; + } + [operationQueue waitUntilAllOperationsAreFinished]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self handleSavedPathList:pathList]; + }); + }); } /** From 5deab57d14a40a57a6405ec933bd8dd9b13f79a0 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Wed, 16 Jun 2021 14:21:17 +0200 Subject: [PATCH 62/79] Update naming --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 6 +++--- ...dlingOperation.h => GetPathForPHPPickerImageOperation.h} | 2 +- ...dlingOperation.m => GetPathForPHPPickerImageOperation.m} | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename packages/image_picker/image_picker/ios/Classes/{FLTPHPickerImageHandlingOperation.h => GetPathForPHPPickerImageOperation.h} (92%) rename packages/image_picker/image_picker/ios/Classes/{FLTPHPickerImageHandlingOperation.m => GetPathForPHPPickerImageOperation.m} (96%) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 624986a1f03f..8136edfd639c 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -14,7 +14,7 @@ #import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" -#import "FLTPHPickerImageHandlingOperation.h" +#import "GetPathForPHPPickerImageOperation.h" @interface FLTImagePickerPlugin () Date: Thu, 17 Jun 2021 13:54:00 +0200 Subject: [PATCH 63/79] Add block to get saved path --- .../ios/Classes/FLTImagePickerPlugin.m | 7 +++--- .../GetPathForPHPPickerImageOperation.h | 3 +-- .../GetPathForPHPPickerImageOperation.m | 24 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 8136edfd639c..d0bb8718e677 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -381,13 +381,14 @@ - (void)picker:(PHPickerViewController *)picker for (int i = 0; i < results.count; i++) { PHPickerResult *result = results[i]; - GetPathForPHPPickerImageOperation *operation = + GetPathForPHPPickerImageOperation *operation = [[GetPathForPHPPickerImageOperation alloc] initWithResult:result - pathlist:pathList maxHeight:maxHeight maxWidth:maxWidth desiredImageQuality:desiredImageQuality - index:i]; + savedPathBlock:^(NSString *savedPath) { + pathList[i] = savedPath; + }]; [operationQueue addOperation:operation]; } [operationQueue waitUntilAllOperationsAreFinished]; diff --git a/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.h b/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.h index 8ad4dbf00e4f..c18989b7027d 100644 --- a/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.h +++ b/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.h @@ -12,10 +12,9 @@ @interface GetPathForPHPPickerImageOperation : NSOperation - (instancetype)initWithResult:(PHPickerResult *)result - pathlist:(NSMutableArray *)pathList maxHeight:(NSNumber *)maxHeight maxWidth:(NSNumber *)maxWidth desiredImageQuality:(NSNumber *)desiredImageQuality - index:(NSInteger)index API_AVAILABLE(ios(14)); + savedPathBlock:(void (^)(NSString *))savedPathBlock API_AVAILABLE(ios(14)); @end diff --git a/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.m b/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.m index 9de83e312c2e..de1c53b9279d 100644 --- a/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.m +++ b/packages/image_picker/image_picker/ios/Classes/GetPathForPHPPickerImageOperation.m @@ -8,33 +8,32 @@ @interface GetPathForPHPPickerImageOperation () @property(strong, nonatomic) PHPickerResult *result; -@property(weak, nonatomic) NSMutableArray *pathList; @property(assign, nonatomic) NSNumber *maxHeight; @property(assign, nonatomic) NSNumber *maxWidth; @property(assign, nonatomic) NSNumber *desiredImageQuality; -@property(assign, nonatomic) NSInteger index; @end +typedef void (^GetSavedPath)(NSString *); + @implementation GetPathForPHPPickerImageOperation { BOOL executing; BOOL finished; + GetSavedPath getSavedPath; } - (instancetype)initWithResult:(PHPickerResult *)result - pathlist:(NSMutableArray *)pathList maxHeight:(NSNumber *)maxHeight maxWidth:(NSNumber *)maxWidth desiredImageQuality:(NSNumber *)desiredImageQuality - index:(NSInteger)index API_AVAILABLE(ios(14)) { + savedPathBlock:(GetSavedPath)savedPathBlock API_AVAILABLE(ios(14)) { if (self = [super init]) { if (result) { self.result = result; - self.pathList = pathList; self.maxHeight = maxHeight; self.maxWidth = maxWidth; self.desiredImageQuality = desiredImageQuality; - self.index = index; + getSavedPath = savedPathBlock; executing = NO; finished = NO; } else { @@ -70,9 +69,10 @@ - (void)setExecuting:(BOOL)isExecuting { [self didChangeValueForKey:@"isExecuting"]; } -- (void)completeOperation { +- (void)completeOperationWithPath:(NSString *)savedPath { [self setExecuting:NO]; [self setFinished:YES]; + getSavedPath(savedPath); } - (void)start { @@ -80,8 +80,8 @@ - (void)start { [self setFinished:YES]; return; } - [self setExecuting:YES]; if (@available(iOS 14, *)) { + [self setExecuting:YES]; [self.result.itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(__kindof id _Nullable image, @@ -104,8 +104,7 @@ - (void)start { [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:nil image:localImage imageQuality:self.desiredImageQuality]; - self.pathList[self.index] = savedPath; - [self completeOperation]; + [self completeOperationWithPath:savedPath]; } else { [[PHImageManager defaultManager] requestImageDataForAsset:originalAsset @@ -120,12 +119,13 @@ - (void)start { maxWidth:self.maxWidth maxHeight:self.maxHeight imageQuality:self.desiredImageQuality]; - self.pathList[self.index] = savedPath; - [self completeOperation]; + [self completeOperationWithPath:savedPath]; }]; } } }]; + } else { + [self setFinished:YES]; } } From d7a5e708d4ea3351c0020bffda0afa99e0d8ca85 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 17 Jun 2021 13:58:03 +0200 Subject: [PATCH 64/79] Update naming --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 6 +++--- ...geOperation.h => FLTPHPickerResultPopulatingOperation.h} | 2 +- ...geOperation.m => FLTPHPickerResultPopulatingOperation.m} | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename packages/image_picker/image_picker/ios/Classes/{GetPathForPHPPickerImageOperation.h => FLTPHPickerResultPopulatingOperation.h} (91%) rename packages/image_picker/image_picker/ios/Classes/{GetPathForPHPPickerImageOperation.m => FLTPHPickerResultPopulatingOperation.m} (96%) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index d0bb8718e677..12518c44bd23 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -14,7 +14,7 @@ #import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" -#import "GetPathForPHPPickerImageOperation.h" +#import "FLTPHPickerResultPopulatingOperation.h" @interface FLTImagePickerPlugin () Date: Thu, 17 Jun 2021 14:05:27 +0200 Subject: [PATCH 65/79] Update CHANGELOG --- packages/image_picker/image_picker/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 6f9aab2c6de3..78f1d28bcb90 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.8.1 * Add a new method `getMultiImage` to allow picking multiple images on iOS 14 or higher and Android 4.3 or higher. -* Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, see: https://github.com/... +* Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634) ## 0.8.0+3 From 32cefbc263b7f463bb8c965f291d2f78a794236e Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 17 Jun 2021 14:08:18 +0200 Subject: [PATCH 66/79] Update README --- packages/image_picker/image_picker/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index 410538cb2647..5f2e5b739bd7 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -119,6 +119,8 @@ Future retrieveLostData() async { There's no way to detect when this happens, so calling this method at the right place is essential. We recommend to wire this into some kind of start up check. Please refer to the example app to see how we used it. +On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634) + ## Deprecation warnings in `pickImage`, `pickVideo` and `LostDataResponse` Starting with version **0.6.7** of the image_picker plugin, the API of the plugin changed slightly to allow for web implementations to exist. From 60b2e065e23d5c3aee0e8fbb6e35f8539ebda5d0 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 17 Jun 2021 14:12:04 +0200 Subject: [PATCH 67/79] Refactor to clean code --- .../ios/Classes/FLTImagePickerPlugin.m | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 12518c44bd23..03fc663a2430 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -383,12 +383,12 @@ - (void)picker:(PHPickerViewController *)picker PHPickerResult *result = results[i]; FLTPHPickerResultPopulatingOperation *operation = [[FLTPHPickerResultPopulatingOperation alloc] initWithResult:result - maxHeight:maxHeight - maxWidth:maxWidth - desiredImageQuality:desiredImageQuality - savedPathBlock:^(NSString *savedPath) { - pathList[i] = savedPath; - }]; + maxHeight:maxHeight + maxWidth:maxWidth + desiredImageQuality:desiredImageQuality + savedPathBlock:^(NSString *savedPath) { + pathList[i] = savedPath; + }]; [operationQueue addOperation:operation]; } [operationQueue waitUntilAllOperationsAreFinished]; @@ -510,10 +510,7 @@ - (void)saveImageWithOriginalImageData:(NSData *)originalImageData maxWidth:maxWidth maxHeight:maxHeight imageQuality:imageQuality]; - NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; - [mutableArray addObject:savedPath]; - - [self handleSavedPathList:mutableArray]; + [self handleSavedPathList:@[ savedPath ]]; } - (void)saveImageWithPickerInfo:(NSDictionary *)info @@ -522,10 +519,7 @@ - (void)saveImageWithPickerInfo:(NSDictionary *)info NSString *savedPath = [FLTImagePickerPhotoAssetUtil saveImageWithPickerInfo:info image:image imageQuality:imageQuality]; - NSMutableArray *mutableArray = [[NSMutableArray alloc] init]; - [mutableArray addObject:savedPath]; - - [self handleSavedPathList:mutableArray]; + [self handleSavedPathList:@[ savedPath ]]; } /** @@ -541,7 +535,7 @@ - (void)saveImageWithPickerInfo:(NSDictionary *)info * * @param @pathList that should be applied to FlutterResult. */ -- (void)handleSavedPathList:(NSMutableArray *)pathList { +- (void)handleSavedPathList:(NSArray *)pathList { if (!self.result) { return; } From 7f3791bbddf6c09e39960d207b8d2b4edd4d95db Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 17 Jun 2021 15:05:15 +0200 Subject: [PATCH 68/79] Refactor if statement and error message --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 03fc663a2430..e74e8f2b3515 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -539,13 +539,16 @@ - (void)handleSavedPathList:(NSArray *)pathList { if (!self.result) { return; } - if (pathList && (self.maxImagesAllowed == 1)) { - self.result(pathList.firstObject); - } else if (pathList.count > 0) { - self.result(pathList); + if (pathList) { + if ((self.maxImagesAllowed == 1)) { + self.result(pathList.firstObject); + } else { + self.result(pathList); + } } else { + // This should never happen. self.result([FlutterError errorWithCode:@"create_error" - message:@"Temporary file(s) could not be created" + message:@"Pathlist should not be nil" details:nil]); } self.result = nil; From 193566ae52a01485301ff0a80c0ffaf980bff362 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Thu, 17 Jun 2021 15:37:05 +0200 Subject: [PATCH 69/79] Add class documentation --- .../Classes/FLTPHPickerResultPopulatingOperation.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTPHPickerResultPopulatingOperation.h b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerResultPopulatingOperation.h index 39fddb182215..8e670a616b8c 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTPHPickerResultPopulatingOperation.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTPHPickerResultPopulatingOperation.h @@ -9,6 +9,17 @@ #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" +/*! + @class FLTPHPickerResultPopulatingOperation + + @brief The FLTPHPickerResultPopulatingOperation class + + @discussion This class was implemented to handle saved image paths and populate the pathList + with the final result by using GetSavedPath type block. + + @superclass SuperClass: NSOperation\n + @helps It helps FLTImagePickerPlugin class. + */ @interface FLTPHPickerResultPopulatingOperation : NSOperation - (instancetype)initWithResult:(PHPickerResult *)result From b41e3f4a95d15e221e198b3008a33413c120f4e4 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 18 Jun 2021 09:34:57 +0200 Subject: [PATCH 70/79] Fix error message --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index e74e8f2b3515..771b39606e89 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -548,7 +548,7 @@ - (void)handleSavedPathList:(NSArray *)pathList { } else { // This should never happen. self.result([FlutterError errorWithCode:@"create_error" - message:@"Pathlist should not be nil" + message:@"pathList should not be nil" details:nil]); } self.result = nil; From 3712c950f7f2eef88fe01f94489079b472ea4a00 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Fri, 18 Jun 2021 12:11:59 +0200 Subject: [PATCH 71/79] Update naming --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 6 +++--- ...ingOperation.h => FLTPHPickerSaveImageToPathOperation.h} | 6 +++--- ...ingOperation.m => FLTPHPickerSaveImageToPathOperation.m} | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) rename packages/image_picker/image_picker/ios/Classes/{FLTPHPickerResultPopulatingOperation.h => FLTPHPickerSaveImageToPathOperation.h} (85%) rename packages/image_picker/image_picker/ios/Classes/{FLTPHPickerResultPopulatingOperation.m => FLTPHPickerSaveImageToPathOperation.m} (96%) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 771b39606e89..8423f776d03c 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -14,7 +14,7 @@ #import "FLTImagePickerImageUtil.h" #import "FLTImagePickerMetaDataUtil.h" #import "FLTImagePickerPhotoAssetUtil.h" -#import "FLTPHPickerResultPopulatingOperation.h" +#import "FLTPHPickerSaveImageToPathOperation.h" @interface FLTImagePickerPlugin () Date: Fri, 18 Jun 2021 12:13:12 +0200 Subject: [PATCH 72/79] Refactor if statement to check pathList items --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 8423f776d03c..d562b4e03965 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -539,12 +539,19 @@ - (void)handleSavedPathList:(NSArray *)pathList { if (!self.result) { return; } + id item = nil; // Local variable to check if the pathList contains nil if (pathList) { + if (![pathList containsObject:item]) { 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 nil" + details:nil]); + } } else { // This should never happen. self.result([FlutterError errorWithCode:@"create_error" From e20b966740d9b6ee543cb6a20c27a51c33ff4ea8 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 21 Jun 2021 09:09:57 +0200 Subject: [PATCH 73/79] Fix format --- .../ios/Classes/FLTImagePickerPlugin.m | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index d562b4e03965..32d6e86f8656 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -383,12 +383,12 @@ - (void)picker:(PHPickerViewController *)picker PHPickerResult *result = results[i]; FLTPHPickerSaveImageToPathOperation *operation = [[FLTPHPickerSaveImageToPathOperation alloc] initWithResult:result - maxHeight:maxHeight - maxWidth:maxWidth - desiredImageQuality:desiredImageQuality - savedPathBlock:^(NSString *savedPath) { - pathList[i] = savedPath; - }]; + maxHeight:maxHeight + maxWidth:maxWidth + desiredImageQuality:desiredImageQuality + savedPathBlock:^(NSString *savedPath) { + pathList[i] = savedPath; + }]; [operationQueue addOperation:operation]; } [operationQueue waitUntilAllOperationsAreFinished]; @@ -542,11 +542,11 @@ - (void)handleSavedPathList:(NSArray *)pathList { id item = nil; // Local variable to check if the pathList contains nil if (pathList) { if (![pathList containsObject:item]) { - if ((self.maxImagesAllowed == 1)) { - self.result(pathList.firstObject); - } else { - self.result(pathList); - } + 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 nil" From 42b2db1c4b6870de7dbee5aec9832e373152814e Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 21 Jun 2021 09:34:38 +0200 Subject: [PATCH 74/79] Refactor if statement --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 32d6e86f8656..cff8b935ef60 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -539,9 +539,9 @@ - (void)handleSavedPathList:(NSArray *)pathList { if (!self.result) { return; } - id item = nil; // Local variable to check if the pathList contains nil + if (pathList) { - if (![pathList containsObject:item]) { + if (![pathList containsObject:[NSNull null]]) { if ((self.maxImagesAllowed == 1)) { self.result(pathList.firstObject); } else { @@ -549,7 +549,7 @@ - (void)handleSavedPathList:(NSArray *)pathList { } } else { self.result([FlutterError errorWithCode:@"create_error" - message:@"pathList's items should not be nil" + message:@"pathList's items should not be null" details:nil]); } } else { From e8a6c959922c0d9c337ef08e8e15fd3bc0a0a9ab Mon Sep 17 00:00:00 2001 From: yusufdag Date: Mon, 21 Jun 2021 09:34:54 +0200 Subject: [PATCH 75/79] Fix unit test --- .../example/ios/RunnerTests/ImagePickerPluginTests.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m index b2a4373ed15e..f667526671f7 100644 --- a/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -151,10 +151,12 @@ - (void)testPluginMultiImagePathIsNil { XCTAssertEqualObjects(pickImageResult.code, @"create_error"); } -- (void)testPluginMultiImagePathHasZeroItem { +- (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; From c39bf6642070c45df78c2bddd2f6f1ba7580a71c Mon Sep 17 00:00:00 2001 From: Yusuf <82844127+ydag@users.noreply.github.com> Date: Tue, 22 Jun 2021 09:39:57 +0200 Subject: [PATCH 76/79] Update packages/image_picker/image_picker/README.md Co-authored-by: Rene Floor --- packages/image_picker/image_picker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index 566c581e7280..3b3746d9f63e 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -68,7 +68,7 @@ Future retrieveLostData() async { There's no way to detect when this happens, so calling this method at the right place is essential. We recommend to wire this into some kind of start up check. Please refer to the example app to see how we used it. -On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634) +On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634). ## Deprecation warnings in `pickImage`, `pickVideo` and `LostDataResponse` From 44ab497fd2bebff44b499be41812fdef80a6ddd6 Mon Sep 17 00:00:00 2001 From: Yusuf <82844127+ydag@users.noreply.github.com> Date: Tue, 22 Jun 2021 09:40:47 +0200 Subject: [PATCH 77/79] Update packages/image_picker/image_picker/CHANGELOG.md Co-authored-by: Rene Floor --- packages/image_picker/image_picker/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 10e4db8261c7..5a2b2e6a838f 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.8.1 * Add a new method `getMultiImage` to allow picking multiple images on iOS 14 or higher and Android 4.3 or higher. -* Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634) +* Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634). ## 0.8.0+4 From 43473dad25ed7822c6f6164944be0dff0e137fa7 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 22 Jun 2021 10:29:43 +0200 Subject: [PATCH 78/79] Add else block for lower iOS versions --- .../image_picker/ios/Classes/FLTImagePickerPlugin.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index cff8b935ef60..7c91606ba535 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -142,6 +142,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result self.result = result; _arguments = call.arguments; [self pickImageWithPHPicker:0]; + } else { + [self pickImageWithUIImagePicker]; } } else if ([@"pickVideo" isEqualToString:call.method]) { _imagePickerController = [[UIImagePickerController alloc] init]; From c50b694f81655fb8297ebcac87d52d22a28e5d23 Mon Sep 17 00:00:00 2001 From: yusufdag Date: Tue, 22 Jun 2021 10:31:04 +0200 Subject: [PATCH 79/79] Update the CHANGELOG --- packages/image_picker/image_picker/CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 5a2b2e6a838f..7147c0f6f7de 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,7 +1,9 @@ ## 0.8.1 -* Add a new method `getMultiImage` to allow picking multiple images on iOS 14 or higher and Android 4.3 or higher. -* Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, see: [#84634](https://github.com/flutter/flutter/issues/84634). +* Add a new method `getMultiImage` to allow picking multiple images on iOS 14 or higher +and Android 4.3 or higher. Returns only 1 image for lower versions of iOS and Android. +* Known issue: On Android, `getLostData` will only get the last picked image when picking multiple images, +see: [#84634](https://github.com/flutter/flutter/issues/84634). ## 0.8.0+4