diff --git a/.gitignore b/.gitignore index b4779f1..73ee123 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,33 @@ .DS_Store .atom/ -.idea +.idea/ +.vscode/ + .packages -.dart_tool/ .pub/ -build/ -ios/.generated/ -packages +.dart_tool/ pubspec.lock -.iml + +Podfile Podfile.lock -file_picker.iml \ No newline at end of file +Pods/ +.symlinks/ +**/Flutter/App.framework/ +**/Flutter/Flutter.framework/ +**/Flutter/Generated.xcconfig +**/Flutter/flutter_assets/ +ServiceDefinitions.json +xcuserdata/ + +local.properties +.gradle/ +gradlew +gradlew.bat +gradle-wrapper.jar +*.iml + +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m +GeneratedPluginRegistrant.java +build/ +.flutter-plugins diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5bfb4..8251cb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 1.1.0 + +**Breaking changes** + * `FileType.PDF` was removed since now it can be used along with custom file types by using the `FileType.CUSTOM` and providing the file extension (e.g. PDF, SVG, ZIP, etc.). + * `FileType.CAPTURE` is now `FileType.CAMERA` + +**New features** + * Now it is possible to provide a custom file extension to filter file picking options by using `FileType.CUSTOM` + +**Bug fixes and updates** + * Fixes file names from cloud on Android. Previously it would always display **Document** + * Fixes an issue on iOS where an exception was being thrown after canceling and re-opening the picker. + * Fixes an issue where collision could happen with request codes on Android. + * Adds public documentation to `file_picker` + * Example app updated. + * Updates .gitignore + ## 1.0.3 * Fixes `build.gradle`. diff --git a/README.md b/README.md index bc05d74..be20362 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,17 @@ # file_picker -File picker plugin alows you to use a native file explorer to load absolute file path from different types of files. +File picker plugin alows you to use a native file explorer to load absolute file path from different file types. ## Installation First, add *file_picker* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/). ``` -file_picker: ^1.0.2 +file_picker: ^1.1.0 ``` ## Android -Add `` to your app `AndroidManifest.xml` file. +Add `` to your app `AndroidManifest.xml` file. ## iOS Since we are using *image_picker* as a dependency from this plugin to load paths from gallery and camera, we need the following keys to your _Info.plist_ file, located in `/ios/Runner/Info.plist`: @@ -22,19 +22,19 @@ Since we are using *image_picker* as a dependency from this plugin to load paths * `NSPhotoLibraryUsageDescription` - describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor. * `NSCameraUsageDescription` - describe why your app needs access to the camera. This is called _Privacy - Camera Usage Description_ in the visual editor. * `NSMicrophoneUsageDescription` - describe why your app needs access to the microphone, if you intend to record videos. This is called _Privacy - Microphone Usage Description_ in the visual editor. +* `UIBackgroundModes` with the `fetch` and `remote-notifications` keys - describe why your app needs to access background taks, such downloading files (from cloud services) when not cached to locate path. This is called _Required background modes_, with the keys _App download content from network_ and _App downloads content in response to push notifications_ respectively in the visual editor (since both methods aren't actually overriden, not adding this property/keys may only display a warning, but shouldn't prevent its correct usage). -## To-do +## Currently supported features * [X] Load paths from **cloud files** (GDrive, Dropbox, iCloud) -* [X] Load path from **PDF** * [X] Load path from **gallery** * [X] Load path from **camera** * [X] Load path from **video** * [X] Load path from **any** type of file (without filtering) -* [ ] Load path from a **custom format** +* [X] Load path from a **custom format** by providing a file extension (pdf, svg, zip, etc.) ## Demo App -![Demo](https://github.com/miguelpruivo/plugins_flutter_file_picker/blob/master/example/demo.png) +![Demo](https://github.com/miguelpruivo/plugins_flutter_file_picker/blob/master/example/example.gif) ## Example ``` diff --git a/android/.gitignore b/android/.gitignore index adcdcf5..5aa89be 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,6 +1,7 @@ *.iml *.class .gradle +.idea/ /local.properties /.idea/workspace.xml /.idea/libraries 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 b49139d..7fbcfa9 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 @@ -6,11 +6,14 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; +import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.util.Log; +import android.webkit.MimeTypeMap; import java.io.BufferedOutputStream; +import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; @@ -25,10 +28,11 @@ import io.flutter.plugin.common.PluginRegistry.Registrar; /** FilePickerPlugin */ public class FilePickerPlugin implements MethodCallHandler { - private static final int REQUEST_CODE = 43; + private static final int REQUEST_CODE = FilePickerPlugin.class.hashCode() + 43; + private static final int PERM_CODE = FilePickerPlugin.class.hashCode() + 50; private static final String TAG = "FilePicker"; - private static final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE; + private static Result result; private static Registrar instance; private static String fileType; @@ -54,7 +58,7 @@ public class FilePickerPlugin implements MethodCallHandler { if(fullPath == null) { FileOutputStream fos = null; - cloudFile = instance.activeContext().getCacheDir().getAbsolutePath() + "/Document"; + cloudFile = instance.activeContext().getCacheDir().getAbsolutePath() + "/" + FileUtils.getFileName(uri, instance.activeContext()); try { fos = new FileOutputStream(cloudFile); @@ -78,7 +82,7 @@ public class FilePickerPlugin implements MethodCallHandler { e.printStackTrace(); } - Log.i(TAG, "Loaded file from cloud created on:" + cloudFile); + Log.i(TAG, "Cloud file loaded and cached on:" + cloudFile); fullPath = cloudFile; } @@ -94,7 +98,7 @@ public class FilePickerPlugin implements MethodCallHandler { instance.addRequestPermissionsResultListener(new PluginRegistry.RequestPermissionsResultListener() { @Override public boolean onRequestPermissionsResult(int requestCode, String[] strings, int[] grantResults) { - if (requestCode == 0 && grantResults.length > 0 + if (requestCode == PERM_CODE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startFileExplorer(fileType); return true; @@ -128,11 +132,20 @@ public class FilePickerPlugin implements MethodCallHandler { Activity activity = instance.activity(); Log.i(TAG, "Requesting permission: " + permission); String[] perm = { permission }; - ActivityCompat.requestPermissions(activity, perm, 0); + ActivityCompat.requestPermissions(activity, perm, PERM_CODE); } private String resolveType(String type) { + final boolean isCustom = type.contains("__CUSTOM_"); + + if(isCustom) { + final String extension = type.split("__CUSTOM_")[1].toLowerCase(); + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + Log.i(TAG, "Custom file type: " + mime); + return mime; + } + switch (type) { case "PDF": return "application/pdf"; @@ -152,14 +165,19 @@ public class FilePickerPlugin implements MethodCallHandler { Intent intent; if (checkPermission()) { - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT){ + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { intent = new Intent(Intent.ACTION_PICK); } else { intent = new Intent(Intent.ACTION_GET_CONTENT); } + Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator); + intent.setDataAndType(uri, type); intent.setType(type); intent.addCategory(Intent.CATEGORY_OPENABLE); + + Log.d(TAG, "Intent: " + intent.toString()); + instance.activity().startActivityForResult(intent, REQUEST_CODE); } else { requestPermission(); 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 ca034c0..9a6bf56 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 @@ -1,4 +1,5 @@ package com.mr.flutter.plugin.filepicker; + import android.annotation.TargetApi; import android.content.ContentUris; import android.content.Context; @@ -10,50 +11,42 @@ import android.provider.DocumentsContract; import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; +import android.webkit.MimeTypeMap; /** - * Credits to NiRRaNjAN from package in.gauriinfotech.commons;. + * Credits to NiRRaNjAN from utils extracted of in.gauriinfotech.commons;. **/ -public class FileUtils -{ + +public class FileUtils { private static final String tag = "FilePathPicker"; - public static String getPath(final Uri uri, Context context) - { + public static String getPath(final Uri uri, Context context) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - if (isKitKat) - { + if (isKitKat) { return getForApi19(context, uri); - } else if ("content".equalsIgnoreCase(uri.getScheme())) - { - if (isGooglePhotosUri(uri)) - { + } else if ("content".equalsIgnoreCase(uri.getScheme())) { + if (isGooglePhotosUri(uri)) { return uri.getLastPathSegment(); } return getDataColumn(context, uri, null, null); - } else if ("file".equalsIgnoreCase(uri.getScheme())) - { + } else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } @TargetApi(19) - private static String getForApi19(Context context, Uri uri) - { + private static String getForApi19(Context context, Uri uri) { Log.e(tag, "+++ API 19 URI :: " + uri); - if (DocumentsContract.isDocumentUri(context, uri)) - { + if (DocumentsContract.isDocumentUri(context, uri)) { Log.e(tag, "+++ Document URI"); - if (isExternalStorageDocument(uri)) - { + if (isExternalStorageDocument(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)) - { + if ("primary".equalsIgnoreCase(type)) { Log.e(tag, "+++ Primary External Document URI"); return Environment.getExternalStorageDirectory() + "/" + split[1]; } @@ -65,13 +58,25 @@ public class FileUtils if (id.startsWith("raw:")) { return id.replaceFirst("raw:", ""); } - try { - final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - return getDataColumn(context, contentUri, null, null); - } catch (Exception e) { - Log.e(tag, "+++ Something went wrong while retrieving document path: " + e.toString()); + + String[] contentUriPrefixesToTry = new String[]{ + "content://downloads/public_downloads", + "content://downloads/my_downloads", + "content://downloads/all_downloads" + }; + for (String contentUriPrefix : contentUriPrefixesToTry) { + Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)); + try { + String path = getDataColumn(context, contentUri, null, null); + if (path != null) { + return path; + } + } catch (Exception e) { + Log.e(tag, "+++ Something went wrong while retrieving document path: " + e.toString()); + } + } + } - } } else if (isMediaDocument(uri)) { Log.e(tag, "+++ Media Document URI"); final String docId = DocumentsContract.getDocumentId(uri); @@ -79,16 +84,13 @@ public class FileUtils final String type = split[0]; Uri contentUri = null; - if ("image".equals(type)) - { + if ("image".equals(type)) { Log.e(tag, "+++ Image Media Document URI"); contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) - { + } else if ("video".equals(type)) { Log.e(tag, "+++ Video Media Document URI"); contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) - { + } else if ("audio".equals(type)) { Log.e(tag, "+++ Audio Media Document URI"); contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } @@ -100,15 +102,13 @@ public class FileUtils return getDataColumn(context, contentUri, selection, selectionArgs); } - } else if ("content".equalsIgnoreCase(uri.getScheme())) - { + } else if ("content".equalsIgnoreCase(uri.getScheme())) { 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())) - { + } else if ("file".equalsIgnoreCase(uri.getScheme())) { Log.e(tag, "+++ No DOCUMENT URI :: FILE "); return uri.getPath(); } @@ -116,47 +116,74 @@ public class FileUtils } private static String getDataColumn(Context context, Uri uri, String selection, - String[] selectionArgs) - { + String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; - try - { + try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) - { + if (cursor != null && cursor.moveToFirst()) { final int index = cursor.getColumnIndexOrThrow(column); return cursor.getString(index); } - } finally - { + } finally { if (cursor != null) cursor.close(); } return null; } - private static boolean isExternalStorageDocument(Uri uri) - { + public static String getFileName(Uri uri, Context context) { + String result = null; + + //if uri is content + if (uri.getScheme() != null && uri.getScheme().equals("content")) { + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + //local filesystem + int index = cursor.getColumnIndex("_data"); + if (index == -1) + //google drive + index = cursor.getColumnIndex("_display_name"); + result = cursor.getString(index); + if (result != null) + uri = Uri.parse(result); + else + return null; + } + } finally { + cursor.close(); + } + } + + if(uri.getPath() != null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) + result = result.substring(cut + 1); + } + + return result; + } + + + private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } - private static boolean isDownloadsDocument(Uri uri) - { + private static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } - private static boolean isMediaDocument(Uri uri) - { + private static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } - private static boolean isGooglePhotosUri(Uri uri) - { + private static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 012df4a..0000000 --- a/example/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# file_picker_example - -Demonstrates how to use the file_picker plugin. - -## Getting Started - -For help getting started with Flutter, view our online -[documentation](https://flutter.io/). diff --git a/example/demo.png b/example/demo.png deleted file mode 100644 index 3811f04..0000000 Binary files a/example/demo.png and /dev/null differ diff --git a/example/example.gif b/example/example.gif new file mode 100644 index 0000000..77ffada Binary files /dev/null and b/example/example.gif differ diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index b841323..a25de3b 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -38,6 +38,11 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIBackgroundModes + + fetch + remote-notification + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/example/lib/main.dart b/example/lib/main.dart index 39d5966..5e27ef0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -13,20 +13,31 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { String _fileName = '...'; String _path = '...'; - FileType _pickingType = FileType.ANY; + String _extension; + bool _hasValidMime = false; + FileType _pickingType; + TextEditingController _controller = new TextEditingController(); + + @override + void initState() { + super.initState(); + _controller.addListener(() => _extension = _controller.text); + } void _openFileExplorer() async { - try { - _path = await FilePicker.getFilePath(type: _pickingType); - } on PlatformException catch (e) { - print(e.toString()); + if (_pickingType != FileType.CUSTOM || _hasValidMime) { + try { + _path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension); + } on PlatformException catch (e) { + print("Unsupported operation" + e.toString()); + } + + if (!mounted) return; + + setState(() { + _fileName = _path != null ? _path.split('/').last : '...'; + }); } - - if (!mounted) return; - - setState(() { - _fileName = _path != null ? _path.split('/').last : '...'; - }); } @override @@ -36,78 +47,97 @@ class _MyAppState extends State { appBar: new AppBar( title: const Text('Plugin example app'), ), - body: new Center( + body: SingleChildScrollView( + child: new Center( + child: new Padding( + padding: const EdgeInsets.only(top: 50.0, left: 10.0, right: 10.0), child: new Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - new Padding( - padding: const EdgeInsets.all(20.0), - child: new DropdownButton( - hint: new Text('LOAD FILE PATH FROM...'), - value: _pickingType, - items: [ - new DropdownMenuItem( - child: new Text('FROM CAMERA'), - value: FileType.CAPTURE, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + new Padding( + padding: const EdgeInsets.only(top: 20.0), + child: new DropdownButton( + 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 GALLERY'), + value: FileType.IMAGE, + ), + new DropdownMenuItem( + child: new Text('FROM VIDEO'), + value: FileType.VIDEO, + ), + new DropdownMenuItem( + child: new Text('FROM ANY'), + value: FileType.ANY, + ), + new DropdownMenuItem( + child: new Text('CUSTOM FORMAT'), + value: FileType.CUSTOM, + ), + ], + onChanged: (value) => setState(() => _pickingType = value)), + ), + new ConstrainedBox( + constraints: new BoxConstraints(maxWidth: 150.0), + child: _pickingType == FileType.CUSTOM + ? new TextFormField( + maxLength: 20, + autovalidate: true, + controller: _controller, + decoration: InputDecoration(labelText: 'File type'), + keyboardType: TextInputType.text, + textCapitalization: TextCapitalization.none, + validator: (value) { + RegExp reg = new RegExp(r'[^a-zA-Z0-9]'); + if (reg.hasMatch(value)) { + _hasValidMime = false; + return 'Invalid format'; + } + _hasValidMime = true; + }, + ) + : new Container(), + ), + new Padding( + padding: const EdgeInsets.only(top: 50.0, bottom: 20.0), + child: new RaisedButton( + onPressed: () => _openFileExplorer(), + child: new Text("Open file picker"), ), - new DropdownMenuItem( - child: new Text('FROM GALLERY'), - value: FileType.IMAGE, + ), + new Text( + 'URI PATH ', + textAlign: TextAlign.center, + style: new TextStyle(fontWeight: FontWeight.bold), + ), + new Text( + _path ?? '...', + textAlign: TextAlign.center, + softWrap: true, + textScaleFactor: 0.85, + ), + new Padding( + padding: const EdgeInsets.only(top: 10.0), + child: new Text( + 'FILE NAME ', + textAlign: TextAlign.center, + style: new TextStyle(fontWeight: FontWeight.bold), ), - new DropdownMenuItem( - child: new Text('FROM PDF'), - value: FileType.PDF, - ), - new DropdownMenuItem( - child: new Text('FROM VIDEO'), - value: FileType.VIDEO, - ), - new DropdownMenuItem( - child: new Text('FROM ANY'), - value: FileType.ANY, - ) - ], - onChanged: (value) { - setState( - () { - _pickingType = value; - }, - ); - }, - ), + ), + new Text( + _fileName, + textAlign: TextAlign.center, + ), + ], ), - new Padding( - padding: const EdgeInsets.all(20.0), - child: new RaisedButton( - onPressed: () => _openFileExplorer(), - child: new Text("Open file picker"), - ), - ), - new Text( - 'URI PATH ', - textAlign: TextAlign.center, - style: new TextStyle(fontWeight: FontWeight.bold), - ), - new Text( - _path ?? '...', - textAlign: TextAlign.center, - softWrap: true, - textScaleFactor: 0.85, - ), - new Padding( - padding: const EdgeInsets.only(top: 10.0), - child: new Text( - 'FILE NAME ', - textAlign: TextAlign.center, - style: new TextStyle(fontWeight: FontWeight.bold), - ), - ), - new Text( - _fileName, - textAlign: TextAlign.center, - ), - ], - )), + )), + ), ), ); } diff --git a/ios/Classes/FilePickerPlugin.h b/ios/Classes/FilePickerPlugin.h index d7d2a7d..145a322 100644 --- a/ios/Classes/FilePickerPlugin.h +++ b/ios/Classes/FilePickerPlugin.h @@ -2,5 +2,5 @@ #import #import -@interface FilePickerPlugin : NSObject +@interface FilePickerPlugin : NSObject @end diff --git a/ios/Classes/FilePickerPlugin.m b/ios/Classes/FilePickerPlugin.m index 4823c4c..f9bc5d3 100644 --- a/ios/Classes/FilePickerPlugin.m +++ b/ios/Classes/FilePickerPlugin.m @@ -83,7 +83,6 @@ didPickDocumentsAtURLs:(NSArray *)urls{ // VideoPicker delegate - - (void) resolvePickVideo{ UIImagePickerController *videoPicker = [[UIImagePickerController alloc] init]; @@ -102,7 +101,13 @@ didPickDocumentsAtURLs:(NSArray *)urls{ _result([videoURL path]); } +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { + _result = nil; + [controller dismissViewControllerAnimated:YES completion:NULL]; +} + - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { + _result = nil; [picker dismissViewControllerAnimated:YES completion:NULL]; } diff --git a/ios/Classes/FileUtils.h b/ios/Classes/FileUtils.h index c5dbd83..a02ba06 100644 --- a/ios/Classes/FileUtils.h +++ b/ios/Classes/FileUtils.h @@ -4,6 +4,7 @@ // // Created by Miguel Ruivo on 05/12/2018. // +#import @interface FileUtils : NSObject + (NSString*) resolveType:(NSString*)type; + (NSString*) resolvePath:(NSArray *)urls; diff --git a/ios/Classes/FileUtils.m b/ios/Classes/FileUtils.m index 10576ab..ae24524 100644 --- a/ios/Classes/FileUtils.m +++ b/ios/Classes/FileUtils.m @@ -11,6 +11,18 @@ + (NSString*) resolveType:(NSString*)type { + BOOL isCustom = [type containsString:@"__CUSTOM_"]; + + if(isCustom) { + type = [type stringByReplacingOccurrencesOfString:@"__CUSTOM_" withString:@""]; + NSString * format = [NSString stringWithFormat:@"dummy.%@", type]; + CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[format pathExtension], NULL); + NSString * UTIString = (__bridge NSString *)(UTI); + CFRelease(UTI); + NSLog(@"Custom file type: %@", UTIString); + return [UTIString containsString:@"dyn."] ? nil : UTIString; + } + if ([type isEqualToString:@"PDF"]) { return @"com.adobe.pdf"; } diff --git a/lib/file_picker.dart b/lib/file_picker.dart index c722e9d..e1f60f2 100644 --- a/lib/file_picker.dart +++ b/lib/file_picker.dart @@ -6,36 +6,55 @@ 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, - PDF, IMAGE, VIDEO, - CAPTURE, + CAMERA, + CUSTOM, } class FilePicker { static const MethodChannel _channel = const MethodChannel('file_picker'); + static const String _tag = 'FilePicker'; - static Future _getPath(String type) async => await _channel.invokeMethod(type); - - static Future _getImage(ImageSource type) async { - var image = await ImagePicker.pickImage(source: type); - - return image?.path; + static Future _getPath(String type) async { + try { + return await _channel.invokeMethod(type); + } on PlatformException catch (e) { + print("[$_tag] Platform exception: " + e.toString()); + } catch (e) { + print( + "[$_tag] Unsupported operation. This probably have happened because [${type.split('_').last}] is an unsupported file type. You may want to try FileType.ALL instead."); + } + return null; } - /// Returns a [String] with the absolute path for the selected file - static Future getFilePath({FileType type = FileType.ANY}) async { + 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 + /// + /// A [type] must be provided to filter the picking results. + /// Can be used a custom file type with `FileType.CUSTOM`. A [fileExtension] must be provided (e.g. PDF, SVG, etc.) + /// Defaults to `FileType.ANY` which will display all file types. + static Future getFilePath({FileType type = FileType.ANY, String fileExtension}) async { switch (type) { case FileType.IMAGE: return _getImage(ImageSource.gallery); - case FileType.CAPTURE: + case FileType.CAMERA: return _getImage(ImageSource.camera); - case FileType.PDF: - return _getPath('PDF'); case FileType.VIDEO: return _getPath('VIDEO'); case FileType.ANY: return _getPath('ANY'); + case FileType.CUSTOM: + return _getPath('__CUSTOM_' + (fileExtension ?? '')); default: return _getPath('ANY'); } diff --git a/pubspec.yaml b/pubspec.yaml index 71f999d..909ddab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: file_picker description: A plugin that allows you to pick absolute paths from diferent file types. -version: 1.0.3 +version: 1.1.0 author: Miguel Ruivo homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker @@ -23,33 +23,3 @@ flutter: androidPackage: com.mr.flutter.plugin.filepicker pluginClass: FilePickerPlugin - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.io/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.io/assets-and-images/#resolution-aware. - - # To add custom fonts to your plugin package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.io/custom-fonts/#from-packages