diff --git a/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerPlugin.java b/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerPlugin.java index 8b2af83..04bf9dd 100644 --- a/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerPlugin.java +++ b/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerPlugin.java @@ -87,7 +87,7 @@ public class FilePickerPlugin implements MethodCallHandler { result.error(TAG, "Failed to retrieve path: " + e.getMessage(),null); } - Log.i(TAG, "Cloud file loaded and cached on:" + cloudFile); + Log.i(TAG, "Remote file loaded and cached at:" + cloudFile); fullPath = cloudFile; } Log.i(TAG, "Absolute file path:" + fullPath); @@ -151,6 +151,10 @@ public class FilePickerPlugin implements MethodCallHandler { } switch (type) { + case "AUDIO": + return "audio/*"; + case "IMAGE": + return "image/*"; case "VIDEO": return "video/*"; case "ANY": diff --git a/android/src/main/java/com/mr/flutter/plugin/filepicker/FileUtils.java b/android/src/main/java/com/mr/flutter/plugin/filepicker/FileUtils.java index 9a6bf56..57454d7 100644 --- a/android/src/main/java/com/mr/flutter/plugin/filepicker/FileUtils.java +++ b/android/src/main/java/com/mr/flutter/plugin/filepicker/FileUtils.java @@ -19,7 +19,7 @@ import android.webkit.MimeTypeMap; public class FileUtils { - private static final String tag = "FilePathPicker"; + private static final String tag = "FilePickerUtils"; public static String getPath(final Uri uri, Context context) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; @@ -38,27 +38,26 @@ public class FileUtils { @TargetApi(19) private static String getForApi19(Context context, Uri uri) { - Log.e(tag, "+++ API 19 URI :: " + uri); + Log.e(tag, " --- API 19 URI --- " + uri); if (DocumentsContract.isDocumentUri(context, uri)) { - Log.e(tag, "+++ Document URI"); + Log.e(tag, "--- Document URI ---"); if (isExternalStorageDocument(uri)) { - Log.e(tag, "+++ External Document URI"); + Log.e(tag, "--- External Document URI ---"); final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { - Log.e(tag, "+++ Primary External Document URI"); + Log.e(tag, "--- Primary External Document URI ---"); return Environment.getExternalStorageDirectory() + "/" + split[1]; } } else if (isDownloadsDocument(uri)) { - Log.e(tag, "+++ Downloads External Document URI"); + Log.e(tag, "--- Downloads External Document URI ---"); final String id = DocumentsContract.getDocumentId(uri); if (!TextUtils.isEmpty(id)) { if (id.startsWith("raw:")) { return id.replaceFirst("raw:", ""); } - String[] contentUriPrefixesToTry = new String[]{ "content://downloads/public_downloads", "content://downloads/my_downloads", @@ -72,26 +71,26 @@ public class FileUtils { return path; } } catch (Exception e) { - Log.e(tag, "+++ Something went wrong while retrieving document path: " + e.toString()); + Log.e(tag, "Something went wrong while retrieving document path: " + e.toString()); } } } } else if (isMediaDocument(uri)) { - Log.e(tag, "+++ Media Document URI"); + Log.e(tag, "--- Media Document URI ---"); final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { - Log.e(tag, "+++ Image Media Document URI"); + Log.e(tag, "--- Image Media Document URI ---"); contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { - Log.e(tag, "+++ Video Media Document URI"); + Log.e(tag, "--- Video Media Document URI ---"); contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { - Log.e(tag, "+++ Audio Media Document URI"); + Log.e(tag, "--- Audio Media Document URI ---"); contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } @@ -103,13 +102,13 @@ public class FileUtils { return getDataColumn(context, contentUri, selection, selectionArgs); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { - Log.e(tag, "+++ No DOCUMENT URI :: CONTENT "); + Log.e(tag, "--- NO DOCUMENT URI - CONTENT ---"); if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); return getDataColumn(context, uri, null, null); } else if ("file".equalsIgnoreCase(uri.getScheme())) { - Log.e(tag, "+++ No DOCUMENT URI :: FILE "); + Log.e(tag, "--- No DOCUMENT URI - FILE ---"); return uri.getPath(); } return null; diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 652a159..f626a5c 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2,26 +2,20 @@ PODS: - file_picker (0.0.1): - Flutter - Flutter (1.0.0) - - image_picker (0.0.1): - - Flutter DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `.symlinks/flutter/ios`) - - image_picker (from `.symlinks/plugins/image_picker/ios`) EXTERNAL SOURCES: file_picker: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: ".symlinks/flutter/ios" - image_picker: - :path: ".symlinks/plugins/image_picker/ios" SPEC CHECKSUMS: file_picker: 78c3344d9b2c343bb3090c2f032b796242ebaea7 Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 - image_picker: ee00aab0487cedc80a304085219503cc6d0f2e22 PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2 diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index a25de3b..114e0c7 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -22,10 +22,6 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS - NSCameraUsageDescription - Used to demonstrate image picker plugin - NSMicrophoneUsageDescription - Used to capture audio for image picker plugin NSPhotoLibraryUsageDescription Used to demonstrate image picker plugin UILaunchStoryboardName @@ -38,11 +34,11 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIBackgroundModes - - fetch - remote-notification - + UIBackgroundModes + + fetch + remote-notification + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/example/lib/main.dart b/example/lib/main.dart index 5e27ef0..5d85465 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -3,14 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:file_picker/file_picker.dart'; -void main() => runApp(new MyApp()); +void main() => runApp(new FilePickerDemo()); -class MyApp extends StatefulWidget { +class FilePickerDemo extends StatefulWidget { @override - _MyAppState createState() => new _MyAppState(); + _FilePickerDemoState createState() => new _FilePickerDemoState(); } -class _MyAppState extends State { +class _FilePickerDemoState extends State { String _fileName = '...'; String _path = '...'; String _extension; @@ -60,9 +60,13 @@ class _MyAppState extends State { hint: new Text('LOAD PATH FROM'), value: _pickingType, items: [ + // new DropdownMenuItem( + // child: new Text('FROM CAMERA'), + // value: FileType.CAMERA, + // ), new DropdownMenuItem( - child: new Text('FROM CAMERA'), - value: FileType.CAMERA, + child: new Text('FROM AUDIO'), + value: FileType.AUDIO, ), new DropdownMenuItem( child: new Text('FROM GALLERY'), diff --git a/ios/Classes/FilePickerPlugin.h b/ios/Classes/FilePickerPlugin.h index 145a322..fecbd3f 100644 --- a/ios/Classes/FilePickerPlugin.h +++ b/ios/Classes/FilePickerPlugin.h @@ -1,5 +1,7 @@ #import #import +#import +#import #import @interface FilePickerPlugin : NSObject diff --git a/ios/Classes/FilePickerPlugin.m b/ios/Classes/FilePickerPlugin.m index f9bc5d3..bfb6ff7 100644 --- a/ios/Classes/FilePickerPlugin.m +++ b/ios/Classes/FilePickerPlugin.m @@ -1,10 +1,13 @@ #import "FilePickerPlugin.h" #import "FileUtils.h" +#import "ImageUtils.h" -@interface FilePickerPlugin() +@interface FilePickerPlugin() @property (nonatomic) FlutterResult result; @property (nonatomic) UIViewController *viewController; +@property (nonatomic) UIImagePickerController *galleryPickerController; @property (nonatomic) UIDocumentPickerViewController *pickerController; +@property (nonatomic) MPMediaPickerController *audioPickerController; @property (nonatomic) UIDocumentInteractionController *interactionController; @property (nonatomic) NSString * fileType; @end @@ -32,16 +35,6 @@ return self; } -- (void)initPicker { - - self.pickerController = [[UIDocumentPickerViewController alloc] - initWithDocumentTypes:@[self.fileType] - inMode:UIDocumentPickerModeImport]; - - self.pickerController.modalPresentationStyle = UIModalPresentationCurrentContext; - self.pickerController.delegate = self; -} - - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if (_result) { _result([FlutterError errorWithCode:@"multiple_request" @@ -56,24 +49,77 @@ if([call.method isEqualToString:@"VIDEO"]) { [self resolvePickVideo]; } - else { + else if([call.method isEqualToString:@"AUDIO"]) { + [self resolvePickAudio]; + } + else if([call.method isEqualToString:@"IMAGE"]) { + [self resolvePickImage]; + } else { self.fileType = [FileUtils resolveType:call.method]; if(self.fileType == nil){ result(FlutterMethodNotImplemented); } else { - [self initPicker]; - [_viewController presentViewController:self.pickerController animated:YES completion:^{ - if (@available(iOS 11.0, *)) { - self.pickerController.allowsMultipleSelection = NO; - } - }]; - + [self resolvePickDocument]; } } } +#pragma mark - Resolvers + +- (void)resolvePickDocument { + + self.pickerController = [[UIDocumentPickerViewController alloc] + initWithDocumentTypes:@[self.fileType] + inMode:UIDocumentPickerModeImport]; + + if (@available(iOS 11.0, *)) { + self.pickerController.allowsMultipleSelection = NO; + } else { + // Fallback on earlier versions + } + self.pickerController.delegate = self; + self.pickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + self.galleryPickerController.allowsEditing = NO; + [_viewController presentViewController:self.pickerController animated:YES completion:nil]; +} + +- (void) resolvePickImage { + + self.galleryPickerController = [[UIImagePickerController alloc] init]; + self.galleryPickerController.delegate = self; + self.galleryPickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + self.galleryPickerController.mediaTypes = @[(NSString *)kUTTypeImage]; + self.galleryPickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + + [_viewController presentViewController:self.galleryPickerController animated:YES completion:nil]; +} + +- (void) resolvePickAudio { + + self.audioPickerController = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio]; + self.audioPickerController.delegate = self; + self.audioPickerController.showsCloudItems = NO; + self.audioPickerController.allowsPickingMultipleItems = NO; + self.audioPickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + [self.viewController presentViewController:self.audioPickerController animated:YES completion:nil]; +} + +- (void) resolvePickVideo { + + self.galleryPickerController = [[UIImagePickerController alloc] init]; + self.galleryPickerController.delegate = self; + self.galleryPickerController.modalPresentationStyle = UIModalPresentationCurrentContext; + self.galleryPickerController.mediaTypes = @[(NSString*)kUTTypeMovie, (NSString*)kUTTypeAVIMovie, (NSString*)kUTTypeVideo, (NSString*)kUTTypeMPEG4]; + self.galleryPickerController.videoQuality = UIImagePickerControllerQualityTypeHigh; + + [self.viewController presentViewController:self.galleryPickerController animated:YES completion:nil]; +} + +#pragma mark - Delegates + +// DocumentPicker delegate - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls{ @@ -82,23 +128,51 @@ didPickDocumentsAtURLs:(NSArray *)urls{ } -// VideoPicker delegate -- (void) resolvePickVideo{ - - UIImagePickerController *videoPicker = [[UIImagePickerController alloc] init]; - videoPicker.delegate = self; - videoPicker.modalPresentationStyle = UIModalPresentationCurrentContext; - videoPicker.mediaTypes = @[(NSString*)kUTTypeMovie, (NSString*)kUTTypeAVIMovie, (NSString*)kUTTypeVideo, (NSString*)kUTTypeMPEG4]; - videoPicker.videoQuality = UIImagePickerControllerQualityTypeHigh; - - [self.viewController presentViewController:videoPicker animated:YES completion:nil]; -} - +// ImagePicker delegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL]; + NSURL *pickedVideoUrl = [info objectForKey:UIImagePickerControllerMediaURL]; + NSURL *pickedImageUrl; + + if (@available(iOS 11.0, *)) { + pickedImageUrl = [info objectForKey:UIImagePickerControllerImageURL]; + } else { + UIImage *pickedImage = [info objectForKey:UIImagePickerControllerEditedImage]; + + if(pickedImage == nil) { + pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage]; + } + pickedImageUrl = [ImageUtils saveTmpImage:pickedImage]; + } + [picker dismissViewControllerAnimated:YES completion:NULL]; - _result([videoURL path]); + + if(pickedImageUrl == nil && pickedVideoUrl == nil) { + _result([FlutterError errorWithCode:@"file_picker_error" + message:@"Temporary file could not be created" + details:nil]); + } + + _result([pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]); +} + + +// AudioPicker delegate +- (void)mediaPicker: (MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection +{ + [mediaPicker dismissViewControllerAnimated:YES completion:NULL]; + NSURL *url = [[[mediaItemCollection items] objectAtIndex:0] valueForKey:MPMediaItemPropertyAssetURL]; + if(url == nil) { + NSLog(@"Couldn't retrieve the audio file path, either is not locally downloaded or the file DRM protected."); + } + _result([url absoluteString]); +} + +#pragma mark - Actions canceled + +- (void)mediaPickerDidCancel:(MPMediaPickerController *)controller { + _result = nil; + [controller dismissViewControllerAnimated:YES completion:NULL]; } - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { diff --git a/ios/Classes/FileUtils.m b/ios/Classes/FileUtils.m index ae24524..805ebca 100644 --- a/ios/Classes/FileUtils.m +++ b/ios/Classes/FileUtils.m @@ -23,10 +23,7 @@ return [UTIString containsString:@"dyn."] ? nil : UTIString; } - if ([type isEqualToString:@"PDF"]) { - return @"com.adobe.pdf"; - } - else if ([type isEqualToString:@"ANY"]) { + if ([type isEqualToString:@"ANY"]) { return @"public.item"; } else { return nil; diff --git a/ios/Classes/ImageUtils.h b/ios/Classes/ImageUtils.h new file mode 100644 index 0000000..53aefad --- /dev/null +++ b/ios/Classes/ImageUtils.h @@ -0,0 +1,11 @@ +// +// ImageUtils.h +// Pods +// +// Created by Miguel Ruivo on 05/03/2019. +// + +@interface ImageUtils : NSObject ++ (BOOL)hasAlpha:(UIImage *)image; ++ (NSURL*)saveTmpImage:(UIImage *)image; +@end diff --git a/ios/Classes/ImageUtils.m b/ios/Classes/ImageUtils.m new file mode 100644 index 0000000..df5b681 --- /dev/null +++ b/ios/Classes/ImageUtils.m @@ -0,0 +1,35 @@ +// +// ImageUtils.m +// file_picker +// +// Created by Miguel Ruivo on 05/03/2019. +// + +#import "ImageUtils.h" + +@implementation ImageUtils + +// Returns true if the image has an alpha layer ++ (BOOL)hasAlpha:(UIImage *)image { + CGImageAlphaInfo alpha = CGImageGetAlphaInfo(image.CGImage); + return (alpha == kCGImageAlphaFirst || alpha == kCGImageAlphaLast || + alpha == kCGImageAlphaPremultipliedFirst || alpha == kCGImageAlphaPremultipliedLast); +} + +// Save the image temporarly in the app's tmp directory ++ (NSURL *)saveTmpImage:(UIImage *)image { + BOOL hasAlpha = [ImageUtils hasAlpha:image]; + NSData *data = hasAlpha ? UIImagePNGRepresentation(image) : UIImageJPEGRepresentation(image, 1.0); + NSString *fileExtension = hasAlpha ? @"tmp_%@.png" : @"tmp_%@.jpg"; + NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; + NSString *tmpFile = [NSString stringWithFormat:fileExtension, guid]; + NSString *tmpDirectory = NSTemporaryDirectory(); + NSString *tmpPath = [tmpDirectory stringByAppendingPathComponent:tmpFile]; + + if ([[NSFileManager defaultManager] createFileAtPath:tmpPath contents:data attributes:nil]) { + return [NSURL URLWithString: tmpPath]; + } + return nil; +} + +@end diff --git a/lib/file_picker.dart b/lib/file_picker.dart index e1f60f2..4527d49 100644 --- a/lib/file_picker.dart +++ b/lib/file_picker.dart @@ -1,14 +1,15 @@ import 'dart:async'; import 'package:flutter/services.dart'; -import 'package:image_picker/image_picker.dart'; +// import 'package:image_picker/image_picker.dart'; /// Supported file types, [ANY] should be used if the file you need isn't listed enum FileType { ANY, IMAGE, VIDEO, - CAMERA, + AUDIO, + // CAMERA, CUSTOM, } @@ -28,15 +29,15 @@ class FilePicker { return null; } - static Future _getImage(ImageSource type) async { - try { - var image = await ImagePicker.pickImage(source: type); - return image?.path; - } on PlatformException catch (e) { - print("[$_tag] Platform exception: " + e.toString()); - } - return null; - } + // static Future _getImage(ImageSource type) async { + // try { + // var image = await ImagePicker.pickImage(source: type); + // return image?.path; + // } on PlatformException catch (e) { + // print("[$_tag] Platform exception: " + e.toString()); + // } + // return null; + // } /// Returns an absolute file path from the calling platform /// @@ -46,9 +47,11 @@ class FilePicker { static Future getFilePath({FileType type = FileType.ANY, String fileExtension}) async { switch (type) { case FileType.IMAGE: - return _getImage(ImageSource.gallery); - case FileType.CAMERA: - return _getImage(ImageSource.camera); + return _getPath('IMAGE'); + // case FileType.CAMERA: + // return _getImage(ImageSource.camera); + case FileType.AUDIO: + return _getPath('AUDIO'); case FileType.VIDEO: return _getPath('VIDEO'); case FileType.ANY: