Adding Video Capability to image_picker (#565)
diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 803dcb9..feac368 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md
@@ -1,3 +1,8 @@ +## 0.4.2 + +* Added support for picking videos. +* Updated example app to show video preview. + ## 0.4.1 * Bugfix: the `pickImage` method will now return null when the user cancels picking the image, instead of hanging indefinitely.
diff --git a/packages/image_picker/android/src/main/AndroidManifest.xml b/packages/image_picker/android/src/main/AndroidManifest.xml index 7672d73..22e6e62 100755 --- a/packages/image_picker/android/src/main/AndroidManifest.xml +++ b/packages/image_picker/android/src/main/AndroidManifest.xml
@@ -1,7 +1,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.flutter.plugins.imagepicker"> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.CAMERA"/> + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> + <uses-permission android:name="android.permission.CAMERA"/> <application> <provider
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 b735658..65812d2 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
@@ -180,6 +180,61 @@ this.fileUtils = fileUtils; } + public void chooseVideoFromGallery(MethodCall methodCall, MethodChannel.Result result) { + if (!setPendingMethodCallAndResult(methodCall, result)) { + finishWithAlreadyActiveError(); + return; + } + + if (!permissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) { + permissionManager.askForPermission( + Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_EXTERNAL_STORAGE_PERMISSION); + return; + } + + launchPickVideoFromGalleryIntent(); + } + + private void launchPickVideoFromGalleryIntent() { + Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT); + pickImageIntent.setType("video/*"); + + activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_FROM_GALLERY); + } + + public void takeVideoWithCamera(MethodCall methodCall, MethodChannel.Result result) { + if (!setPendingMethodCallAndResult(methodCall, result)) { + finishWithAlreadyActiveError(); + return; + } + + if (!permissionManager.isPermissionGranted(Manifest.permission.CAMERA)) { + permissionManager.askForPermission(Manifest.permission.CAMERA, REQUEST_CAMERA_PERMISSION); + return; + } + + launchTakeVideoWithCameraIntent(); + } + + private void launchTakeVideoWithCameraIntent() { + Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + boolean canTakePhotos = intentResolver.resolveActivity(intent); + + if (!canTakePhotos) { + finishWithError("no_available_camera", "No cameras available for taking pictures."); + return; + } + + File imageFile = createTemporaryWritableImageFile(); + pendingCameraImageUri = Uri.parse("file:" + imageFile.getAbsolutePath()); + + Uri imageUri = fileUriResolver.resolveFileProviderUriForFile(fileProviderName, imageFile); + intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); + grantUriPermissions(intent, imageUri); + + activity.startActivityForResult(intent, REQUEST_CODE_TAKE_WITH_CAMERA); + } + public void chooseImageFromGallery(MethodCall methodCall, MethodChannel.Result result) { if (!setPendingMethodCallAndResult(methodCall, result)) { finishWithAlreadyActiveError();
diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index 9ecc6a9..3e658a6 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java
@@ -49,10 +49,8 @@ result.error("no_activity", "image_picker plugin requires a foreground activity.", null); return; } - if (call.method.equals("pickImage")) { int imageSource = call.argument("source"); - switch (imageSource) { case SOURCE_GALLERY: delegate.chooseImageFromGallery(call, result); @@ -63,6 +61,18 @@ default: throw new IllegalArgumentException("Invalid image source: " + imageSource); } + } else if (call.method.equals("pickVideo")) { + int imageSource = call.argument("source"); + switch (imageSource) { + case SOURCE_GALLERY: + delegate.chooseVideoFromGallery(call, result); + break; + case SOURCE_CAMERA: + delegate.takeVideoWithCamera(call, result); + break; + default: + throw new IllegalArgumentException("Invalid video source: " + imageSource); + } } else { throw new IllegalArgumentException("Unknown method " + call.method); }
diff --git a/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj old mode 100755 new mode 100644 index 19c24cf..0e61475 --- a/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/image_picker/example/ios/Runner.xcodeproj/project.pbxproj
@@ -7,11 +7,10 @@ objects = { /* Begin PBXBuildFile section */ - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; + 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, ); }; }; - 3E15FEC43344A77097B00440 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01A6711EF19E90303F1F55DC /* Pods_Runner.framework */; }; 5C9513011EC38BD300040975 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.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, ); }; }; @@ -22,6 +21,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EC32F6993F4529982D9519F1 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -40,9 +40,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 01A6711EF19E90303F1F55DC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; }; 5C9512FF1EC38BD300040975 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 5C9513001EC38BD300040975 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; @@ -58,6 +57,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + EC32F6993F4529982D9519F1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,7 +67,7 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - 3E15FEC43344A77097B00440 /* Pods_Runner.framework in Frameworks */, + F4F7A436CCA4BF276270A3AE /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -141,7 +141,7 @@ CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( - 01A6711EF19E90303F1F55DC /* Pods_Runner.framework */, + EC32F6993F4529982D9519F1 /* libPods-Runner.a */, ); name = Frameworks; sourceTree = "<group>"; @@ -183,7 +183,12 @@ TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 3GRKCVVJ22; + DevelopmentTeam = 9FK3425VTA; + SystemCapabilities = { + com.apple.BackgroundModes = { + enabled = 1; + }; + }; }; }; }; @@ -258,9 +263,12 @@ files = ( ); inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/.symlinks/flutter/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -287,13 +295,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -427,7 +438,6 @@ buildSettings = { ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 3GRKCVVJ22; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -450,7 +460,6 @@ buildSettings = { ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 3GRKCVVJ22; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)",
diff --git a/packages/image_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/image_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/packages/image_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IDEDidComputeMac32BitWarning</key> + <true/> +</dict> +</plist>
diff --git a/packages/image_picker/example/ios/Runner/Info.plist b/packages/image_picker/example/ios/Runner/Info.plist index 780303c..f9c1909 100755 --- a/packages/image_picker/example/ios/Runner/Info.plist +++ b/packages/image_picker/example/ios/Runner/Info.plist
@@ -22,6 +22,16 @@ <string>1</string> <key>LSRequiresIPhoneOS</key> <true/> + <key>NSCameraUsageDescription</key> + <string>Used to demonstrate image picker plugin</string> + <key>NSMicrophoneUsageDescription</key> + <string>Used to capture audio for image picker plugin</string> + <key>NSPhotoLibraryUsageDescription</key> + <string>Used to demonstrate image picker plugin</string> + <key>UIBackgroundModes</key> + <array> + <string>remote-notification</string> + </array> <key>UILaunchStoryboardName</key> <string>LaunchScreen</string> <key>UIMainStoryboardFile</key> @@ -43,10 +53,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>NSCameraUsageDescription</key> - <string>Used to demonstrate image picker plugin</string> - <key>NSPhotoLibraryUsageDescription</key> - <string>Used to demonstrate image picker plugin</string> <key>UIViewControllerBasedStatusBarAppearance</key> <false/> </dict>
diff --git a/packages/image_picker/example/lib/main.dart b/packages/image_picker/example/lib/main.dart index 25c54bf..e0312f9 100755 --- a/packages/image_picker/example/lib/main.dart +++ b/packages/image_picker/example/lib/main.dart
@@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:video_player/video_player.dart'; void main() { runApp(new MyApp()); @@ -33,52 +34,207 @@ class _MyHomePageState extends State<MyHomePage> { Future<File> _imageFile; + bool isVideo = false; + VideoPlayerController _controller; + VoidCallback listener; void _onImageButtonPressed(ImageSource source) { setState(() { - _imageFile = ImagePicker.pickImage(source: source); + if (_controller != null) { + _controller.setVolume(0.0); + _controller.removeListener(listener); + } + if (isVideo) { + ImagePicker.pickVideo(source: source).then((File file) { + if (file != null && mounted) { + setState(() { + _controller = VideoPlayerController.file(file) + ..addListener(listener) + ..setVolume(1.0) + ..initialize() + ..setLooping(true) + ..play(); + }); + } + }); + } else { + _imageFile = ImagePicker.pickImage(source: source); + } }); } @override + void deactivate() { + if (_controller != null) { + _controller.setVolume(0.0); + _controller.removeListener(listener); + } + super.deactivate(); + } + + @override + void dispose() { + if (_controller != null) { + _controller.dispose(); + } + super.dispose(); + } + + @override + void initState() { + super.initState(); + listener = () { + setState(() {}); + }; + } + + Widget _previewVideo(VideoPlayerController controller) { + if (controller == null) { + return const Text( + 'You have not yet picked a video', + textAlign: TextAlign.center, + ); + } else if (controller.value.initialized) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: AspectRatioVideo(controller), + ); + } else { + return const Text( + 'Error Loading Video', + textAlign: TextAlign.center, + ); + } + } + + Widget _previewImage() { + return FutureBuilder<File>( + future: _imageFile, + builder: (BuildContext context, AsyncSnapshot<File> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.data != null) { + return Image.file(snapshot.data); + } else if (snapshot.error != null) { + return const Text( + 'Error picking image.', + textAlign: TextAlign.center, + ); + } else { + return const Text( + 'You have not yet picked an image.', + textAlign: TextAlign.center, + ); + } + }); + } + + @override Widget build(BuildContext context) { - return new Scaffold( - appBar: new AppBar( - title: const Text('Image Picker Example'), + return Scaffold( + appBar: AppBar( + title: Text(widget.title), ), - body: new Center( - child: new FutureBuilder<File>( - future: _imageFile, - builder: (BuildContext context, AsyncSnapshot<File> snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.data != null) { - return new Image.file(snapshot.data); - } else if (snapshot.error != null) { - return const Text('error picking image.'); - } else { - return const Text('You have not yet picked an image.'); - } - }, - ), + body: Center( + child: isVideo ? _previewVideo(_controller) : _previewImage(), ), - floatingActionButton: new Column( + floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ - new FloatingActionButton( - onPressed: () => _onImageButtonPressed(ImageSource.gallery), + FloatingActionButton( + onPressed: () { + isVideo = false; + _onImageButtonPressed(ImageSource.gallery); + }, + heroTag: 'image0', tooltip: 'Pick Image from gallery', child: const Icon(Icons.photo_library), ), - new Padding( + Padding( padding: const EdgeInsets.only(top: 16.0), - child: new FloatingActionButton( - onPressed: () => _onImageButtonPressed(ImageSource.camera), + child: FloatingActionButton( + onPressed: () { + isVideo = false; + _onImageButtonPressed(ImageSource.camera); + }, + heroTag: 'image1', tooltip: 'Take a Photo', child: const Icon(Icons.camera_alt), ), ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + backgroundColor: Colors.red, + onPressed: () { + isVideo = true; + _onImageButtonPressed(ImageSource.gallery); + }, + heroTag: 'video0', + tooltip: 'Pick Video from gallery', + child: const Icon(Icons.video_library), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: FloatingActionButton( + backgroundColor: Colors.red, + onPressed: () { + isVideo = true; + _onImageButtonPressed(ImageSource.camera); + }, + heroTag: 'video1', + tooltip: 'Take a Video', + child: const Icon(Icons.videocam), + ), + ), ], ), ); } } + +class AspectRatioVideo extends StatefulWidget { + final VideoPlayerController controller; + + AspectRatioVideo(this.controller); + + @override + AspectRatioVideoState createState() => new AspectRatioVideoState(); +} + +class AspectRatioVideoState extends State<AspectRatioVideo> { + VideoPlayerController get controller => widget.controller; + bool initialized = false; + + VoidCallback listener; + + @override + void initState() { + super.initState(); + listener = () { + if (!mounted) { + return; + } + if (initialized != controller.value.initialized) { + initialized = controller.value.initialized; + setState(() {}); + } + }; + controller.addListener(listener); + } + + @override + Widget build(BuildContext context) { + if (initialized) { + final Size size = controller.value.size; + return new Center( + child: new AspectRatio( + aspectRatio: size.width / size.height, + child: new VideoPlayer(controller), + ), + ); + } else { + return new Container(); + } + } +}
diff --git a/packages/image_picker/example/pubspec.yaml b/packages/image_picker/example/pubspec.yaml index 919751f..8fbf4f4 100755 --- a/packages/image_picker/example/pubspec.yaml +++ b/packages/image_picker/example/pubspec.yaml
@@ -2,6 +2,7 @@ description: Demonstrates how to use the image_picker plugin. dependencies: + video_player: "0.5.2" flutter: sdk: flutter image_picker:
diff --git a/packages/image_picker/ios/Classes/ImagePickerPlugin.m b/packages/image_picker/ios/Classes/ImagePickerPlugin.m index ef6106b..a016659 100644 --- a/packages/image_picker/ios/Classes/ImagePickerPlugin.m +++ b/packages/image_picker/ios/Classes/ImagePickerPlugin.m
@@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -@import UIKit; - #import "ImagePickerPlugin.h" +#import <MobileCoreServices/MobileCoreServices.h> +#import <Photos/Photos.h> +#import <UIKit/UIKit.h> + @interface FLTImagePickerPlugin ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate> @end @@ -50,6 +52,7 @@ if ([@"pickImage" isEqualToString:call.method]) { _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; + _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; _result = result; _arguments = call.arguments; @@ -69,6 +72,33 @@ details:nil]); break; } + } else if ([@"pickVideo" isEqualToString:call.method]) { + _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + _imagePickerController.delegate = self; + _imagePickerController.mediaTypes = @[ + (NSString *)kUTTypeMovie, (NSString *)kUTTypeAVIMovie, (NSString *)kUTTypeVideo, + (NSString *)kUTTypeMPEG4 + ]; + _imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; + + _result = result; + _arguments = call.arguments; + + int imageSource = [[_arguments objectForKey:@"source"] intValue]; + + switch (imageSource) { + case SOURCE_CAMERA: + [self showCamera]; + break; + case SOURCE_GALLERY: + [self showPhotoLibrary]; + break; + default: + result([FlutterError errorWithCode:@"invalid_source" + message:@"Invalid video source." + details:nil]); + break; + } } else { result(FlutterMethodNotImplemented); } @@ -96,34 +126,52 @@ - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info { - [_imagePickerController dismissViewControllerAnimated:YES completion:nil]; + NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL]; UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage]; - if (image == nil) { - image = [info objectForKey:UIImagePickerControllerOriginalImage]; - } - image = [self normalizedImage:image]; + [_imagePickerController dismissViewControllerAnimated:YES completion:nil]; - NSNumber *maxWidth = [_arguments objectForKey:@"maxWidth"]; - NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; + if (videoURL != nil) { + NSData *data = [NSData dataWithContentsOfURL:videoURL]; + NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; + NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.MOV", guid]; + NSString *tmpDirectory = NSTemporaryDirectory(); + NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; - if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - image = [self scaledImage:image maxWidth:maxWidth maxHeight:maxHeight]; - } - - NSData *data = UIImageJPEGRepresentation(image, 1.0); - NSString *tmpDirectory = NSTemporaryDirectory(); - NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; - // TODO(jackson): Using the cache directory might be better than temporary - // directory. - NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.jpg", guid]; - NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; - if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { - _result(tmpPath); + if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { + _result(tmpPath); + } else { + _result([FlutterError errorWithCode:@"create_error" + message:@"Temporary file could not be created" + details:nil]); + } } else { - _result([FlutterError errorWithCode:@"create_error" - message:@"Temporary file could not be created" - details:nil]); + if (image == nil) { + image = [info objectForKey:UIImagePickerControllerOriginalImage]; + } + image = [self normalizedImage:image]; + + NSNumber *maxWidth = [_arguments objectForKey:@"maxWidth"]; + NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; + + if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { + image = [self scaledImage:image maxWidth:maxWidth maxHeight:maxHeight]; + } + + NSData *data = UIImageJPEGRepresentation(image, 1.0); + NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; + NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.jpg", guid]; + NSString *tmpDirectory = NSTemporaryDirectory(); + NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; + + if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { + _result(tmpPath); + } else { + _result([FlutterError errorWithCode:@"create_error" + message:@"Temporary file could not be created" + details:nil]); + } } + _result = nil; _arguments = nil; }
diff --git a/packages/image_picker/lib/image_picker.dart b/packages/image_picker/lib/image_picker.dart index 2874318..26d09c2 100755 --- a/packages/image_picker/lib/image_picker.dart +++ b/packages/image_picker/lib/image_picker.dart
@@ -37,11 +37,11 @@ assert(source != null); if (maxWidth != null && maxWidth < 0) { - throw new ArgumentError.value(maxWidth, 'maxWidth can\'t be negative'); + throw new ArgumentError.value(maxWidth, 'maxWidth cannot be negative'); } if (maxHeight != null && maxHeight < 0) { - throw new ArgumentError.value(maxHeight, 'maxHeight can\'t be negative'); + throw new ArgumentError.value(maxHeight, 'maxHeight cannot be negative'); } final String path = await _channel.invokeMethod( @@ -53,6 +53,20 @@ }, ); - return path != null ? new File(path) : null; + return path == null ? null : new File(path); + } + + static Future<File> pickVideo({ + @required ImageSource source, + }) async { + assert(source != null); + + final String path = await _channel.invokeMethod( + 'pickVideo', + <String, dynamic>{ + 'source': source.index, + }, + ); + return path == null ? null : new File(path); } }
diff --git a/packages/image_picker/pubspec.yaml b/packages/image_picker/pubspec.yaml index 6cf51d5..5396e9d 100755 --- a/packages/image_picker/pubspec.yaml +++ b/packages/image_picker/pubspec.yaml
@@ -1,9 +1,11 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. -author: Flutter Team <flutter-dev@googlegroups.com> +authors: + - Flutter Team <flutter-dev@googlegroups.com> + - Rhodes Davis Jr. <rody.davis.jr@gmail.com> homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.4.1 +version: 0.4.2 flutter: plugin: