Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
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 `getMultiVideoWithOptions`.

## 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 video.
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
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,41 @@ public void pickVideos_whenSourceIsCamera_invokesTakeImageWithCamera_FrontCamera
verify(mockImagePickerDelegate).setCameraDevice(eq(ImagePickerDelegate.CameraDevice.FRONT));
}

@Test
public void pickVideos_invokesChooseMultiVideoFromGallery() {
plugin.pickVideos(
SOURCE_GALLERY,
DEFAULT_VIDEO_OPTIONS,
GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER,
mockResult);
verify(mockImagePickerDelegate)
.chooseMultiVideoFromGallery(any(), eq(false), eq(Integer.MAX_VALUE), any());
verifyNoInteractions(mockResult);
}

@Test
public void pickVideos_usingPhotoPicker_invokesChooseMultiVideoFromGallery() {
plugin.pickVideos(
SOURCE_GALLERY,
DEFAULT_VIDEO_OPTIONS,
GENERAL_OPTIONS_ALLOW_MULTIPLE_USE_PHOTO_PICKER,
mockResult);
verify(mockImagePickerDelegate)
.chooseMultiVideoFromGallery(any(), eq(true), eq(Integer.MAX_VALUE), any());
verifyNoInteractions(mockResult);
}

@Test
public void pickVideos_withLimit5_invokesChooseMultiVideoFromGallery() {
plugin.pickVideos(
SOURCE_GALLERY,
DEFAULT_VIDEO_OPTIONS,
GENERAL_OPTIONS_ALLOW_MULTIPLE_DONT_USE_PHOTO_PICKER_WITH_LIMIT,
mockResult);
verify(mockImagePickerDelegate).chooseMultiVideoFromGallery(any(), eq(false), eq(5), any());
verifyNoInteractions(mockResult);
}

