Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[image_picker] Added functions to get the most recent images/videos from a user's gallery #676

Closed
wants to merge 10 commits into from
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.provider.MediaStore;
Expand Down Expand Up @@ -68,11 +69,13 @@ public class ImagePickerDelegate
@VisibleForTesting static final int REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY = 2342;
@VisibleForTesting static final int REQUEST_CODE_TAKE_IMAGE_WITH_CAMERA = 2343;
@VisibleForTesting static final int REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION = 2344;
@VisibleForTesting static final int REQUEST_CAMERA_IMAGE_PERMISSION = 2345;
@VisibleForTesting static final int REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION_LATEST = 2345;
@VisibleForTesting static final int REQUEST_CAMERA_IMAGE_PERMISSION = 2346;
@VisibleForTesting static final int REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY = 2352;
@VisibleForTesting static final int REQUEST_CODE_TAKE_VIDEO_WITH_CAMERA = 2353;
@VisibleForTesting static final int REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION = 2354;
@VisibleForTesting static final int REQUEST_CAMERA_VIDEO_PERMISSION = 2355;
@VisibleForTesting static final int REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION_LATEST = 2355;
@VisibleForTesting static final int REQUEST_CAMERA_VIDEO_PERMISSION = 2356;

@VisibleForTesting final String fileProviderName;

Expand Down Expand Up @@ -206,6 +209,42 @@ private void launchPickVideoFromGalleryIntent() {
activity.startActivityForResult(pickVideoIntent, REQUEST_CODE_CHOOSE_VIDEO_FROM_GALLERY);
}

public void getMostRecentVideo(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_VIDEO_STORAGE_PERMISSION_LATEST);
return;
}

getMostRecentVideoFromGallery();
}

public void getMostRecentVideoFromGallery() {
String[] projection = new String[] {MediaStore.Video.VideoColumns.DATA};
Cursor cursor =
activity
.getApplicationContext()
.getContentResolver()
.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
projection,
null,
null,
MediaStore.Video.VideoColumns.DATE_TAKEN + " DESC");

if (cursor.moveToFirst()) {
handleVideoResult(cursor.getString(0));
} else {
finishWithSuccess(null);
}
}

public void takeVideoWithCamera(MethodCall methodCall, MethodChannel.Result result) {
if (!setPendingMethodCallAndResult(methodCall, result)) {
finishWithAlreadyActiveError();
Expand Down Expand Up @@ -262,6 +301,42 @@ private void launchPickImageFromGalleryIntent() {
activity.startActivityForResult(pickImageIntent, REQUEST_CODE_CHOOSE_IMAGE_FROM_GALLERY);
}

public void getMostRecentImage(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_IMAGE_STORAGE_PERMISSION_LATEST);
return;
}

getMostRecentImageFromGallery();
}

public void getMostRecentImageFromGallery() {
String[] projection = new String[] {MediaStore.Images.ImageColumns.DATA};
Cursor cursor =
activity
.getApplicationContext()
.getContentResolver()
.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
null,
null,
MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC");

if (cursor.moveToFirst()) {
handleImageResult(cursor.getString(0));
} else {
finishWithSuccess(null);
}
}

public void takeImageWithCamera(MethodCall methodCall, MethodChannel.Result result) {
if (!setPendingMethodCallAndResult(methodCall, result)) {
finishWithAlreadyActiveError();
Expand Down Expand Up @@ -342,11 +417,21 @@ public boolean onRequestPermissionsResult(
launchPickImageFromGalleryIntent();
}
break;
case REQUEST_EXTERNAL_IMAGE_STORAGE_PERMISSION_LATEST:
if (permissionGranted) {
getMostRecentImageFromGallery();
}
break;
case REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION:
if (permissionGranted) {
launchPickVideoFromGalleryIntent();
}
break;
case REQUEST_EXTERNAL_VIDEO_STORAGE_PERMISSION_LATEST:
if (permissionGranted) {
getMostRecentVideoFromGallery();
}
break;
case REQUEST_CAMERA_IMAGE_PERMISSION:
if (permissionGranted) {
launchTakeImageWithCameraIntent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
default:
throw new IllegalArgumentException("Invalid image source: " + imageSource);
}
} else if (call.method.equals("getMostRecentImage")) {
delegate.getMostRecentImage(call, result);
} else if (call.method.equals("pickVideo")) {
int imageSource = call.argument("source");
switch (imageSource) {
Expand All @@ -73,6 +75,8 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
default:
throw new IllegalArgumentException("Invalid video source: " + imageSource);
}
} else if (call.method.equals("getMostRecentVideo")) {
delegate.getMostRecentVideo(call, result);
} else {
throw new IllegalArgumentException("Unknown method " + call.method);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */,
532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
Expand Down Expand Up @@ -242,29 +241,14 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${PODS_ROOT}/.symlinks/flutter/ios/Flutter.framework",
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
Expand Down
111 changes: 85 additions & 26 deletions packages/image_picker/ios/Classes/ImagePickerPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,31 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
details:nil]);
break;
}
} else if ([@"getMostRecentImage" isEqualToString:call.method]) {
if (@available(iOS 8.0, *)) {
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];

PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];

