diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index 55ba7213d31..8c8f1d4a29c 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 1.2.0 +* Adds `pickMultiVideo` to allow selecting multiple videos from the gallery. * Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. ## 1.1.2 diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index a836c0a8433..f1ec8b76341 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -61,7 +61,7 @@ class _MyHomePageState extends State { Future _playVideo(XFile? file) async { if (file != null && mounted) { await _disposeVideoController(); - late VideoPlayerController controller; + final VideoPlayerController controller; if (kIsWeb) { controller = VideoPlayerController.networkUrl(Uri.parse(file.path)); } else { @@ -85,7 +85,7 @@ class _MyHomePageState extends State { Future _onImageButtonPressed( ImageSource source, { required BuildContext context, - bool isMultiImage = false, + bool allowMultiple = false, bool isMedia = false, }) async { if (_controller != null) { @@ -93,10 +93,17 @@ class _MyHomePageState extends State { } if (context.mounted) { if (isVideo) { - final XFile? file = await _picker.pickVideo( - source: source, maxDuration: const Duration(seconds: 10)); - await _playVideo(file); - } else if (isMultiImage) { + final List files; + if (allowMultiple) { + files = await _picker.pickMultiVideo(); + } else { + final XFile? file = await _picker.pickVideo( + source: source, maxDuration: const Duration(seconds: 10)); + files = [if (file != null) file]; + } + // Just play the first file, to keep the example simple. + await _playVideo(files.firstOrNull); + } else if (allowMultiple) { await _displayPickImageDialog(context, true, (double? maxWidth, double? maxHeight, int? quality, int? limit) async { try { @@ -349,7 +356,7 @@ class _MyHomePageState extends State { _onImageButtonPressed(ImageSource.gallery, context: context); }, heroTag: 'image0', - tooltip: 'Pick Image from gallery', + tooltip: 'Pick image from gallery', child: const Icon(Icons.photo), ), ), @@ -361,12 +368,11 @@ class _MyHomePageState extends State { _onImageButtonPressed( ImageSource.gallery, context: context, - isMultiImage: true, - isMedia: true, + allowMultiple: true, ); }, - heroTag: 'multipleMedia', - tooltip: 'Pick Multiple Media from gallery', + heroTag: 'image1', + tooltip: 'Pick multiple images', child: const Icon(Icons.photo_library), ), ), @@ -382,8 +388,8 @@ class _MyHomePageState extends State { ); }, heroTag: 'media', - tooltip: 'Pick Single Media from gallery', - child: const Icon(Icons.photo_library), + tooltip: 'Pick item from gallery', + child: const Icon(Icons.photo_outlined), ), ), Padding( @@ -394,12 +400,13 @@ class _MyHomePageState extends State { _onImageButtonPressed( ImageSource.gallery, context: context, - isMultiImage: true, + allowMultiple: true, + isMedia: true, ); }, - heroTag: 'image1', - tooltip: 'Pick Multiple Image from gallery', - child: const Icon(Icons.photo_library), + heroTag: 'multipleMedia', + tooltip: 'Pick multiple items', + child: const Icon(Icons.photo_library_outlined), ), ), if (_picker.supportsImageSource(ImageSource.camera)) @@ -411,7 +418,7 @@ class _MyHomePageState extends State { _onImageButtonPressed(ImageSource.camera, context: context); }, heroTag: 'image2', - tooltip: 'Take a Photo', + tooltip: 'Take a photo', child: const Icon(Icons.camera_alt), ), ), @@ -423,8 +430,22 @@ class _MyHomePageState extends State { isVideo = true; _onImageButtonPressed(ImageSource.gallery, context: context); }, - heroTag: 'video0', - tooltip: 'Pick Video from gallery', + heroTag: 'video', + tooltip: 'Pick video from gallery', + child: const Icon(Icons.video_file), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + backgroundColor: Colors.red, + onPressed: () { + isVideo = true; + _onImageButtonPressed(ImageSource.gallery, + context: context, allowMultiple: true); + }, + heroTag: 'multiVideo', + tooltip: 'Pick multiple videos', child: const Icon(Icons.video_library), ), ), @@ -437,8 +458,8 @@ class _MyHomePageState extends State { isVideo = true; _onImageButtonPressed(ImageSource.camera, context: context); }, - heroTag: 'video1', - tooltip: 'Take a Video', + heroTag: 'takeVideo', + tooltip: 'Take a video', child: const Icon(Icons.videocam), ), ), diff --git a/packages/image_picker/image_picker/example/pubspec.yaml b/packages/image_picker/image_picker/example/pubspec.yaml index fb36a9fd71d..b69c9124cea 100644 --- a/packages/image_picker/image_picker/example/pubspec.yaml +++ b/packages/image_picker/image_picker/example/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - image_picker_platform_interface: ^2.10.0 + image_picker_platform_interface: ^2.11.0 mime: ^1.0.4 video_player: ^2.7.0 diff --git a/packages/image_picker/image_picker/lib/image_picker.dart b/packages/image_picker/image_picker/lib/image_picker.dart index 3e51cc13dac..b5473d6f0eb 100755 --- a/packages/image_picker/image_picker/lib/image_picker.dart +++ b/packages/image_picker/image_picker/lib/image_picker.dart @@ -298,6 +298,34 @@ class ImagePicker { ); } + /// Returns a [List] of the videos 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. + /// + /// The videos come from the gallery. + /// + /// The [maxDuration] argument specifies the maximum duration of the captured + /// videos. If no [maxDuration] is specified, the maximum duration will be + /// infinite. This value may be ignored by platforms that cannot support it. + /// + /// The `limit` parameter modifies the maximum number of videos that can be + /// selected. This value may be ignored by platforms that cannot support it. + /// + /// The method can throw a [PlatformException] if the video selection process + /// fails. + Future> pickMultiVideo({ + Duration? maxDuration, + int? limit, + }) { + return platform.getMultiVideoWithOptions( + options: MultiVideoPickerOptions( + maxDuration: maxDuration, + limit: limit, + ), + ); + } + /// Retrieve the lost [XFile] when [pickImage], [pickMultiImage] or [pickVideo] failed because the MainActivity /// is destroyed. (Android only) /// diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 4c3e4666422..28cef5b855e 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 1.1.2 +version: 1.2.0 environment: sdk: ^3.6.0 @@ -28,13 +28,13 @@ flutter: dependencies: flutter: sdk: flutter - image_picker_android: ^0.8.7 - image_picker_for_web: ">=2.2.0 <4.0.0" - image_picker_ios: ^0.8.8 - image_picker_linux: ^0.2.1 - image_picker_macos: ^0.2.1 - image_picker_platform_interface: ^2.10.0 - image_picker_windows: ^0.2.1 + image_picker_android: ^0.8.13 + image_picker_for_web: ^3.1.0 + image_picker_ios: ^0.8.13 + image_picker_linux: ^0.2.2 + image_picker_macos: ^0.2.2 + image_picker_platform_interface: ^2.11.0 + image_picker_windows: ^0.2.2 dev_dependencies: build_runner: ^2.1.10 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 766b3808998..9613d29f1d2 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.dart @@ -397,6 +397,45 @@ void main() { expect(response.exception!.message, 'test_error_message'); }); }); + + group('#pickMultiVideo', () { + setUp(() { + when(mockPlatform.getMultiVideoWithOptions( + options: anyNamed('options'), + )).thenAnswer((Invocation _) async => []); + }); + + test('passes the arguments correctly', () async { + final ImagePicker picker = ImagePicker(); + await picker.pickMultiVideo(); + await picker.pickMultiVideo(maxDuration: const Duration(seconds: 10)); + await picker.pickMultiVideo(limit: 5); + + verifyInOrder([ + mockPlatform.getMultiVideoWithOptions( + options: argThat( + isInstanceOf(), + named: 'options', + )), + mockPlatform.getMultiVideoWithOptions( + options: argThat( + isInstanceOf().having( + (MultiVideoPickerOptions options) => options.maxDuration, + 'maxDuration', + equals(const Duration(seconds: 10))), + named: 'options', + )), + mockPlatform.getMultiVideoWithOptions( + options: argThat( + isInstanceOf().having( + (MultiVideoPickerOptions options) => options.limit, + 'limit', + equals(5)), + named: 'options', + )), + ]); + }); + }); }); group('#Multi images', () { diff --git a/packages/image_picker/image_picker/test/image_picker_test.mocks.dart b/packages/image_picker/image_picker/test/image_picker_test.mocks.dart index dcd89d9ac45..4277592f06b 100644 --- a/packages/image_picker/image_picker/test/image_picker_test.mocks.dart +++ b/packages/image_picker/image_picker/test/image_picker_test.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.4 from annotations +// Mocks generated by Mockito 5.4.6 from annotations // in image_picker/test/image_picker_test.dart. // Do not manually edit this file. @@ -19,6 +19,7 @@ import 'package:mockito/mockito.dart' as _i1; // ignore_for_file: deprecated_member_use_from_same_package // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable // ignore_for_file: prefer_const_constructors // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types @@ -248,6 +249,19 @@ class MockImagePickerPlatform extends _i1.Mock returnValue: _i4.Future>.value(<_i5.XFile>[]), ) as _i4.Future>); + @override + _i4.Future> getMultiVideoWithOptions( + {_i2.MultiVideoPickerOptions? options = + const _i2.MultiVideoPickerOptions()}) => + (super.noSuchMethod( + Invocation.method( + #getMultiVideoWithOptions, + [], + {#options: options}, + ), + returnValue: _i4.Future>.value(<_i5.XFile>[]), + ) as _i4.Future>); + @override bool supportsImageSource(_i2.ImageSource? source) => (super.noSuchMethod( Invocation.method(