From 374325736b37bbc5e0339743e70c4710e9e4728b Mon Sep 17 00:00:00 2001 From: tarrinneal Date: Mon, 22 May 2023 18:07:17 -0700 Subject: [PATCH] allow multiple --- packages/image_picker/image_picker/README.md | 4 +- .../image_picker/example/lib/main.dart | 47 +++- .../example/lib/readme_excerpts.dart | 7 +- .../example/test/readme_excerpts_test.dart | 4 +- .../image_picker/lib/image_picker.dart | 51 +++- .../image_picker/test/image_picker_test.dart | 236 ++++++++++++++++-- .../imagepicker/ImagePickerDelegate.java | 10 +- .../imagepicker/ImagePickerPlugin.java | 3 +- .../flutter/plugins/imagepicker/Messages.java | 10 +- .../imagepicker/ImagePickerDelegateTest.java | 2 +- .../imagepicker/ImagePickerPluginTest.java | 8 +- .../lib/image_picker_android.dart | 9 +- .../lib/src/messages.g.dart | 16 +- .../pigeons/messages.dart | 1 + .../test/image_picker_android_test.dart | 42 +++- .../image_picker_android/test/test_api.g.dart | 14 +- .../lib/image_picker_for_web.dart | 8 +- .../ios/RunnerTests/ImagePickerPluginTests.m | 3 + .../ios/Classes/FLTImagePickerPlugin.m | 4 + .../image_picker_ios/ios/Classes/messages.g.h | 1 + .../image_picker_ios/ios/Classes/messages.g.m | 8 +- .../lib/image_picker_ios.dart | 9 +- .../image_picker_ios/lib/src/messages.g.dart | 11 +- .../image_picker_ios/pigeons/messages.dart | 6 +- .../test/image_picker_ios_test.dart | 65 +++-- .../image_picker_ios/test/test_api.g.dart | 11 +- .../method_channel_image_picker.dart | 11 +- .../image_picker_platform.dart | 4 - .../src/types/media_selection_options.dart | 28 +-- .../new_method_channel_image_picker_test.dart | 53 ++-- .../lib/image_picker_windows.dart | 14 +- 31 files changed, 533 insertions(+), 167 deletions(-) diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md index d47cb6d3373f..a7bbcac0c6cd 100755 --- a/packages/image_picker/image_picker/README.md +++ b/packages/image_picker/image_picker/README.md @@ -125,8 +125,10 @@ final XFile? galleryVideo = final XFile? cameraVideo = await picker.pickVideo(source: ImageSource.camera); // Pick multiple images. final List images = await picker.pickMultiImage(); +// Pick singe image or video. +final XFile? media = await picker.pickMedia(); // Pick multiple images and videos. -final List media = await picker.pickMedia(); +final List medias = await picker.pickMultipleMedia(); ``` ## Migrating to 0.8.2+ diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index 8a8290875471..308c6cb3fdfc 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -94,7 +94,7 @@ class _MyHomePageState extends State { final XFile? file = await _picker.pickVideo( source: source, maxDuration: const Duration(seconds: 10)); await _playVideo(file); - } else if (isMultiImage) { + } else if (isMultiImage && !isMedia) { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { @@ -112,11 +112,11 @@ class _MyHomePageState extends State { }); } }); - } else if (isMedia) { + } else if (isMedia && isMultiImage) { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { try { - final List pickedFileList = await _picker.pickMedia( + final List pickedFileList = await _picker.pickMultipleMedia( maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: quality, @@ -130,6 +130,28 @@ class _MyHomePageState extends State { }); } }); + } else if (isMedia) { + await _displayPickImageDialog(context, + (double? maxWidth, double? maxHeight, int? quality) async { + try { + final List pickedFileList = []; + final XFile? media = await _picker.pickMedia( + maxWidth: maxWidth, + maxHeight: maxHeight, + imageQuality: quality, + ); + if (media != null) { + pickedFileList.add(media); + setState(() { + _imageFileList = pickedFileList; + }); + } + } catch (e) { + setState(() { + _pickImageError = e; + }); + } + }); } else { await _displayPickImageDialog(context, (double? maxWidth, double? maxHeight, int? quality) async { @@ -338,6 +360,23 @@ class _MyHomePageState extends State { child: const Icon(Icons.photo), ), ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + onPressed: () { + isVideo = false; + _onImageButtonPressed( + ImageSource.gallery, + context: context, + isMultiImage: true, + isMedia: true, + ); + }, + heroTag: 'multipleMedia', + tooltip: 'Pick Multiple Media from gallery', + child: const Icon(Icons.photo_library), + ), + ), Padding( padding: const EdgeInsets.only(top: 16.0), child: FloatingActionButton( @@ -350,7 +389,7 @@ class _MyHomePageState extends State { ); }, heroTag: 'media', - tooltip: 'Pick Media from gallery', + tooltip: 'Pick Single Media from gallery', child: const Icon(Icons.photo_library), ), ), diff --git a/packages/image_picker/image_picker/example/lib/readme_excerpts.dart b/packages/image_picker/image_picker/example/lib/readme_excerpts.dart index 459bb456d785..c21c213186ce 100644 --- a/packages/image_picker/image_picker/example/lib/readme_excerpts.dart +++ b/packages/image_picker/image_picker/example/lib/readme_excerpts.dart @@ -20,8 +20,10 @@ Future> readmePickExample() async { final XFile? cameraVideo = await picker.pickVideo(source: ImageSource.camera); // Pick multiple images. final List images = await picker.pickMultiImage(); + // Pick singe image or video. + final XFile? media = await picker.pickMedia(); // Pick multiple images and videos. - final List media = await picker.pickMedia(); + final List medias = await picker.pickMultipleMedia(); // #enddocregion Pick // Return everything for the sanity check test. @@ -31,7 +33,8 @@ Future> readmePickExample() async { galleryVideo, cameraVideo, if (images.isEmpty) null else images.first, - if (media.isEmpty) null else media.first, + media, + if (medias.isEmpty) null else medias.first, ]; } diff --git a/packages/image_picker/image_picker/example/test/readme_excerpts_test.dart b/packages/image_picker/image_picker/example/test/readme_excerpts_test.dart index 56b1461ac63d..83902f53ba6a 100644 --- a/packages/image_picker/image_picker/example/test/readme_excerpts_test.dart +++ b/packages/image_picker/image_picker/example/test/readme_excerpts_test.dart @@ -52,7 +52,9 @@ class FakeImagePicker extends ImagePickerPlatform { @override Future> getMedia({required MediaSelectionOptions options}) async { - return [XFile('media')]; + return options.allowMultiple + ? [XFile('media'), XFile('medias')] + : [XFile('media')]; } @override diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index ed55b3ae37f1..c0dc79c3f960 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -283,6 +283,39 @@ class ImagePicker { ); } + /// Returns an [XFile] of the image or video that was picked. + /// The image or videos can only come from the 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. + /// + /// 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. + /// + /// If no images or videos were picked, the return value is null. + Future pickMedia({ + double? maxWidth, + double? maxHeight, + int? imageQuality, + bool requestFullMetadata = true, + }) async { + final List listMedia = await platform.getMedia( + options: MediaSelectionOptions( + imageOptions: ImageOptions.createAndValidate( + maxHeight: maxHeight, + maxWidth: maxWidth, + imageQuality: imageQuality, + requestFullMetadata: requestFullMetadata, + ), + allowMultiple: false, + ), + ); + + return listMedia.isNotEmpty ? listMedia.first : null; + } + /// Returns a [List] with the images and/or videos that were picked. /// The images and videos come from the gallery. /// @@ -290,17 +323,12 @@ class ImagePicker { /// above only support HEIC images if used in addition to a size modification, /// of which the usage is explained below. /// - /// This method is only supported on Android API 19 and above. - /// It could possibly work on API 18 and below on some devices, - /// but this is not guaranteed. - /// /// 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. /// /// If no images or videos were picked, the return value is an empty list. - Future> pickMedia({ - List? types, + Future> pickMultipleMedia({ double? maxWidth, double? maxHeight, int? imageQuality, @@ -308,11 +336,12 @@ class ImagePicker { }) { return platform.getMedia( options: MediaSelectionOptions( - types: types, - maxHeight: maxHeight, - maxWidth: maxWidth, - imageQuality: imageQuality, - requestFullMetadata: requestFullMetadata, + imageOptions: ImageOptions.createAndValidate( + maxHeight: maxHeight, + maxWidth: maxWidth, + imageQuality: imageQuality, + requestFullMetadata: requestFullMetadata, + ), ), ); } 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 a1b40cfca87c..091ae76cd528 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -632,7 +632,8 @@ void main() { mockPlatform.getMedia( options: argThat( isInstanceOf().having( - (MediaSelectionOptions options) => options.maxWidth, + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, 'maxWidth', equals(10.0)), named: 'options', @@ -641,7 +642,8 @@ void main() { mockPlatform.getMedia( options: argThat( isInstanceOf().having( - (MediaSelectionOptions options) => options.maxHeight, + (MediaSelectionOptions options) => + options.imageOptions?.maxHeight, 'maxHeight', equals(10.0)), named: 'options', @@ -650,10 +652,14 @@ void main() { mockPlatform.getMedia( options: argThat( isInstanceOf() - .having((MediaSelectionOptions options) => options.maxWidth, - 'maxWidth', equals(10.0)) .having( - (MediaSelectionOptions options) => options.maxHeight, + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxWidth', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxHeight, 'maxHeight', equals(20.0)), named: 'options', @@ -662,10 +668,14 @@ void main() { mockPlatform.getMedia( options: argThat( isInstanceOf() - .having((MediaSelectionOptions options) => options.maxWidth, - 'maxWidth', equals(10.0)) .having( - (MediaSelectionOptions options) => options.imageQuality, + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxWidth', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.imageQuality, 'imageQuality', equals(70)), named: 'options', @@ -675,11 +685,13 @@ void main() { options: argThat( isInstanceOf() .having( - (MediaSelectionOptions options) => options.maxHeight, + (MediaSelectionOptions options) => + options.imageOptions?.maxHeight, 'maxHeight', equals(10.0)) .having( - (MediaSelectionOptions options) => options.imageQuality, + (MediaSelectionOptions options) => + options.imageOptions?.imageQuality, 'imageQuality', equals(70)), named: 'options', @@ -688,12 +700,19 @@ void main() { mockPlatform.getMedia( options: argThat( isInstanceOf() - .having((MediaSelectionOptions options) => options.maxWidth, - 'maxWidth', equals(10.0)) - .having((MediaSelectionOptions options) => options.maxWidth, - 'maxHeight', equals(10.0)) .having( - (MediaSelectionOptions options) => options.imageQuality, + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxWidth', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxHeight', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.imageQuality, 'imageQuality', equals(70)), named: 'options', @@ -718,8 +737,8 @@ void main() { test('handles an empty image file response gracefully', () async { final ImagePicker picker = ImagePicker(); - expect(await picker.pickMedia(), isEmpty); - expect(await picker.pickMedia(), isEmpty); + expect(await picker.pickMedia(), isNull); + expect(await picker.pickMedia(), isNull); }); test('full metadata argument defaults to true', () async { @@ -730,7 +749,7 @@ void main() { options: argThat( isInstanceOf().having( (MediaSelectionOptions options) => - options.requestFullMetadata, + options.imageOptions?.requestFullMetadata, 'requestFullMetadata', isTrue), named: 'options', @@ -748,7 +767,186 @@ void main() { options: argThat( isInstanceOf().having( (MediaSelectionOptions options) => - options.requestFullMetadata, + options.imageOptions?.requestFullMetadata, + 'requestFullMetadata', + isFalse), + named: 'options', + ), + )); + }); + }); + + group('#pickMultipleMedia', () { + test('passes the width and height arguments correctly', () async { + final ImagePicker picker = ImagePicker(); + await picker.pickMultipleMedia(); + await picker.pickMultipleMedia( + maxWidth: 10.0, + ); + await picker.pickMultipleMedia( + maxHeight: 10.0, + ); + await picker.pickMultipleMedia( + maxWidth: 10.0, + maxHeight: 20.0, + ); + await picker.pickMultipleMedia( + maxWidth: 10.0, + imageQuality: 70, + ); + await picker.pickMultipleMedia( + maxHeight: 10.0, + imageQuality: 70, + ); + await picker.pickMultipleMedia( + maxWidth: 10.0, maxHeight: 20.0, imageQuality: 70); + + verifyInOrder([ + mockPlatform.getMedia( + options: argThat( + isInstanceOf(), + named: 'options', + ), + ), + mockPlatform.getMedia( + options: argThat( + isInstanceOf().having( + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxWidth', + equals(10.0)), + named: 'options', + ), + ), + mockPlatform.getMedia( + options: argThat( + isInstanceOf().having( + (MediaSelectionOptions options) => + options.imageOptions?.maxHeight, + 'maxHeight', + equals(10.0)), + named: 'options', + ), + ), + mockPlatform.getMedia( + options: argThat( + isInstanceOf() + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxWidth', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxHeight, + 'maxHeight', + equals(20.0)), + named: 'options', + ), + ), + mockPlatform.getMedia( + options: argThat( + isInstanceOf() + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxWidth', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.imageQuality, + 'imageQuality', + equals(70)), + named: 'options', + ), + ), + mockPlatform.getMedia( + options: argThat( + isInstanceOf() + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxHeight, + 'maxHeight', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.imageQuality, + 'imageQuality', + equals(70)), + named: 'options', + ), + ), + mockPlatform.getMedia( + options: argThat( + isInstanceOf() + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxWidth', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.maxWidth, + 'maxHeight', + equals(10.0)) + .having( + (MediaSelectionOptions options) => + options.imageOptions?.imageQuality, + 'imageQuality', + equals(70)), + named: 'options', + ), + ), + ]); + }); + + test('does not accept a negative width or height argument', () { + final ImagePicker picker = ImagePicker(); + expect( + () => picker.pickMultipleMedia(maxWidth: -1.0), + throwsArgumentError, + ); + + expect( + () => picker.pickMultipleMedia(maxHeight: -1.0), + throwsArgumentError, + ); + }); + + test('handles an empty image file response gracefully', () async { + final ImagePicker picker = ImagePicker(); + + expect(await picker.pickMultipleMedia(), isEmpty); + expect(await picker.pickMultipleMedia(), isEmpty); + }); + + test('full metadata argument defaults to true', () async { + final ImagePicker picker = ImagePicker(); + await picker.pickMultipleMedia(); + + verify(mockPlatform.getMedia( + options: argThat( + isInstanceOf().having( + (MediaSelectionOptions options) => + options.imageOptions?.requestFullMetadata, + 'requestFullMetadata', + isTrue), + named: 'options', + ), + )); + }); + + test('passes the full metadata argument correctly', () async { + final ImagePicker picker = ImagePicker(); + await picker.pickMultipleMedia( + requestFullMetadata: false, + ); + + verify(mockPlatform.getMedia( + options: argThat( + isInstanceOf().having( + (MediaSelectionOptions options) => + options.imageOptions?.requestFullMetadata, 'requestFullMetadata', isFalse), named: 'options', diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index 2476c7fcb07d..cdc76ff2985b 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -289,16 +289,18 @@ public void chooseMediaFromGallery( @NonNull Messages.ImageSelectionOptions imageOptions, @NonNull Messages.VideoSelectionOptions videoOptions, @NonNull Boolean usePhotoPicker, + @NonNull Boolean allowMultiple, @NonNull Messages.Result> result) { if (!setPendingOptionsAndResult(imageOptions, videoOptions, result)) { finishWithAlreadyActiveError(result); return; } - launchPickMediaFromGalleryIntent(usePhotoPicker); + launchPickMediaFromGalleryIntent(usePhotoPicker, allowMultiple); } - private void launchPickMediaFromGalleryIntent(Boolean useAndroidPhotoPicker) { + private void launchPickMediaFromGalleryIntent( + Boolean useAndroidPhotoPicker, Boolean allowMultiple) { Intent pickMediaIntent; if (useAndroidPhotoPicker && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { pickMediaIntent = @@ -310,9 +312,9 @@ private void launchPickMediaFromGalleryIntent(Boolean useAndroidPhotoPicker) { .build()); } else { pickMediaIntent = new Intent(Intent.ACTION_GET_CONTENT); - pickMediaIntent.setType("*/*"); + pickMediaIntent.setType("video/* image/*"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - pickMediaIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + pickMediaIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple); } } activity.startActivityForResult(pickMediaIntent, REQUEST_CODE_CHOOSE_MEDIA_FROM_GALLERY); diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index fef54c4c9617..bb1df3c1cdec 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -332,6 +332,7 @@ public void pickMedia( @NonNull Messages.ImageSelectionOptions imageOptions, @NonNull Messages.VideoSelectionOptions videoOptions, @NonNull Boolean usePhotoPicker, + @NonNull Boolean allowMultiple, @NonNull Result> result) { ImagePickerDelegate delegate = getImagePickerDelegate(); if (delegate == null) { @@ -340,7 +341,7 @@ public void pickMedia( "no_activity", "image_picker plugin requires a foreground activity.", null)); return; } - delegate.chooseMediaFromGallery(imageOptions, videoOptions, usePhotoPicker, result); + delegate.chooseMediaFromGallery(imageOptions, videoOptions, usePhotoPicker, allowMultiple, result); } @Override diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java index f2453da819de..0e97e7a4da1a 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/Messages.java @@ -582,7 +582,6 @@ void pickVideos( @NonNull Boolean allowMultiple, @NonNull Boolean usePhotoPicker, @NonNull Result> result); - /** * Selects images and videos and returns their paths. * @@ -593,6 +592,7 @@ void pickMedia( @NonNull ImageSelectionOptions imageOptions, @NonNull VideoSelectionOptions videoOptions, @NonNull Boolean usePhotoPicker, + @NonNull Boolean allowMultiple, @NonNull Result> result); /** Returns results from a previous app session, if any. */ @Nullable @@ -682,6 +682,7 @@ public void error(Throwable error) { ImageSelectionOptions imageOptionsArg = (ImageSelectionOptions) args.get(0); VideoSelectionOptions videoOptionsArg = (VideoSelectionOptions) args.get(1); Boolean usePhotoPickerArg = (Boolean) args.get(2); + Boolean allowMultipleArg = (Boolean) args.get(3); Result> resultCallback = new Result>() { public void success(List result) { @@ -695,7 +696,12 @@ public void error(Throwable error) { } }; - api.pickMedia(imageOptionsArg, videoOptionsArg, usePhotoPickerArg, resultCallback); + api.pickMedia( + imageOptionsArg, + videoOptionsArg, + usePhotoPickerArg, + allowMultipleArg, + resultCallback); }); } else { channel.setMessageHandler(null); diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 99b72c8f4089..35fac206477f 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -158,7 +158,7 @@ public void chooseMediaFromGallery_whenPendingResultExists_finishesWithAlreadyAc createDelegateWithPendingResultAndOptions(DEFAULT_IMAGE_OPTIONS, null); delegate.chooseMediaFromGallery( - DEFAULT_IMAGE_OPTIONS, DEFAULT_VIDEO_OPTIONS, false, mockResult); + DEFAULT_IMAGE_OPTIONS, DEFAULT_VIDEO_OPTIONS, false, false, mockResult); verifyFinishedWithAlreadyActiveError(); verifyNoMoreInteractions(mockResult); diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java index f6aece6dc02f..b071581fc54c 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java @@ -158,15 +158,15 @@ public void pickImages_usingPhotoPicker_invokesChooseMultiImageFromGallery() { @Test public void pickMedia_invokesChooseMediaFromGallery() { - plugin.pickMedia(DEFAULT_IMAGE_OPTIONS, DEFAULT_VIDEO_OPTIONS, false, mockResult); - verify(mockImagePickerDelegate).chooseMediaFromGallery(any(), any(), eq(false), any()); + plugin.pickMedia(DEFAULT_IMAGE_OPTIONS, DEFAULT_VIDEO_OPTIONS, false, false, mockResult); + verify(mockImagePickerDelegate).chooseMediaFromGallery(any(), any(), eq(false), eq(false), any()); verifyNoInteractions(mockResult); } @Test public void pickMedia_usingPhotoPicker_invokesChooseMediaFromGallery() { - plugin.pickMedia(DEFAULT_IMAGE_OPTIONS, DEFAULT_VIDEO_OPTIONS, true, mockResult); - verify(mockImagePickerDelegate).chooseMediaFromGallery(any(), any(), eq(true), any()); + plugin.pickMedia(DEFAULT_IMAGE_OPTIONS, DEFAULT_VIDEO_OPTIONS, true, false, mockResult); + verify(mockImagePickerDelegate).chooseMediaFromGallery(any(), any(), eq(true), eq(false), any()); verifyNoInteractions(mockResult); } diff --git a/packages/image_picker/image_picker_android/lib/image_picker_android.dart b/packages/image_picker/image_picker_android/lib/image_picker_android.dart index 21841bac3618..d40fce27f41b 100644 --- a/packages/image_picker/image_picker_android/lib/image_picker_android.dart +++ b/packages/image_picker/image_picker_android/lib/image_picker_android.dart @@ -119,6 +119,7 @@ class ImagePickerAndroid extends ImagePickerPlatform { } Future> _getMediaPaths({ + required bool allowMultiple, double? maxWidth, double? maxHeight, int? imageQuality, @@ -143,6 +144,7 @@ class ImagePickerAndroid extends ImagePickerPlatform { quality: imageQuality ?? 100), VideoSelectionOptions(), useAndroidPhotoPicker, + allowMultiple, ); } @@ -230,9 +232,10 @@ class ImagePickerAndroid extends ImagePickerPlatform { required MediaSelectionOptions options, }) async { final List paths = await _getMediaPaths( - maxHeight: options.maxHeight, - maxWidth: options.maxWidth, - imageQuality: options.imageQuality, + allowMultiple: options.allowMultiple, + maxHeight: options.imageOptions?.maxHeight, + maxWidth: options.imageOptions?.maxWidth, + imageQuality: options.imageOptions?.imageQuality, ); return paths.map((dynamic path) => XFile(path as String)).toList(); } diff --git a/packages/image_picker/image_picker_android/lib/src/messages.g.dart b/packages/image_picker/image_picker_android/lib/src/messages.g.dart index e8f6f4b7792b..63d45c9c9963 100644 --- a/packages/image_picker/image_picker_android/lib/src/messages.g.dart +++ b/packages/image_picker/image_picker_android/lib/src/messages.g.dart @@ -317,14 +317,20 @@ class ImagePickerApi { /// /// Elements must not be null, by convention. See /// https://github.com/flutter/flutter/issues/97848 - Future> pickMedia(ImageSelectionOptions arg_imageOptions, - VideoSelectionOptions arg_videoOptions, bool arg_usePhotoPicker) async { + Future> pickMedia( + ImageSelectionOptions arg_imageOptions, + VideoSelectionOptions arg_videoOptions, + bool arg_usePhotoPicker, + bool arg_allowMultiple) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send( - [arg_imageOptions, arg_videoOptions, arg_usePhotoPicker]) - as List?; + final List? replyList = await channel.send([ + arg_imageOptions, + arg_videoOptions, + arg_usePhotoPicker, + arg_allowMultiple + ]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/image_picker/image_picker_android/pigeons/messages.dart b/packages/image_picker/image_picker_android/pigeons/messages.dart index a2e0b44168ff..d10c541efd18 100644 --- a/packages/image_picker/image_picker_android/pigeons/messages.dart +++ b/packages/image_picker/image_picker_android/pigeons/messages.dart @@ -117,6 +117,7 @@ abstract class ImagePickerApi { VideoSelectionOptions videoOptions, // this isn't needed, but might be in the future bool usePhotoPicker, + bool allowMultiple, ); /// Returns results from a previous app session, if any. diff --git a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart index 4386007dd600..ed97bd8d33d3 100644 --- a/packages/image_picker/image_picker_android/test/image_picker_android_test.dart +++ b/packages/image_picker/image_picker_android/test/image_picker_android_test.dart @@ -682,9 +682,11 @@ void main() { test('passes image option arguments correctly', () async { await picker.getMedia( options: MediaSelectionOptions( - maxWidth: 10.0, - maxHeight: 20.0, - imageQuality: 70, + imageOptions: const ImageOptions( + maxWidth: 10.0, + maxHeight: 20.0, + imageQuality: 70, + ), )); expect(api.passedImageOptions?.maxWidth, 10.0); @@ -694,25 +696,40 @@ void main() { test('does not accept a negative width or height argument', () { expect( - () => picker.getMedia(options: MediaSelectionOptions(maxWidth: -1.0)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: const ImageOptions(maxWidth: -1.0), + ), + ), throwsArgumentError, ); expect( - () => picker.getMedia(options: MediaSelectionOptions(maxHeight: -1.0)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: const ImageOptions(maxHeight: -1.0), + ), + ), throwsArgumentError, ); }); test('does not accept an invalid imageQuality argument', () { expect( - () => picker.getMedia(options: MediaSelectionOptions(imageQuality: -1)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: const ImageOptions(imageQuality: -1), + ), + ), throwsArgumentError, ); expect( - () => - picker.getMedia(options: MediaSelectionOptions(imageQuality: 101)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: const ImageOptions(imageQuality: 101), + ), + ), throwsArgumentError, ); }); @@ -916,11 +933,16 @@ class _FakeImagePickerApi implements ImagePickerApi { } @override - Future> pickMedia(ImageSelectionOptions imageOptions, - VideoSelectionOptions options, bool usePhotoPicker) async { + Future> pickMedia( + ImageSelectionOptions imageOptions, + VideoSelectionOptions options, + bool usePhotoPicker, + bool allowMultiple, + ) async { lastCall = _LastPickType.image; passedImageOptions = imageOptions; passedPhotoPickerFlag = usePhotoPicker; + passedAllowMultiple = allowMultiple; return returnValue as List? ?? []; } diff --git a/packages/image_picker/image_picker_android/test/test_api.g.dart b/packages/image_picker/image_picker_android/test/test_api.g.dart index 7e059ce5b6f5..deb7c06743a2 100644 --- a/packages/image_picker/image_picker_android/test/test_api.g.dart +++ b/packages/image_picker/image_picker_android/test/test_api.g.dart @@ -79,8 +79,11 @@ abstract class TestHostImagePickerApi { /// /// Elements must not be null, by convention. See /// https://github.com/flutter/flutter/issues/97848 - Future> pickMedia(ImageSelectionOptions imageOptions, - VideoSelectionOptions videoOptions, bool usePhotoPicker); + Future> pickMedia( + ImageSelectionOptions imageOptions, + VideoSelectionOptions videoOptions, + bool usePhotoPicker, + bool allowMultiple); /// Returns results from a previous app session, if any. CacheRetrievalResult? retrieveLostResults(); @@ -180,8 +183,11 @@ abstract class TestHostImagePickerApi { final bool? arg_usePhotoPicker = (args[2] as bool?); assert(arg_usePhotoPicker != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null bool.'); - final List output = await api.pickMedia( - arg_imageOptions!, arg_videoOptions!, arg_usePhotoPicker!); + final bool? arg_allowMultiple = (args[3] as bool?); + assert(arg_allowMultiple != null, + 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null bool.'); + final List output = await api.pickMedia(arg_imageOptions!, + arg_videoOptions!, arg_usePhotoPicker!, arg_allowMultiple!); return [output]; }); } diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index 924e812ea362..f6572c99f576 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -196,14 +196,14 @@ class ImagePickerPlugin extends ImagePickerPlatform { }) async { final List images = await getFiles( accept: '$_kAcceptImageMimeType,$_kAcceptVideoMimeType', - multiple: true, + multiple: options.allowMultiple, ); final Iterable> resized = images.map( (XFile image) => _imageResizer.resizeImageIfNeeded( image, - options.maxWidth, - options.maxHeight, - options.imageQuality, + options.imageOptions?.maxWidth, + options.imageOptions?.maxHeight, + options.imageOptions?.imageQuality, ), ); diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m index 946613960d9b..1ab79b71ae93 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -199,6 +199,7 @@ - (void)testPickMediaShouldUseUIImagePickerControllerOnPreiOS14 { pickMediaWithMaxSize:[FLTMaxSize makeWithWidth:@(100) height:@(200)] quality:@(50) fullMetadata:@YES + allowMultiple:@YES completion:^(NSArray *_Nullable result, FlutterError *_Nullable error){ }]; OCMVerify(times(1), @@ -251,6 +252,8 @@ - (void)testPickMediaWithoutFullMetadata { pickMediaWithMaxSize:[[FLTMaxSize alloc] init] quality:nil fullMetadata:@NO + allowMultiple:@YES + completion:^(NSArray *_Nullable result, FlutterError *_Nullable error){ }]; diff --git a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m index 127c1393dc63..fd4e1bd75c5d 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/FLTImagePickerPlugin.m @@ -220,6 +220,7 @@ - (void)pickMultiImageWithMaxSize:(nonnull FLTMaxSize *)maxSize - (void)pickMediaWithMaxSize:(nonnull FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality fullMetadata:(NSNumber *)fullMetadata + allowMultiple:(NSNumber *)allowMultiple completion:(nonnull void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion { FLTImagePickerMethodCallContext *context = @@ -228,6 +229,9 @@ - (void)pickMediaWithMaxSize:(nonnull FLTMaxSize *)maxSize context.imageQuality = imageQuality; context.requestFullMetadata = [fullMetadata boolValue]; context.includeVideo = YES; + if (!allowMultiple) { + context.maxImageCount = 1; + } if (@available(iOS 14, *)) { [self launchPHPickerWithContext:context]; diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h index 794036c08ea5..1a59a21dc41d 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h +++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.h @@ -61,6 +61,7 @@ NSObject *FLTImagePickerApiGetCodec(void); - (void)pickMediaWithMaxSize:(FLTMaxSize *)maxSize quality:(nullable NSNumber *)imageQuality fullMetadata:(NSNumber *)requestFullMetadata + allowMultiple:(NSNumber *)allowMultiple completion: (void (^)(NSArray *_Nullable, FlutterError *_Nullable))completion; @end diff --git a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m index 35f7e10452fd..f6e57c992b62 100644 --- a/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m +++ b/packages/image_picker/image_picker_ios/ios/Classes/messages.g.m @@ -227,19 +227,21 @@ void FLTImagePickerApiSetup(id binaryMessenger, binaryMessenger:binaryMessenger codec:FLTImagePickerApiGetCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(pickMediaWithMaxSize: - quality:fullMetadata:completion:)], + NSCAssert([api respondsToSelector:@selector + (pickMediaWithMaxSize:quality:fullMetadata:allowMultiple:completion:)], @"FLTImagePickerApi api (%@) doesn't respond to " - @"@selector(pickMediaWithMaxSize:quality:fullMetadata:completion:)", + @"@selector(pickMediaWithMaxSize:quality:fullMetadata:allowMultiple:completion:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FLTMaxSize *arg_maxSize = GetNullableObjectAtIndex(args, 0); NSNumber *arg_imageQuality = GetNullableObjectAtIndex(args, 1); NSNumber *arg_requestFullMetadata = GetNullableObjectAtIndex(args, 2); + NSNumber *arg_allowMultiple = GetNullableObjectAtIndex(args, 3); [api pickMediaWithMaxSize:arg_maxSize quality:arg_imageQuality fullMetadata:arg_requestFullMetadata + allowMultiple:arg_allowMultiple completion:^(NSArray *_Nullable output, FlutterError *_Nullable error) { callback(wrapResult(output, error)); diff --git a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart index 46735eb5213b..1084c02c4642 100644 --- a/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart +++ b/packages/image_picker/image_picker_ios/lib/image_picker_ios.dart @@ -176,6 +176,7 @@ class ImagePickerIOS extends ImagePickerPlatform { } Future> _getMediaPaths({ + required bool allowMultiple, double? maxWidth, double? maxHeight, int? imageQuality, @@ -198,6 +199,7 @@ class ImagePickerIOS extends ImagePickerPlatform { MaxSize(width: maxWidth, height: maxHeight), imageQuality, options.requestFullMetadata, + allowMultiple, ); } @@ -206,9 +208,10 @@ class ImagePickerIOS extends ImagePickerPlatform { required MediaSelectionOptions options, }) async { final List paths = await _getMediaPaths( - maxHeight: options.maxHeight, - maxWidth: options.maxWidth, - imageQuality: options.imageQuality, + maxHeight: options.imageOptions?.maxHeight, + maxWidth: options.imageOptions?.maxWidth, + imageQuality: options.imageOptions?.imageQuality, + allowMultiple: options.allowMultiple, ); return paths.map((dynamic path) => XFile(path as String)).toList(); } diff --git a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart index 8fa20dd938c6..a6b4ea8e4203 100644 --- a/packages/image_picker/image_picker_ios/lib/src/messages.g.dart +++ b/packages/image_picker/image_picker_ios/lib/src/messages.g.dart @@ -187,13 +187,16 @@ class ImagePickerApi { /// Selects images and videos and returns their paths. Future> pickMedia(MaxSize arg_maxSize, int? arg_imageQuality, - bool arg_requestFullMetadata) async { + bool arg_requestFullMetadata, bool arg_allowMultiple) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.ImagePickerApi.pickMedia', codec, binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send( - [arg_maxSize, arg_imageQuality, arg_requestFullMetadata]) - as List?; + final List? replyList = await channel.send([ + arg_maxSize, + arg_imageQuality, + arg_requestFullMetadata, + arg_allowMultiple + ]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/image_picker/image_picker_ios/pigeons/messages.dart b/packages/image_picker/image_picker_ios/pigeons/messages.dart index d2af69e03bda..0bb2ad59d3ee 100644 --- a/packages/image_picker/image_picker_ios/pigeons/messages.dart +++ b/packages/image_picker/image_picker_ios/pigeons/messages.dart @@ -48,7 +48,7 @@ abstract class ImagePickerApi { /// Selects images and videos and returns their paths. @async - @ObjCSelector('pickMediaWithMaxSize:quality:fullMetadata:') - List pickMedia( - MaxSize maxSize, int? imageQuality, bool requestFullMetadata); + @ObjCSelector('pickMediaWithMaxSize:quality:fullMetadata:allowMultiple:') + List pickMedia(MaxSize maxSize, int? imageQuality, + bool requestFullMetadata, bool allowMultiple); } diff --git a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart index b533c415ba9f..0011f371338a 100644 --- a/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart +++ b/packages/image_picker/image_picker_ios/test/image_picker_ios_test.dart @@ -76,12 +76,14 @@ class _ApiLogger implements TestHostImagePickerApi { MaxSize maxSize, int? imageQuality, bool requestFullMetadata, + bool allowMultiple, ) async { calls.add(_LoggedMethodCall('pickMedia', arguments: { 'maxWidth': maxSize.width, 'maxHeight': maxSize.height, 'imageQuality': imageQuality, 'requestFullMetadata': requestFullMetadata, + 'allowMultiple': allowMultiple, })); return returnValue as List; } @@ -906,6 +908,7 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, + 'allowMultiple': true }), ], ); @@ -916,32 +919,44 @@ void main() { await picker.getMedia(options: MediaSelectionOptions()); await picker.getMedia( options: MediaSelectionOptions( - maxWidth: 10.0, + imageOptions: ImageOptions.createAndValidate( + maxWidth: 10.0, + ), )); await picker.getMedia( options: MediaSelectionOptions( - maxHeight: 10.0, + imageOptions: ImageOptions.createAndValidate( + maxHeight: 10.0, + ), )); await picker.getMedia( options: MediaSelectionOptions( - maxWidth: 10.0, - maxHeight: 20.0, + imageOptions: ImageOptions.createAndValidate( + maxWidth: 10.0, + maxHeight: 20.0, + ), )); await picker.getMedia( options: MediaSelectionOptions( - maxWidth: 10.0, - imageQuality: 70, + imageOptions: ImageOptions.createAndValidate( + maxWidth: 10.0, + imageQuality: 70, + ), )); await picker.getMedia( options: MediaSelectionOptions( - maxHeight: 10.0, - imageQuality: 70, + imageOptions: ImageOptions.createAndValidate( + maxHeight: 10.0, + imageQuality: 70, + ), )); await picker.getMedia( options: MediaSelectionOptions( - maxWidth: 10.0, - maxHeight: 20.0, - imageQuality: 70, + imageOptions: ImageOptions.createAndValidate( + maxWidth: 10.0, + maxHeight: 20.0, + imageQuality: 70, + ), )); expect( @@ -952,42 +967,49 @@ void main() { 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, + 'allowMultiple': true }), const _LoggedMethodCall('pickMedia', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': null, 'requestFullMetadata': true, + 'allowMultiple': true }), const _LoggedMethodCall('pickMedia', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': null, 'requestFullMetadata': true, + 'allowMultiple': true }), const _LoggedMethodCall('pickMedia', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': null, 'requestFullMetadata': true, + 'allowMultiple': true }), const _LoggedMethodCall('pickMedia', arguments: { 'maxWidth': 10.0, 'maxHeight': null, 'imageQuality': 70, 'requestFullMetadata': true, + 'allowMultiple': true }), const _LoggedMethodCall('pickMedia', arguments: { 'maxWidth': null, 'maxHeight': 10.0, 'imageQuality': 70, 'requestFullMetadata': true, + 'allowMultiple': true }), const _LoggedMethodCall('pickMedia', arguments: { 'maxWidth': 10.0, 'maxHeight': 20.0, 'imageQuality': 70, 'requestFullMetadata': true, + 'allowMultiple': true }), ], ); @@ -996,12 +1018,18 @@ void main() { test('does not accept a negative width or height argument', () { log.returnValue = ['0', '1']; expect( - () => picker.getMedia(options: MediaSelectionOptions(maxWidth: -1.0)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: ImageOptions.createAndValidate(maxWidth: -1.0), + )), throwsArgumentError, ); expect( - () => picker.getMedia(options: MediaSelectionOptions(maxHeight: -1.0)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: ImageOptions.createAndValidate(maxHeight: -1.0), + )), throwsArgumentError, ); }); @@ -1009,13 +1037,18 @@ void main() { test('does not accept a invalid imageQuality argument', () { log.returnValue = ['0', '1']; expect( - () => picker.getMedia(options: MediaSelectionOptions(imageQuality: -1)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: ImageOptions.createAndValidate(imageQuality: -1), + )), throwsArgumentError, ); expect( - () => - picker.getMedia(options: MediaSelectionOptions(imageQuality: 101)), + () => picker.getMedia( + options: MediaSelectionOptions( + imageOptions: ImageOptions.createAndValidate(imageQuality: 101), + )), throwsArgumentError, ); }); diff --git a/packages/image_picker/image_picker_ios/test/test_api.g.dart b/packages/image_picker/image_picker_ios/test/test_api.g.dart index 2c49ddf34a72..2f54a125e12e 100644 --- a/packages/image_picker/image_picker_ios/test/test_api.g.dart +++ b/packages/image_picker/image_picker_ios/test/test_api.g.dart @@ -56,8 +56,8 @@ abstract class TestHostImagePickerApi { SourceSpecification source, int? maxDurationSeconds); /// Selects images and videos and returns their paths. - Future> pickMedia( - MaxSize maxSize, int? imageQuality, bool requestFullMetadata); + Future> pickMedia(MaxSize maxSize, int? imageQuality, + bool requestFullMetadata, bool allowMultiple); static void setup(TestHostImagePickerApi? api, {BinaryMessenger? binaryMessenger}) { @@ -165,8 +165,11 @@ abstract class TestHostImagePickerApi { final bool? arg_requestFullMetadata = (args[2] as bool?); assert(arg_requestFullMetadata != null, 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null bool.'); - final List output = await api.pickMedia( - arg_maxSize!, arg_imageQuality, arg_requestFullMetadata!); + final bool? arg_allowMultiple = (args[3] as bool?); + assert(arg_allowMultiple != null, + 'Argument for dev.flutter.pigeon.ImagePickerApi.pickMedia was null, expected non-null bool.'); + final List output = await api.pickMedia(arg_maxSize!, + arg_imageQuality, arg_requestFullMetadata!, arg_allowMultiple!); return [output]; }); } 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 e1b4d8a8b8ab..ef7cafdeb270 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 @@ -257,17 +257,16 @@ class MethodChannelImagePicker extends ImagePickerPlatform { MediaSelectionOptions? options, }) async { options ??= MediaSelectionOptions(); - final int? imageQuality = options.imageQuality; - - final double? maxImageWidth = options.maxWidth; - - final double? maxImageHeight = options.maxHeight; + final ImageOptions? imageOptions = options.imageOptions; + final int? imageQuality = imageOptions?.imageQuality; + final double? maxImageWidth = imageOptions?.maxWidth; + final double? maxImageHeight = imageOptions?.maxHeight; final Map args = { - 'types': options.types.map(serializeMediaSelectionType).toList(), 'maxImageWidth': maxImageWidth, 'maxImageHeight': maxImageHeight, 'imageQuality': imageQuality, + 'allowMultiple': options.allowMultiple, }; List? paths; paths = await _channel 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 61662d8d414a..bdffeeb4c247 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 @@ -222,10 +222,6 @@ abstract class ImagePickerPlatform extends PlatformInterface { /// above only support HEIC images if used in addition to a size modification, /// of which the usage is explained below. /// - /// This method is only supported on Android API 19 and above. - /// It could possibly work on API 18 and below on some devices, - /// but this is not guaranteed. - /// /// 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. diff --git a/packages/image_picker/image_picker_platform_interface/lib/src/types/media_selection_options.dart b/packages/image_picker/image_picker_platform_interface/lib/src/types/media_selection_options.dart index 1448821aa536..19e64d4dff11 100644 --- a/packages/image_picker/image_picker_platform_interface/lib/src/types/media_selection_options.dart +++ b/packages/image_picker/image_picker_platform_interface/lib/src/types/media_selection_options.dart @@ -5,26 +5,16 @@ import '../../image_picker_platform_interface.dart'; /// Specifies options class for selecting items when using [ImagePickerPlatform.getMedia]. -/// -/// This class inheritance is a byproduct of the api changing over time. -/// It exists solely to avoid breaking changes. -class MediaSelectionOptions extends ImageOptions { +class MediaSelectionOptions { /// Construct a new MediaSelectionOptions instance. MediaSelectionOptions({ - super.maxHeight, - super.maxWidth, - super.imageQuality, - super.requestFullMetadata, - List? types, - }) : types = types ?? - [ - MediaSelectionType.image, - MediaSelectionType.video - ], - super.createAndValidate(); + this.imageOptions, + this.allowMultiple = true, + }); - /// The types of allowed media to be picked. - /// - /// Allows all types by default. - final List types; + /// Options that will apply to images upon selection. + final ImageOptions? imageOptions; + + /// Whether to allow for selecting multiple media. Defaults to true. + final bool allowMultiple; } 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 347c012cf629..d77c251d2ab9 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 @@ -884,10 +884,7 @@ void main() { 'maxImageWidth': null, 'maxImageHeight': null, 'imageQuality': null, - 'types': [ - 'image', - 'video', - ], + 'allowMultiple': true, }), ], ); @@ -901,17 +898,23 @@ void main() { returnValue = ['0']; await picker.getMedia( options: MediaSelectionOptions( - maxWidth: 10.0, + imageOptions: ImageOptions.createAndValidate( + maxWidth: 10.0, + ), ), ); await picker.getMedia( options: MediaSelectionOptions( - maxHeight: 10.0, + imageOptions: ImageOptions.createAndValidate( + maxHeight: 10.0, + ), ), ); await picker.getMedia( options: MediaSelectionOptions( - imageQuality: 70, + imageOptions: ImageOptions.createAndValidate( + imageQuality: 70, + ), ), ); @@ -922,37 +925,25 @@ void main() { 'maxImageWidth': null, 'maxImageHeight': null, 'imageQuality': null, - 'types': [ - 'image', - 'video', - ], + 'allowMultiple': true, }), isMethodCall('pickMedia', arguments: { 'maxImageWidth': 10.0, 'maxImageHeight': null, 'imageQuality': null, - 'types': [ - 'image', - 'video', - ], + 'allowMultiple': true, }), isMethodCall('pickMedia', arguments: { 'maxImageWidth': null, 'maxImageHeight': 10.0, 'imageQuality': null, - 'types': [ - 'image', - 'video', - ], + 'allowMultiple': true, }), isMethodCall('pickMedia', arguments: { 'maxImageWidth': null, 'maxImageHeight': null, 'imageQuality': 70, - 'types': [ - 'image', - 'video', - ], + 'allowMultiple': true, }), ], ); @@ -963,7 +954,9 @@ void main() { expect( () => picker.getMedia( options: MediaSelectionOptions( - maxWidth: -1.0, + imageOptions: ImageOptions.createAndValidate( + maxWidth: -1.0, + ), ), ), throwsArgumentError, @@ -972,7 +965,9 @@ void main() { expect( () => picker.getMedia( options: MediaSelectionOptions( - maxHeight: -1.0, + imageOptions: ImageOptions.createAndValidate( + maxHeight: -1.0, + ), ), ), throwsArgumentError, @@ -984,7 +979,9 @@ void main() { expect( () => picker.getMedia( options: MediaSelectionOptions( - imageQuality: -1, + imageOptions: ImageOptions.createAndValidate( + imageQuality: -1, + ), ), ), throwsArgumentError, @@ -993,7 +990,9 @@ void main() { expect( () => picker.getMedia( options: MediaSelectionOptions( - imageQuality: 101, + imageOptions: ImageOptions.createAndValidate( + imageQuality: 101, + ), ), ), throwsArgumentError, diff --git a/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart b/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart index 8a75550997ed..4a0061c5118c 100644 --- a/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart +++ b/packages/image_picker/image_picker_windows/lib/image_picker_windows.dart @@ -173,8 +173,18 @@ class ImagePickerWindows extends ImagePickerPlatform { const XTypeGroup typeGroup = XTypeGroup( label: 'images and videos', extensions: [...imageFormats, ...videoFormats]); - final List files = await fileSelector - .openFiles(acceptedTypeGroups: [typeGroup]); + + List files; + + if (options.allowMultiple) { + files = await fileSelector + .openFiles(acceptedTypeGroups: [typeGroup]); + } else { + files = []; + final XFile? file = await fileSelector + .openFile(acceptedTypeGroups: [typeGroup]); + file ?? files.add(file!); + } return files; } }