diff --git a/AUTHORS b/AUTHORS index b3957d26e36a..35094d2217c7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -44,6 +44,7 @@ Lukasz Piliszczuk SoundReply Solutions GmbH Rafal Wachol Pau Picas +Alexandru Tuca Christian Weder Rhodes Davis Jr. Luigi Agosti diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 9a0057d07ebe..b6f787a2ecac 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.6.5 + +* Set maximum duration for video recording. +* Fix some existing XCTests. + ## 0.6.4 * Add a new parameter to select preferred camera device. diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index c0727dc10d19..ff7f1534a586 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -291,6 +291,10 @@ public void takeVideoWithCamera(MethodCall methodCall, MethodChannel.Result resu private void launchTakeVideoWithCameraIntent() { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + if (this.methodCall != null && this.methodCall.argument("maxDuration") != null) { + int maxSeconds = this.methodCall.argument("maxDuration"); + intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, maxSeconds); + } if (cameraDevice == CameraDevice.FRONT) { useFrontCamera(intent); } diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 88fa3372766f..aa9b00521f53 100644 --- a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -26,6 +26,7 @@ public class ImagePickerDelegateTest { private static final Double WIDTH = 10.0; private static final Double HEIGHT = 10.0; + private static final Double MAX_DURATION = 10.0; private static final Integer IMAGE_QUALITY = 90; @Mock Activity mockActivity; @@ -373,6 +374,19 @@ public void onActivityResult_WhenImageTakenWithCamera_AndNoResizeNeeded_Finishes verifyNoMoreInteractions(mockResult); } + @Test + public void + onActivityResult_WhenVideoTakenWithCamera_AndMaxDurationParametersSupplied_FinishesWithFilePath() { + when(mockMethodCall.argument("maxDuration")).thenReturn(MAX_DURATION); + + ImagePickerDelegate delegate = createDelegateWithPendingResultAndMethodCall(); + delegate.onActivityResult( + ImagePickerDelegate.REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA, Activity.RESULT_OK, mockIntent); + + verify(mockResult).success("pathFromUri"); + verifyNoMoreInteractions(mockResult); + } + private ImagePickerDelegate createDelegate() { return new ImagePickerDelegate( mockActivity, diff --git a/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj index 4ebf1def19ca..106d49cad0c7 100644 --- a/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,16 +8,13 @@ /* Begin PBXBuildFile section */ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5C9513011EC38BD300040975 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */; }; 680049262280D736006DD6AB /* MetaDataUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 680049252280D736006DD6AB /* MetaDataUtilTests.m */; }; 680049272280D79A006DD6AB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 680049382280F2B9006DD6AB /* pngImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 680049352280F2B8006DD6AB /* pngImage.png */; }; 680049392280F2B9006DD6AB /* jpgImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 680049362280F2B8006DD6AB /* jpgImage.jpg */; }; + 68B9AF72243E4B3F00927CE4 /* ImagePickerPluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */; }; 68F4B464228B3AB500C25614 /* PhotoAssetUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -46,8 +43,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -56,7 +51,6 @@ /* Begin PBXFileReference section */ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 5A9D31B91557877A0E8EF3E7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 5C9512FF1EC38BD300040975 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; @@ -66,13 +60,13 @@ 680049352280F2B8006DD6AB /* pngImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pngImage.png; sourceTree = ""; }; 680049362280F2B8006DD6AB /* jpgImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = jpgImage.jpg; sourceTree = ""; }; 6801632E632668F4349764C9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ImagePickerPluginTests.m; path = ../../../ios/Tests/ImagePickerPluginTests.m; sourceTree = ""; }; 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = PhotoAssetUtilTests.m; path = ../../../ios/Tests/PhotoAssetUtilTests.m; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -98,8 +92,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -116,6 +108,7 @@ 68F4B463228B3AB500C25614 /* PhotoAssetUtilTests.m */, F78AF3172342D9D7008449C7 /* ImagePickerTestImages.h */, F78AF3182342D9D7008449C7 /* ImagePickerTestImages.m */, + 68B9AF71243E4B3F00927CE4 /* ImagePickerPluginTests.m */, ); path = image_picker_exampleTests; sourceTree = ""; @@ -142,9 +135,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -331,7 +322,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -339,9 +330,12 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -390,6 +384,7 @@ 9FC8F0EE229FB90B00C8D58F /* ImageUtilTests.m in Sources */, F78AF3192342D9D7008449C7 /* ImagePickerTestImages.m in Sources */, 680049262280D736006DD6AB /* MetaDataUtilTests.m in Sources */, + 68B9AF72243E4B3F00927CE4 /* ImagePickerPluginTests.m in Sources */, 68F4B464228B3AB500C25614 /* PhotoAssetUtilTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -598,6 +593,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -619,6 +615,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/image_picker/example/lib/main.dart b/packages/image_picker/example/lib/main.dart index 919c8388266f..29781f7df449 100755 --- a/packages/image_picker/example/lib/main.dart +++ b/packages/image_picker/example/lib/main.dart @@ -64,7 +64,8 @@ class _MyHomePageState extends State { await _controller.setVolume(0.0); } if (isVideo) { - final File file = await ImagePicker.pickVideo(source: source); + final File file = await ImagePicker.pickVideo( + source: source, maxDuration: const Duration(seconds: 10)); await _playVideo(file); } else { await _displayPickImageDialog(context, diff --git a/packages/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/ios/Classes/FLTImagePickerPlugin.m index 3f092fe0dbbb..f866ee30fc5e 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -102,6 +102,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result _arguments = call.arguments; int imageSource = [[_arguments objectForKey:@"source"] intValue]; + if ([[_arguments objectForKey:@"maxDuration"] isKindOfClass:[NSNumber class]]) { + NSTimeInterval max = [[_arguments objectForKey:@"maxDuration"] doubleValue]; + _imagePickerController.videoMaximumDuration = max; + } switch (imageSource) { case SOURCE_CAMERA: diff --git a/packages/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/ios/Tests/ImagePickerPluginTests.m index 1d701f344170..e961aab55bfd 100644 --- a/packages/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -14,7 +14,7 @@ @implementation ImagePickerPluginTests #pragma mark - Test camera devices, no op on simulators - (void)testPluginPickImageDeviceBack { - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } FLTImagePickerPlugin *plugin = @@ -30,7 +30,7 @@ - (void)testPluginPickImageDeviceBack { } - (void)testPluginPickImageDeviceFront { - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } FLTImagePickerPlugin *plugin = @@ -46,7 +46,7 @@ - (void)testPluginPickImageDeviceFront { } - (void)testPluginPickVideoDeviceBack { - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } FLTImagePickerPlugin *plugin = @@ -62,7 +62,7 @@ - (void)testPluginPickVideoDeviceBack { } - (void)testPluginPickVideoDeviceFront { - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } FLTImagePickerPlugin *plugin = @@ -77,4 +77,17 @@ - (void)testPluginPickVideoDeviceFront { UIImagePickerControllerCameraDeviceFront); } +#pragma mark - Test video duration +- (void)testPickingVideoWithDuration { + FLTImagePickerPlugin *plugin = + [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FlutterMethodCall *call = [FlutterMethodCall + methodCallWithMethodName:@"pickVideo" + arguments:@{@"source" : @(0), @"cameraDevice" : @(0), @"maxDuration" : @95}]; + [plugin handleMethodCall:call + result:^(id _Nullable r){ + }]; + XCTAssertEqual([plugin getImagePickerController].videoMaximumDuration, 95); +} + @end diff --git a/packages/image_picker/lib/image_picker.dart b/packages/image_picker/lib/image_picker.dart index 5e87946a98db..9b0754da1975 100755 --- a/packages/image_picker/lib/image_picker.dart +++ b/packages/image_picker/lib/image_picker.dart @@ -101,6 +101,9 @@ class ImagePicker { /// The [source] argument controls where the video comes from. This can /// be either [ImageSource.camera] or [ImageSource.gallery]. /// + /// The [maxDuration] argument specifies the maximum duration of the captured video. If no [maxDuration] is specified, + /// the maximum duration will be infinite. + /// /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. /// Defaults to [CameraDevice.rear]. @@ -109,12 +112,14 @@ class ImagePicker { /// in this call. You can then call [retrieveLostData] when your app relaunches to retrieve the lost data. static Future pickVideo( {@required ImageSource source, - CameraDevice preferredCameraDevice = CameraDevice.rear}) async { + CameraDevice preferredCameraDevice = CameraDevice.rear, + Duration maxDuration}) async { assert(source != null); final String path = await _channel.invokeMethod( 'pickVideo', { 'source': source.index, + 'maxDuration': maxDuration?.inSeconds, 'cameraDevice': preferredCameraDevice.index }, ); diff --git a/packages/image_picker/pubspec.yaml b/packages/image_picker/pubspec.yaml index 40dd85bab4f1..df8879e05a6a 100755 --- a/packages/image_picker/pubspec.yaml +++ b/packages/image_picker/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.6.4 +version: 0.6.5 flutter: plugin: diff --git a/packages/image_picker/test/image_picker_test.dart b/packages/image_picker/test/image_picker_test.dart index aa559cf2e9c9..8db71adcf778 100644 --- a/packages/image_picker/test/image_picker_test.dart +++ b/packages/image_picker/test/image_picker_test.dart @@ -199,16 +199,55 @@ void main() { isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, + 'maxDuration': null, }), isMethodCall('pickVideo', arguments: { 'source': 1, 'cameraDevice': 0, + 'maxDuration': null, }), ], ); }); - test('handles a null image path response gracefully', () async { + test('passes the duration argument correctly', () async { + await ImagePicker.pickVideo(source: ImageSource.camera); + await ImagePicker.pickVideo( + source: ImageSource.camera, + maxDuration: const Duration(seconds: 10)); + await ImagePicker.pickVideo( + source: ImageSource.camera, + maxDuration: const Duration(minutes: 1)); + await ImagePicker.pickVideo( + source: ImageSource.camera, maxDuration: const Duration(hours: 1)); + expect( + log, + [ + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': null, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 10, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 60, + 'cameraDevice': 0, + }), + isMethodCall('pickVideo', arguments: { + 'source': 0, + 'maxDuration': 3600, + 'cameraDevice': 0, + }), + ], + ); + }); + + test('handles a null video path response gracefully', () async { channel.setMockMethodCallHandler((MethodCall methodCall) => null); expect( @@ -225,6 +264,7 @@ void main() { isMethodCall('pickVideo', arguments: { 'source': 0, 'cameraDevice': 0, + 'maxDuration': null, }), ], ); @@ -240,6 +280,7 @@ void main() { [ isMethodCall('pickVideo', arguments: { 'source': 0, + 'maxDuration': null, 'cameraDevice': 1, }), ],