-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[image_picker] Multiple image support #3783
Changes from all commits
cf18209
41af3db
7b934b0
82385a5
74c9fad
f027e9a
31eae2d
bbb094b
e8cf955
a543675
0844445
745fb1e
17cb3d8
3fd1a01
75b34be
ea075ad
2619cf4
4362c6e
9b1171f
e693266
4563f90
c09e604
8cb0114
d3315c8
85d605c
aaf5fd8
7cf9338
7d9b7a5
bba9609
2382d9e
0e910ac
ca3be77
b69234a
d2be72d
9c96f25
3a0cb11
d39e39c
527491d
1bae4e2
572e04b
dc40ea3
ba34e22
ae3f811
3b07dc2
6b657f7
5595392
6c82b13
6c6ca76
aeac535
02a1d07
7f147dc
a1ec45a
9ad4a9a
a598d78
4af8c30
e996554
3b8c1dd
7c09ae6
b5de177
ed7ef7e
a2621e6
60b5238
25d9a46
6f4e7c7
33b74a0
8c0c5ca
152bd33
f3d2441
44c06f1
3a0aca4
9d0b257
d9d8525
5deab57
3182612
d7a5e70
6c514b7
32cefbc
60b2e06
7f3791b
193566a
b41e3f4
3712c95
59f3501
080a211
b3c8518
e20b966
42b2db1
e8a6c95
27e0790
c39bf66
44ab497
b858317
43473da
c50b694
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,9 @@ First, add `image_picker` as a [dependency in your pubspec.yaml file](https://fl | |
|
|
||
| ### iOS | ||
|
|
||
| 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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a Google Search URL, is there a direct link to the Apple issue tracker? I can only find forum threads like this: https://developer.apple.com/forums/thread/658135
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, there is not an issue tracker. Not one publicly available. It seems those issues can contain private data which is why they are private.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is https://developer.apple.com/forums/thread/658135 a better link? @danielroek |
||
|
|
||
| Add the following keys to your _Info.plist_ file, located in `<project root>/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. | ||
|
|
@@ -19,6 +22,8 @@ Add the following keys to your _Info.plist_ file, located in `<project root>/ios | |
|
|
||
| ### Android | ||
|
|
||
| Starting with version **0.8.1** the Android implementation support to pick (multiple) images on Android 4.3 or higher. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's also mention the android knowns issue for get lost data in the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
|
||
| 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 `<application>` tag in AndroidManifest.xml, as `image_picker` has been updated to make use of scoped storage. | ||
|
|
@@ -63,6 +68,8 @@ Future<void> 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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| import io.flutter.plugin.common.PluginRegistry; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.UUID; | ||
|
|
@@ -75,6 +76,7 @@ public class ImagePickerDelegate | |
| @VisibleForTesting static final int REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY = 2342; | ||
| @VisibleForTesting static final int REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA = 2343; | ||
| @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_CODE_CHOOSE_VIDEO_FROM_GALLERY = 2352; | ||
| @VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353; | ||
| @VisibleForTesting static final int REQUEST_CAMERA_VIDEO_PERMISSION = 2355; | ||
|
|
@@ -315,13 +317,32 @@ public void chooseImageFromGallery(MethodCall methodCall, MethodChannel.Result r | |
| launchPickImageFromGalleryIntent(); | ||
| } | ||
|
|
||
| public void chooseMultiImageFromGallery(MethodCall methodCall, MethodChannel.Result result) { | ||
| if (!setPendingMethodCallAndResult(methodCall, result)) { | ||
| finishWithAlreadyActiveError(result); | ||
| return; | ||
| } | ||
|
|
||
| launchMultiPickImageFromGalleryIntent(); | ||
| } | ||
|
|
||
| private void launchPickImageFromGalleryIntent() { | ||
| Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); | ||
| pickImageIntent.setType("image/*"); | ||
|
|
||
| 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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So lower android version won't allow multiple images right? We need to mention it in the CHANGELOG and other docs as well.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| 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); | ||
|
|
@@ -440,6 +461,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; | ||
|
|
@@ -467,6 +491,24 @@ private void handleChooseImageResult(int resultCode, Intent data) { | |
| finishWithSuccess(null); | ||
| } | ||
|
|
||
| private void handleChooseMultiImageResult(int resultCode, Intent intent) { | ||
| if (resultCode == Activity.RESULT_OK && intent != null) { | ||
| ArrayList<String> paths = new ArrayList<>(); | ||
| 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, intent.getData())); | ||
| } | ||
| 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()); | ||
|
|
@@ -516,26 +558,45 @@ public void onPathReady(String path) { | |
| finishWithSuccess(null); | ||
| } | ||
|
|
||
| private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) { | ||
| private void handleMultiImageResult( | ||
| ArrayList<String> 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); | ||
|
|
||
| finishWithSuccess(finalImagePath); | ||
| for (int i = 0; i < paths.size(); i++) { | ||
| String finalImagePath = getResizedImagePath(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); | ||
| } | ||
| } | ||
|
|
||
| private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled) { | ||
| if (methodCall != null) { | ||
| String finalImagePath = getResizedImagePath(path); | ||
| //delete original file if scaled | ||
| if (finalImagePath != null && !finalImagePath.equals(path) && shouldDeleteOriginalIfScaled) { | ||
| new File(path).delete(); | ||
| } | ||
| finishWithSuccess(finalImagePath); | ||
| } else { | ||
| finishWithSuccess(path); | ||
| } | ||
| } | ||
|
|
||
| private String getResizedImagePath(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); | ||
| } | ||
|
|
@@ -564,6 +625,17 @@ private void finishWithSuccess(String imagePath) { | |
| clearMethodCallAndResult(); | ||
| } | ||
|
|
||
| private void finishWithListSuccess(ArrayList<String> imagePaths) { | ||
| if (pendingResult == null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we also call |
||
| for (String imagePath : imagePaths) { | ||
| cache.saveResult(imagePath, null, null); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only one image can be saved in the "cache", so only the last one saved is in the cache, it is not what we want right?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can save only one image path and it is the last one in this case. Maybe we can create another PR to update the cache. What do you think?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, please create an issue and link a TODO to it. We also need to mentioned this "known issue" in the CHANGELOG, which also can be linked to the same issue. |
||
| } | ||
| return; | ||
| } | ||
| pendingResult.success(imagePaths); | ||
| clearMethodCallAndResult(); | ||
| } | ||
|
|
||
| private void finishWithAlreadyActiveError(MethodChannel.Result result) { | ||
| result.error("already_active", "Image picker is already active", null); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!