Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/image_picker/image_picker/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
63 changes: 42 additions & 21 deletions packages/image_picker/image_picker/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class _MyHomePageState extends State<MyHomePage> {
Future<void> _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 {
Expand All @@ -85,18 +85,25 @@ class _MyHomePageState extends State<MyHomePage> {
Future<void> _onImageButtonPressed(
ImageSource source, {
required BuildContext context,
bool isMultiImage = false,
bool allowMultiple = false,
bool isMedia = false,
}) async {
if (_controller != null) {
await _controller!.setVolume(0.0);
}
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<XFile> files;
if (allowMultiple) {
files = await _picker.pickMultiVideo();
} else {
final XFile? file = await _picker.pickVideo(
source: source, maxDuration: const Duration(seconds: 10));
files = <XFile>[if (file != null) file];
}
// Just play the first file, to keep the example simple.
await _playVideo(files.first);
} else if (allowMultiple) {
await _displayPickImageDialog(context, true, (double? maxWidth,
double? maxHeight, int? quality, int? limit) async {
try {
Expand Down Expand Up @@ -349,7 +356,7 @@ class _MyHomePageState extends State<MyHomePage> {
_onImageButtonPressed(ImageSource.gallery, context: context);
},
heroTag: 'image0',
tooltip: 'Pick Image from gallery',
tooltip: 'Pick image from gallery',
child: const Icon(Icons.photo),
),
),
Expand All @@ -361,12 +368,11 @@ class _MyHomePageState extends State<MyHomePage> {
_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),
),
),
Expand All @@ -382,8 +388,8 @@ class _MyHomePageState extends State<MyHomePage> {
);
},
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(
Expand All @@ -394,12 +400,13 @@ class _MyHomePageState extends State<MyHomePage> {
_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))
Expand All @@ -411,7 +418,7 @@ class _MyHomePageState extends State<MyHomePage> {
_onImageButtonPressed(ImageSource.camera, context: context);
},
heroTag: 'image2',
tooltip: 'Take a Photo',
tooltip: 'Take a photo',
child: const Icon(Icons.camera_alt),
),
),
Expand All @@ -424,7 +431,21 @@ class _MyHomePageState extends State<MyHomePage> {
_onImageButtonPressed(ImageSource.gallery, context: context);
},
heroTag: 'video0',
tooltip: 'Pick Video from gallery',
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: 'video1',
tooltip: 'Pick multiple videos',
child: const Icon(Icons.video_library),
),
),
Expand All @@ -437,8 +458,8 @@ class _MyHomePageState extends State<MyHomePage> {
isVideo = true;
_onImageButtonPressed(ImageSource.camera, context: context);
},
heroTag: 'video1',
tooltip: 'Take a Video',
heroTag: 'video2',
tooltip: 'Take a video',
child: const Icon(Icons.videocam),
),
),
Expand Down
10 changes: 10 additions & 0 deletions packages/image_picker/image_picker/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
image_picker_android: {path: ../../../../packages/image_picker/image_picker_android}
image_picker_for_web: {path: ../../../../packages/image_picker/image_picker_for_web}
image_picker_ios: {path: ../../../../packages/image_picker/image_picker_ios}
image_picker_linux: {path: ../../../../packages/image_picker/image_picker_linux}
image_picker_macos: {path: ../../../../packages/image_picker/image_picker_macos}
image_picker_platform_interface: {path: ../../../../packages/image_picker/image_picker_platform_interface}
image_picker_windows: {path: ../../../../packages/image_picker/image_picker_windows}
28 changes: 28 additions & 0 deletions packages/image_picker/image_picker/lib/image_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,34 @@ class ImagePicker {
);
}

/// Returns a [List<XFile>] of the videos that were picked.
///
/// The returned [List<XFile>] 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<List<XFile>> 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)
///
Expand Down
12 changes: 11 additions & 1 deletion packages/image_picker/image_picker/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,3 +49,13 @@ topics:
- image-picker
- files
- file-selection
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
image_picker_android: {path: ../../../packages/image_picker/image_picker_android}
image_picker_for_web: {path: ../../../packages/image_picker/image_picker_for_web}
image_picker_ios: {path: ../../../packages/image_picker/image_picker_ios}
image_picker_linux: {path: ../../../packages/image_picker/image_picker_linux}
image_picker_macos: {path: ../../../packages/image_picker/image_picker_macos}
image_picker_platform_interface: {path: ../../../packages/image_picker/image_picker_platform_interface}
image_picker_windows: {path: ../../../packages/image_picker/image_picker_windows}
26 changes: 26 additions & 0 deletions packages/image_picker/image_picker/test/image_picker_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,32 @@ void main() {
expect(response.exception!.message, 'test_error_message');
});
});

group('#pickMultiVideo', () {
setUp(() {
when(mockPlatform.getMultiVideoWithOptions(
options: any,
)).thenAnswer((Invocation _) async => <XFile>[]);
});

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(<Object>[
mockPlatform.getMultiVideoWithOptions(),
mockPlatform.getMultiVideoWithOptions(
options: const MultiVideoPickerOptions(
maxDuration: Duration(seconds: 10))),
mockPlatform.getMultiVideoWithOptions(
options: const MultiVideoPickerOptions(limit: 5)),
]);
});
});
});