@Test
public void onConstructor_whenContextTypeIsActivity_shouldNotCrash() {
new ImagePickerPlugin(mockImagePickerDelegate, mockActivity);
Expand Down
99 changes: 59 additions & 40 deletions packages/image_picker/image_picker_android/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,10 @@ class _MyHomePageState extends State<MyHomePage> {
Future<void> _playVideo(XFile? file) async {
if (file != null && mounted) {
await _disposeVideoController();
late VideoPlayerController controller;

controller = VideoPlayerController.file(File(file.path));
final VideoPlayerController controller =
VideoPlayerController.file(File(file.path));
_controller = controller;
const double volume = 1.0;
await controller.setVolume(volume);
await controller.setVolume(1.0);
await controller.initialize();
await controller.setLooping(true);
await controller.play();
Expand All @@ -95,21 +93,28 @@ 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.getVideo(
source: source, maxDuration: const Duration(seconds: 10));
if (file != null && context.mounted) {
_showPickedSnackBar(context, <XFile>[file]);
final List<XFile> files;
if (allowMultiple) {
files = await _picker.getMultiVideoWithOptions();
} else {
final XFile? file = await _picker.getVideo(
source: source, maxDuration: const Duration(seconds: 10));
files = <XFile>[if (file != null) file];
}
if (files.isNotEmpty && context.mounted) {
_showPickedSnackBar(context, files);
// Just play the first file, to keep the example simple.
await _playVideo(files.first);
}
await _playVideo(file);
} else if (isMultiImage) {
} else if (allowMultiple) {
await _displayPickImageDialog(context, true, (double? maxWidth,
double? maxHeight, int? quality, int? limit) async {
try {
Expand All @@ -121,7 +126,7 @@ class _MyHomePageState extends State<MyHomePage> {
final List<XFile> pickedFileList = isMedia
? await _picker.getMedia(
options: MediaOptions(
allowMultiple: isMultiImage,
allowMultiple: allowMultiple,
imageOptions: imageOptions,
limit: limit,
),
Expand Down Expand Up @@ -151,7 +156,7 @@ class _MyHomePageState extends State<MyHomePage> {
final List<XFile> pickedFileList = <XFile>[];
final XFile? media = _firstOrNull(await _picker.getMedia(
options: MediaOptions(
allowMultiple: isMultiImage,
allowMultiple: allowMultiple,
imageOptions: ImageOptions(
maxWidth: maxWidth,
maxHeight: maxHeight,
Expand Down Expand Up @@ -290,8 +295,7 @@ class _MyHomePageState extends State<MyHomePage> {
Widget _buildInlineVideoPlayer(int index) {
final VideoPlayerController controller =
VideoPlayerController.file(File(_mediaFileList![index].path));
const double volume = 1.0;
controller.setVolume(volume);
controller.setVolume(1.0);
controller.initialize();
controller.setLooping(true);
controller.play();
Expand All @@ -314,7 +318,7 @@ class _MyHomePageState extends State<MyHomePage> {
if (response.file != null) {
if (response.type == RetrieveType.video) {
_isVideo = true;
await _playVideo(response.file);
await _playVideo(response.files?.firstOrNull);
} else {
_isVideo = false;
setState(() {
Expand Down Expand Up @@ -380,8 +384,8 @@ class _MyHomePageState extends State<MyHomePage> {
_onImageButtonPressed(ImageSource.gallery, context: context);
},
heroTag: 'image0',
tooltip: 'Pick Image from gallery',
label: const Text('Pick Image from gallery'),
tooltip: 'Pick image from gallery',
label: const Text('Pick image from gallery'),
icon: const Icon(Icons.photo),
),
),
Expand All @@ -393,13 +397,12 @@ class _MyHomePageState extends State<MyHomePage> {
_onImageButtonPressed(
ImageSource.gallery,
context: context,
isMultiImage: true,
isMedia: true,
allowMultiple: true,
);
},
heroTag: 'multipleMedia',
tooltip: 'Pick Multiple Media from gallery',
label: const Text('Pick Multiple Media from gallery'),
heroTag: 'image1',
tooltip: 'Pick multiple images',
label: const Text('Pick multiple images'),
icon: const Icon(Icons.photo_library),
),
),
Expand All @@ -415,9 +418,9 @@ class _MyHomePageState extends State<MyHomePage> {
);
},
heroTag: 'media',
tooltip: 'Pick Single Media from gallery',
label: const Text('Pick Single Media from gallery'),
icon: const Icon(Icons.photo_library),
tooltip: 'Pick item from gallery',
label: const Text('Pick item from gallery'),
icon: const Icon(Icons.photo_outlined),
),
),
Padding(
Expand All @@ -428,13 +431,14 @@ class _MyHomePageState extends State<MyHomePage> {
_onImageButtonPressed(
ImageSource.gallery,
context: context,
isMultiImage: true,
allowMultiple: true,
isMedia: true,
);
},
heroTag: 'image1',
tooltip: 'Pick Multiple Image from gallery',
label: const Text('Pick Multiple Image from gallery'),
icon: const Icon(Icons.photo_library),
heroTag: 'multipleMedia',
tooltip: 'Pick multiple items',
label: const Text('Pick multiple items'),
icon: const Icon(Icons.photo_library_outlined),
),
),
Padding(
Expand All @@ -445,8 +449,8 @@ class _MyHomePageState extends State<MyHomePage> {
_onImageButtonPressed(ImageSource.camera, context: context);
},
heroTag: 'image2',
tooltip: 'Take a Photo',
label: const Text('Take a Photo'),
tooltip: 'Take a photo',
label: const Text('Take a photo'),
icon: const Icon(Icons.camera_alt),
),
),
Expand All @@ -458,9 +462,24 @@ class _MyHomePageState extends State<MyHomePage> {
_isVideo = true;
_onImageButtonPressed(ImageSource.gallery, context: context);
},
heroTag: 'video0',
tooltip: 'Pick Video from gallery',
label: const Text('Pick Video from gallery'),
heroTag: 'video',
tooltip: 'Pick video from gallery',
label: const Text('Pick video from gallery'),
icon: const Icon(Icons.video_file),
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: FloatingActionButton.extended(
backgroundColor: Colors.red,
onPressed: () {
_isVideo = true;
_onImageButtonPressed(ImageSource.gallery,
context: context, allowMultiple: true);
},
heroTag: 'multiVideo',
tooltip: 'Pick multiple videos',
label: const Text('Pick multiple videos'),
icon: const Icon(Icons.video_library),
),
),
Expand All @@ -472,9 +491,9 @@ class _MyHomePageState extends State<MyHomePage> {
_isVideo = true;
_onImageButtonPressed(ImageSource.camera, context: context);
},
heroTag: 'video1',
tooltip: 'Take a Video',
label: const Text('Take a Video'),
heroTag: 'takeVideo',
tooltip: 'Take a video',
label: const Text('Take a video'),
icon: const Icon(Icons.videocam),
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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: ^2.0.0
video_player: ^2.1.4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,27 @@ class ImagePickerAndroid extends ImagePickerPlatform {
return path != null ? XFile(path) : null;
}

@override
Future<List<XFile>> getMultiVideoWithOptions({
MultiVideoPickerOptions options = const MultiVideoPickerOptions(),
}) async {
final List<String> paths = await _hostApi.pickVideos(
SourceSpecification(type: SourceType.gallery),
VideoSelectionOptions(maxDurationSeconds: options.maxDuration?.inSeconds),
GeneralOptions(
allowMultiple: true,
usePhotoPicker: useAndroidPhotoPicker,
limit: options.limit,
),
);

if (paths.isEmpty) {
return <XFile>[];
}

return paths.map((String path) => XFile(path)).toList();
}

MediaSelectionOptions _mediaOptionsToMediaSelectionOptions(
MediaOptions mediaOptions) {
final ImageSelectionOptions imageSelectionOptions =
Expand Down
Loading