if (assetsFetchResult.count > 0) {
PHAsset *asset = [assetsFetchResult objectAtIndex:0];
[[PHImageManager defaultManager] requestImageForAsset:asset
targetSize:CGSizeMake(0, 0)
contentMode:PHImageContentModeDefault
options:nil
resultHandler:^(UIImage *image, NSDictionary *info) {
self->_result = result;
self->_arguments = call.arguments;
[self onPickImage:image];
}
];
} else {
result(nil);
}
} else {
// Flutter minimum requirement is iOS 8.0, this should never happen
}
} else if ([@"pickVideo" isEqualToString:call.method]) {
_imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
_imagePickerController.delegate = self;
Expand Down Expand Up @@ -99,6 +124,29 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
details:nil]);
break;
}
} else if ([@"getMostRecentVideo" isEqualToString:call.method]) {
if (@available(iOS 8.0, *)) {
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];

PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:fetchOptions];

if (assetsFetchResult.count > 0) {
PHAsset *asset = [assetsFetchResult objectAtIndex:0];
[[PHImageManager defaultManager] requestAVAssetForVideo:asset
options:nil
resultHandler:^(AVAsset *video, AVAudioMix *audioMix, NSDictionary *info) {
self->_result = result;
self->_arguments = call.arguments;
[self onPickVideo:((AVURLAsset *)video).URL];
}
];
} else {
result(nil);
}
} else {
// Flutter minimum requirement is iOS 8.0, this should never happen
}
} else {
result(FlutterMethodNotImplemented);
}
Expand All @@ -124,16 +172,19 @@ - (void)showPhotoLibrary {
[_viewController presentViewController:_imagePickerController animated:YES completion:nil];
}

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info {
NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
[_imagePickerController dismissViewControllerAnimated:YES completion:nil];

if (videoURL != nil) {
NSData *data = [NSData dataWithContentsOfURL:videoURL];
- (void)onPickImage:(UIImage *)image {
UIImage *nImage = [self normalizedImage:image];

NSNumber *maxWidth = [_arguments objectForKey:@"maxWidth"];
NSNumber *maxHeight = [_arguments objectForKey:@"maxHeight"];

if (maxWidth != (id)[NSNull null] || maxHeight != (id)[NSNull null]) {
nImage = [self scaledImage:nImage maxWidth:maxWidth maxHeight:maxHeight];
}

NSData *data = UIImageJPEGRepresentation(nImage, 1.0);
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.MOV", guid];
NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.jpg", guid];
NSString *tmpDirectory = NSTemporaryDirectory();
NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile];

Expand All @@ -144,36 +195,44 @@ - (void)imagePickerController:(UIImagePickerController *)picker
message:@"Temporary file could not be created"
details:nil]);
}
} else {
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];
}
_result = nil;
_arguments = nil;
}

NSData *data = UIImageJPEGRepresentation(image, 1.0);
- (void)onPickVideo:(NSURL *)url {
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.jpg", guid];
NSString *tmpFile = [NSString stringWithFormat:@"image_picker_%@.MOV", 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;
_result = nil;
_arguments = nil;
}

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info {
NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL];
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
[_imagePickerController dismissViewControllerAnimated:YES completion:nil];

if (videoURL != nil) {
[self onPickVideo:videoURL];
} else {
if (image == nil) {
image = [info objectForKey:UIImagePickerControllerOriginalImage];
}
[self onPickImage:image];
}
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
Expand Down
37 changes: 37 additions & 0 deletions packages/image_picker/lib/image_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,34 @@ class ImagePicker {
return path == null ? null : new File(path);
}

/// Returns a [File] object pointing to the most recent image in the user's gallery.
///
/// If specified, the image will be at most [maxWidth] wide and
/// [maxHeight] tall. Otherwise the image will be returned at it's
/// original width and height.
static Future<File> getMostRecentImage({
double maxWidth,
double maxHeight,
}) async {
if (maxWidth != null && maxWidth < 0) {
throw new ArgumentError.value(maxWidth, 'maxWidth cannot be negative');
}

if (maxHeight != null && maxHeight < 0) {
throw new ArgumentError.value(maxHeight, 'maxHeight cannot be negative');
}

final String path = await _channel.invokeMethod(
'getMostRecentImage',
<String, dynamic>{
'maxWidth': maxWidth,
'maxHeight': maxHeight,
},
);

return path == null ? null : new File(path);
}

static Future<File> pickVideo({
@required ImageSource source,
}) async {
Expand All @@ -69,4 +97,13 @@ class ImagePicker {
);
return path == null ? null : new File(path);
}

/// Returns a [File] object pointing to the most recent video in the user's gallery.
static Future<File> getMostRecentVideo() async {
final String path = await _channel.invokeMethod(
'getMostRecentVideo'
);

return path == null ? null : new File(path);
}
}