group('#Multi images', () {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -248,6 +249,19 @@ class MockImagePickerPlatform extends _i1.Mock
returnValue: _i4.Future<List<_i5.XFile>>.value(<_i5.XFile>[]),
) as _i4.Future<List<_i5.XFile>>);

@override
_i4.Future<List<_i5.XFile>> getMultiVideoWithOptions(
{_i2.MultiVideoPickerOptions? options =
const _i2.MultiVideoPickerOptions()}) =>
(super.noSuchMethod(
Invocation.method(
#getMultiVideoWithOptions,
[],
{#options: options},
),
returnValue: _i4.Future<List<_i5.XFile>>.value(<_i5.XFile>[]),
) as _i4.Future<List<_i5.XFile>>);

@override
bool supportsImageSource(_i2.ImageSource? source) => (super.noSuchMethod(
Invocation.method(
Expand Down
4 changes: 4 additions & 0 deletions packages/image_picker/image_picker_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.13

* Adds support for `getMultiVideo`.

## 0.8.12+25

* Updates kotlin version to 2.2.0 to enable gradle 8.11 support.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public class ImagePickerDelegate
@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_MEDIA_FROM_GALLERY = 2347;
@VisibleForTesting static final int REQUEST_CODE_CHOOSE_MULTI_VIDEO_FROM_GALLERY = 2348;

@VisibleForTesting static final int REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY = 2352;
@VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353;
Expand Down Expand Up @@ -474,6 +475,38 @@ private void launchMultiPickImageFromGalleryIntent(Boolean usePhotoPicker, int l
pickMultiImageIntent, REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY);
}

public void chooseMultiVideoFromGallery(
@NonNull VideoSelectionOptions options,
boolean usePhotoPicker,
int limit,
@NonNull Messages.Result<List<String>> result) {
if (!setPendingOptionsAndResult(null, options, result)) {
finishWithAlreadyActiveError(result);
return;
}

launchMultiPickVideoFromGalleryIntent(usePhotoPicker, limit);
}

private void launchMultiPickVideoFromGalleryIntent(Boolean usePhotoPicker, int limit) {
Intent pickMultiVideoIntent;
if (usePhotoPicker) {
pickMultiVideoIntent =
new ActivityResultContracts.PickMultipleVisualMedia(limit)
.createIntent(
activity,
new PickVisualMediaRequest.Builder()
.setMediaType(ActivityResultContracts.PickVisualMedia.VideoOnly.INSTANCE)
.build());
} else {
pickMultiVideoIntent = new Intent(Intent.ACTION_GET_CONTENT);
pickMultiVideoIntent.setType("video/*");
pickMultiVideoIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
activity.startActivityForResult(
pickMultiVideoIntent, REQUEST_CODE_CHOOSE_MULTI_VIDEO_FROM_GALLERY);
}

public void takeImageWithCamera(
@NonNull ImageSelectionOptions options, @NonNull Messages.Result<List<String>> result) {
if (!setPendingOptionsAndResult(options, null, result)) {
Expand Down Expand Up @@ -617,6 +650,9 @@ public boolean onActivityResult(
case REQUEST_CODE_CHOOSE_MULTI_IMAGE_FROM_GALLERY:
handlerRunnable = () -> handleChooseMultiImageResult(resultCode, data);
break;
case REQUEST_CODE_CHOOSE_MULTI_VIDEO_FROM_GALLERY:
handlerRunnable = () -> handleChooseMultiVideoResult(resultCode, data);
break;
case REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA:
handlerRunnable = () -> handleCaptureImageResult(resultCode);
break;
Expand Down Expand Up @@ -696,6 +732,24 @@ private void handleChooseImageResult(int resultCode, Intent data) {
finishWithSuccess(null);
}

private void handleChooseMultiVideoResult(int resultCode, Intent intent) {
if (resultCode == Activity.RESULT_OK && intent != null) {
ArrayList<MediaPath> paths = getPathsFromIntent(intent, false);
// If there's no valid Uri, return an error
if (paths == null) {
finishWithError(
"missing_valid_video_uri", "Cannot find at least one of the selected videos.");
return;
}

handleMediaResult(paths);
return;
}

// User cancelled choosing a picture.
finishWithSuccess(null);
}

public class MediaPath {
public MediaPath(@NonNull String path, @Nullable String mimeType) {
this.path = path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,9 @@ public void pickVideos(

setCameraDevice(delegate, source);
if (generalOptions.getAllowMultiple()) {
result.error(new RuntimeException("Multi-video selection is not implemented"));
int limit = ImagePickerUtils.getLimitFromOption(generalOptions);
delegate.chooseMultiVideoFromGallery(
options, generalOptions.getUsePhotoPicker(), limit, result);
} else {
switch (source.getType()) {
case GALLERY:
Expand Down
Loading