diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 803dcb922740..feac368d66d5 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 7672d73e5e09..22e6e624f09f 100755 --- a/packages/image_picker/android/src/main/AndroidManifest.xml +++ b/packages/image_picker/android/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ - - + + + /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 000000000000..18d981003d68 --- /dev/null +++ b/packages/image_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/image_picker/example/ios/Runner/Info.plist b/packages/image_picker/example/ios/Runner/Info.plist index 780303c86d1c..f9c1909383ca 100755 --- a/packages/image_picker/example/ios/Runner/Info.plist +++ b/packages/image_picker/example/ios/Runner/Info.plist @@ -22,6 +22,16 @@ 1 LSRequiresIPhoneOS + NSCameraUsageDescription + Used to demonstrate image picker plugin + NSMicrophoneUsageDescription + Used to capture audio for image picker plugin + NSPhotoLibraryUsageDescription + Used to demonstrate image picker plugin + UIBackgroundModes + + remote-notification + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,10 +53,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSCameraUsageDescription - Used to demonstrate image picker plugin - NSPhotoLibraryUsageDescription - Used to demonstrate image picker plugin UIViewControllerBasedStatusBarAppearance diff --git a/packages/image_picker/example/lib/main.dart b/packages/image_picker/example/lib/main.dart index 25c54bfb37cc..e0312f9ed660 100755 --- a/packages/image_picker/example/lib/main.dart +++ b/packages/image_picker/example/lib/main.dart @@ -7,6 +7,7 @@ import 'dart:io'; 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 MyHomePage extends StatefulWidget { class _MyHomePageState extends State { Future _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( + future: _imageFile, + builder: (BuildContext context, AsyncSnapshot 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( - future: _imageFile, - builder: (BuildContext context, AsyncSnapshot 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: [ - 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 { + 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 919751f68148..8fbf4f47b6ad 100755 --- a/packages/image_picker/example/pubspec.yaml +++ b/packages/image_picker/example/pubspec.yaml @@ -2,6 +2,7 @@ name: image_picker_example 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 ef6106b8fcc7..a01665995b21 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 +#import +#import + @interface FLTImagePickerPlugin () @end @@ -50,6 +52,7 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result if ([@"pickImage" isEqualToString:call.method]) { _imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; _imagePickerController.delegate = self; + _imagePickerController.mediaTypes = @[ (NSString *)kUTTypeImage ]; _result = result; _arguments = call.arguments; @@ -69,6 +72,33 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result 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)showPhotoLibrary { - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)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]; - - NSNumber *maxWidth = [_arguments objectForKey:@"maxWidth"]; - NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"]; + [_imagePickerController dismissViewControllerAnimated:YES completion:nil]; - if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) { - image = [self scaledImage:image maxWidth:maxWidth maxHeight: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]; - 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 287431869b68..26d09c2c313b 100755 --- a/packages/image_picker/lib/image_picker.dart +++ b/packages/image_picker/lib/image_picker.dart @@ -37,11 +37,11 @@ class ImagePicker { 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 @@ class ImagePicker { }, ); - return path != null ? new File(path) : null; + return path == null ? null : new File(path); + } + + static Future pickVideo({ + @required ImageSource source, + }) async { + assert(source != null); + + final String path = await _channel.invokeMethod( + 'pickVideo', + { + '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 6cf51d57a373..5396e9d4e334 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 +authors: + - Flutter Team + - Rhodes Davis Jr. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.4.1 +version: 0.4.2 flutter: plugin: