Merge pull request #46 from miguelpruivo/Working-on-roadmap-1.3.0

Working on roadmap 1.3.0
This commit is contained in:
Miguel Ruivo 2019-03-12 01:27:00 +00:00 committed by GitHub
commit 9377775f32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 228 additions and 174 deletions

View File

@ -6,6 +6,7 @@
**New features** **New features**
* You can now pick multiple files by using the `getMultiFilePath()` method which will return a `Map<String,String>` with all paths from selected files, where the key matches the file name and the value its path. Optionally, it also supports filtering by file extension, otherwise all files will be selectable. Nevertheless, you should keep using `getFilePath()` for single path picking. * You can now pick multiple files by using the `getMultiFilePath()` method which will return a `Map<String,String>` with all paths from selected files, where the key matches the file name and the value its path. Optionally, it also supports filtering by file extension, otherwise all files will be selectable. Nevertheless, you should keep using `getFilePath()` for single path picking.
* You can now use `FileType.AUDIO` to pick audio files. In iOS this will let you select from your music library. Paths from DRM protected files won't be loaded (see README for more details). * You can now use `FileType.AUDIO` to pick audio files. In iOS this will let you select from your music library. Paths from DRM protected files won't be loaded (see README for more details).
* Adds `getFile()` utility method that does the same of `getFilePath()` but returns a `File` object instead, for the returned path.
**Bug fixes and updates** **Bug fixes and updates**
* This package is no longer attached to the [image_picker](https://pub.dartlang.org/packages/image_picker), and because of that, camera permission is also no longer required. * This package is no longer attached to the [image_picker](https://pub.dartlang.org/packages/image_picker), and because of that, camera permission is also no longer required.

View File

@ -3,7 +3,7 @@
# file_picker # file_picker
File picker plugin alows you to use a native file explorer to load absolute file path from different file types. A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support.
## Installation ## Installation
@ -12,17 +12,39 @@ First, add *file_picker* as a dependency in [your pubspec.yaml file](https://f
``` ```
file_picker: ^1.3.0 file_picker: ^1.3.0
``` ```
## Android ### Android
Add `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>` to your app `AndroidManifest.xml` file. This is required due to file caching when a path is required from a remote file (eg. Google Drive). Add `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>` to your app `AndroidManifest.xml` file. This is required due to file caching when a path is required from a remote file (eg. Google Drive).
## iOS ### iOS
Based on the location of the files that you are willing to pick paths, you may need to add some keys to your iOS app's _Info.plist_ file, located in `<project root>/ios/Runner/Info.plist`: Based on the location of the files that you are willing to pick paths, you may need to add some keys to your iOS app's _Info.plist_ file, located in `<project root>/ios/Runner/Info.plist`:
* **_UIBackgroundModes_** with the **_fetch_** and **_remote-notifications_** keys - Required if you'll be using the `FileType.ANY` or `FileType.CUSTOM`. Describe why your app needs to access background taks, such downloading files (from cloud services). 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).
```
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
```
* **_NSAppleMusicUsageDescription_** - Required if you'll be using the `FileType.AUDIO`. Describe why your app needs permission to access music library. This is called _Privacy - Media Library Usage Description_ in the visual editor. * **_NSAppleMusicUsageDescription_** - Required if you'll be using the `FileType.AUDIO`. Describe why your app needs permission to access music library. This is called _Privacy - Media Library Usage Description_ in the visual editor.
```
<key>NSAppleMusicUsageDescription</key>
<string>Explain why your app uses music</string>
```
* **_NSPhotoLibraryUsageDescription_** - Required if you'll be using the `FileType.IMAGE` or `FileType.VIDEO`. Describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor. * **_NSPhotoLibraryUsageDescription_** - Required if you'll be using the `FileType.IMAGE` or `FileType.VIDEO`. Describe why your app needs permission for the photo library. This is called _Privacy - Photo Library Usage Description_ in the visual editor.
* **_UIBackgroundModes_** with the **_fetch_** and **_remote-notifications_** keys - Required if you'll be using the `FileType.ANY` or `FileType.CUSTOM`. 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). ```
<key>NSPhotoLibraryUsageDescription</key>
<string>Explain why your app uses photo library</string>
```
**Note:** Any iOS version below 11.0, will require an Apple Developer Program account to enable _CloudKit_ and make it possible to use the document picker (which happens when you select `FileType.ALL`, `FileType.CUSTOM` or any other option with `getMultiFilePath()`). You can read more about it [here]( https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/CloudKitQuickStart/EnablingiCloudandConfiguringCloudKit/EnablingiCloudandConfiguringCloudKit.html).
## Usage ## Usage
There are only two methods that should be used with this package: There are only two methods that should be used with this package:
@ -48,31 +70,37 @@ So, a few example usages can be as follow:
``` ```
// Single file path // Single file path
String filePath; String filePath;
filePath = await FilePicker.getFilePath(type: FileType.ANY); // will let you pick one file, from all extensions filePath = await FilePicker.getFilePath(type: FileType.ANY); // will let you pick one file path, from all extensions
filePath = await FilePicker.getFilePath(type: FileType.CUSTOM, fileExtension: 'svg'); // will filter and only let you pick files with svg extension. filePath = await FilePicker.getFilePath(type: FileType.CUSTOM, fileExtension: 'svg'); // will filter and only let you pick files with svg extension
// Pick a single file directly
File file = await FilePicker.getFile(type: FileType.ANY); // will return a File object directly from the selected file
// Multi file path // Multi file path
Map<String,String> filesPaths; Map<String,String> filesPaths;
filePaths = await FilePicker.getMultiFilePath(); // will let you pick multiple files of any format at once filePaths = await FilePicker.getMultiFilePath(); // will let you pick multiple files of any format at once
filePaths = await FilePicker.getMultiFilePath(fileExtension: 'pdf'); // will let you pick multiple pdf files at once filePaths = await FilePicker.getMultiFilePath(fileExtension: 'pdf'); // will let you pick multiple pdf files at once
filePaths = await FilePicker.getMultiFilePath(type: FileType.IMAGE); // will let you pick multiple image files at once
List<String> allNames = filePaths.keys; // List of all file names List<String> allNames = filePaths.keys; // List of all file names
List<String> allPaths = filePaths.values; // List of all paths List<String> allPaths = filePaths.values; // List of all paths
String someFilePath = filePaths['fileName']; // Access a file path directly by its name (matching a key) String someFilePath = filePaths['fileName']; // Access a file path directly by its name (matching a key)
``` ```
##### A few notes ##### A few side notes
* Using `getMultiFilePath()` on iOS will always use the document picker (aka Files app). This means that multi picks are not currently supported for photo library images/videos or music library files.
* When using `FileType.CUSTOM`, unsupported extensions will throw a `MissingPluginException` that is handled by the plugin. * When using `FileType.CUSTOM`, unsupported extensions will throw a `MissingPluginException` that is handled by the plugin.
* On Android, when available, you should avoid using custom file explorers as those may prevent file extension filtering (behaving as `FileType.ANY`). In this scenario, you will need to validate it on return. * On Android, when available, you should avoid using third-party file explorers as those may prevent file extension filtering (behaving as `FileType.ANY`). In this scenario, you will need to validate it on return.
## Currently supported features ## Currently supported features
* [X] Load paths from **cloud files** (GDrive, Dropbox, iCloud) * [X] Load paths from **cloud files** (GDrive, Dropbox, iCloud)
* [X] Load path from a **custom format** by providing a file extension (pdf, svg, zip, etc.) * [X] Load path from a **custom format** by providing a file extension (pdf, svg, zip, etc.)
* [X] Load path from **multiple files** with an optional file extension * [X] Load path from **multiple files** optionally, supplying a file extension
* [X] Load path from **gallery** * [X] Load path from **gallery**
* [X] Load path from **audio** * [X] Load path from **audio**
* [X] Load path from **video** * [X] Load path from **video**
* [X] Load path from **any** file type (without filtering, just pick what you want) * [X] Load path from **any**
* [X] Create a `File` object from **any** selected file
## Demo App ## Demo App
@ -88,3 +116,5 @@ For help getting started with Flutter, view our online
For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code). For help on editing plugin code, view the [documentation](https://flutter.io/platform-plugins/#edit-code).

View File

@ -65,8 +65,6 @@ public class FilePickerPlugin implements MethodCallHandler {
Uri uri = data.getData(); Uri uri = data.getData();
Log.i(TAG, "[SingleFilePick] File URI:" +data.getData().toString()); Log.i(TAG, "[SingleFilePick] File URI:" +data.getData().toString());
String fullPath = FileUtils.getPath(uri, instance.context()); String fullPath = FileUtils.getPath(uri, instance.context());
String cloudFile = null;
if(fullPath == null) { if(fullPath == null) {
fullPath = FileUtils.getUriFromRemote(instance.activeContext(), uri, result); fullPath = FileUtils.getUriFromRemote(instance.activeContext(), uri, result);
@ -84,7 +82,7 @@ public class FilePickerPlugin implements MethodCallHandler {
result.success(null); result.success(null);
return true; return true;
} }
result.error(TAG, "Unknown activity error, please report issue." ,null); result.error(TAG, "Unknown activity error, please fill an issue." ,null);
return false; return false;
} }
}); });
@ -110,6 +108,8 @@ public class FilePickerPlugin implements MethodCallHandler {
if(fileType == null) { if(fileType == null) {
result.notImplemented(); result.notImplemented();
} else if(fileType.equals("unsupported")) {
result.error(TAG, "Unsupported filter. Make sure that you are only using the extension without the dot, (ie., jpg instead of .jpg). This could also have happened because you are using an unsupported file extension. If the problem persists, you may want to consider using FileType.ALL instead." ,null);
} else { } else {
startFileExplorer(fileType); startFileExplorer(fileType);
} }
@ -136,6 +136,7 @@ public class FilePickerPlugin implements MethodCallHandler {
if(isCustom) { if(isCustom) {
final String extension = type.split("__CUSTOM_")[1].toLowerCase(); final String extension = type.split("__CUSTOM_")[1].toLowerCase();
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
mime = mime == null ? "unsupported" : mime;
Log.i(TAG, "Custom file type: " + mime); Log.i(TAG, "Custom file type: " + mime);
return mime; return mime;
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

View File

@ -8,15 +8,12 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 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, ); }; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
94EE95F5D222CC3C902F7AA8 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E29C2B321AA1B6738D05DCC /* libPods-Runner.a */; }; 94EE95F5D222CC3C902F7AA8 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E29C2B321AA1B6738D05DCC /* libPods-Runner.a */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 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, ); }; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@ -42,7 +39,6 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; 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>"; }; 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>"; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@ -77,7 +73,6 @@
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
3B80C3931E831B6300D905FE /* App.framework */, 3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEBA1CF902C7004384FC /* Flutter.framework */,
@ -177,7 +172,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0910; LastUpgradeCheck = 1010;
ORGANIZATIONNAME = "The Chromium Authors"; ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
@ -210,11 +205,8 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -335,12 +327,14 @@
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@ -389,12 +383,14 @@
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0910" LastUpgradeVersion = "1010"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -18,12 +18,14 @@
<string>$(FLUTTER_BUILD_NAME)</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>NSAppleMusicUsageDescription</key>
<string>Used to demonstrate file picker plugin</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>Used to demonstrate image picker plugin</string> <string>Used to demonstrate file picker plugin</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>

View File

@ -31,7 +31,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
try { try {
if (_multiPick) { if (_multiPick) {
_path = null; _path = null;
_paths = await FilePicker.getMultiFilePath(fileExtension: _extension); _paths = await FilePicker.getMultiFilePath(type: _pickingType, fileExtension: _extension);
} else { } else {
_paths = null; _paths = null;
_path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension); _path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension);
@ -54,58 +54,55 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
appBar: new AppBar( appBar: new AppBar(
title: const Text('File Picker example app'), title: const Text('File Picker example app'),
), ),
body: SingleChildScrollView( body: new Center(
child: new Center( child: new Padding(
child: new Padding( padding: const EdgeInsets.only(top: 50.0, left: 10.0, right: 10.0),
padding: const EdgeInsets.only(top: 50.0, left: 10.0, right: 10.0), child: new SingleChildScrollView(
child: new ConstrainedBox( child: new Column(
constraints: new BoxConstraints(maxWidth: 200.0), mainAxisAlignment: MainAxisAlignment.center,
child: new Column( children: <Widget>[
mainAxisAlignment: MainAxisAlignment.center, new Padding(
children: <Widget>[ padding: const EdgeInsets.only(top: 20.0),
new Padding( child: new DropdownButton(
padding: const EdgeInsets.only(top: 20.0), hint: new Text('LOAD PATH FROM'),
child: new DropdownButton( value: _pickingType,
hint: new Text('LOAD PATH FROM'), items: <DropdownMenuItem>[
value: _pickingType, new DropdownMenuItem(
items: <DropdownMenuItem>[ child: new Text('FROM AUDIO'),
new DropdownMenuItem( value: FileType.AUDIO,
child: new Text('FROM AUDIO'), ),
value: FileType.AUDIO, new DropdownMenuItem(
), child: new Text('FROM IMAGE'),
new DropdownMenuItem( value: FileType.IMAGE,
child: new Text('FROM GALLERY'), ),
value: FileType.IMAGE, new DropdownMenuItem(
), child: new Text('FROM VIDEO'),
new DropdownMenuItem( value: FileType.VIDEO,
child: new Text('FROM VIDEO'), ),
value: FileType.VIDEO, new DropdownMenuItem(
), child: new Text('FROM ANY'),
new DropdownMenuItem( value: FileType.ANY,
child: new Text('FROM ANY'), ),
value: FileType.ANY, new DropdownMenuItem(
), child: new Text('CUSTOM FORMAT'),
new DropdownMenuItem( value: FileType.CUSTOM,
child: new Text('CUSTOM FORMAT'), ),
value: FileType.CUSTOM, ],
), onChanged: (value) => setState(() {
], _pickingType = value;
onChanged: (value) => setState(() { if (_pickingType != FileType.CUSTOM) {
_pickingType = value; _controller.text = _extension = '';
if (_pickingType != FileType.CUSTOM && _pickingType != FileType.ANY) { }
_multiPick = false; })),
} ),
if (_pickingType != FileType.CUSTOM) { ConstrainedBox(
_controller.text = _extension = ''; constraints: BoxConstraints.tightFor(width: 100.0),
} child: _pickingType == FileType.CUSTOM
})),
),
_pickingType == FileType.CUSTOM
? new TextFormField( ? new TextFormField(
maxLength: 20, maxLength: 15,
autovalidate: true, autovalidate: true,
controller: _controller, controller: _controller,
decoration: InputDecoration(labelText: 'File type'), decoration: InputDecoration(labelText: 'File extension'),
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.none, textCapitalization: TextCapitalization.none,
validator: (value) { validator: (value) {
@ -118,49 +115,52 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
}, },
) )
: new Container(), : new Container(),
new Visibility( ),
visible: _pickingType == FileType.ANY || _pickingType == FileType.CUSTOM, new ConstrainedBox(
child: new SwitchListTile.adaptive( constraints: BoxConstraints.tightFor(width: 200.0),
title: new Text('Pick multiple files', textAlign: TextAlign.right), child: new SwitchListTile.adaptive(
onChanged: (bool value) => setState(() => _multiPick = value), title: new Text('Pick multiple files', textAlign: TextAlign.right),
value: _multiPick, onChanged: (bool value) => setState(() => _multiPick = value),
), value: _multiPick,
), ),
new Padding( ),
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0), new Padding(
child: new RaisedButton( padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
onPressed: () => _openFileExplorer(), child: new RaisedButton(
child: new Text("Open file picker"), onPressed: () => _openFileExplorer(),
), child: new Text("Open file picker"),
), ),
new Text( ),
'URI PATH ', new Builder(
textAlign: TextAlign.center, builder: (BuildContext context) => new Container(
style: new TextStyle(fontWeight: FontWeight.bold), padding: const EdgeInsets.only(bottom: 30.0),
), height: MediaQuery.of(context).size.height * 0.50,
new Text( child: new Scrollbar(
_path ?? ((_paths != null && _paths.isNotEmpty) ? _paths.values.map((path) => path + '\n\n').toString() : '...'), child: _path != null || _paths != null
textAlign: TextAlign.center, ? new ListView.separated(
softWrap: true, itemCount: _paths != null && _paths.isNotEmpty ? _paths.length : 1,
textScaleFactor: 0.85, itemBuilder: (BuildContext context, int index) {
), final bool isMultiPath = _paths != null && _paths.isNotEmpty;
new Padding( final String name = 'File $index: ' + (isMultiPath ? _paths.keys.toList()[index] : _fileName ?? '...');
padding: const EdgeInsets.only(top: 10.0), final path = isMultiPath ? _paths.values.toList()[index].toString() : _path;
child: new Text(
'FILE NAME ', return new ListTile(
textAlign: TextAlign.center, title: new Text(
style: new TextStyle(fontWeight: FontWeight.bold), name,
), ),
), subtitle: new Text(path),
new Text( );
_fileName ?? '...', },
textAlign: TextAlign.center, separatorBuilder: (BuildContext context, int index) => new Divider(),
), )
], : new Container(),
), ),
),
),
],
), ),
)), ),
), )),
), ),
); );
} }

View File

@ -6,7 +6,7 @@
@property (nonatomic) FlutterResult result; @property (nonatomic) FlutterResult result;
@property (nonatomic) UIViewController *viewController; @property (nonatomic) UIViewController *viewController;
@property (nonatomic) UIImagePickerController *galleryPickerController; @property (nonatomic) UIImagePickerController *galleryPickerController;
@property (nonatomic) UIDocumentPickerViewController *pickerController; @property (nonatomic) UIDocumentPickerViewController *documentPickerController;
@property (nonatomic) UIDocumentInteractionController *interactionController; @property (nonatomic) UIDocumentInteractionController *interactionController;
@property (nonatomic) MPMediaPickerController *audioPickerController; @property (nonatomic) MPMediaPickerController *audioPickerController;
@property (nonatomic) NSString * fileType; @property (nonatomic) NSString * fileType;
@ -37,31 +37,34 @@
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if (_result) { if (_result) {
_result([FlutterError errorWithCode:@"multiple_request" result([FlutterError errorWithCode:@"multiple_request"
message:@"Cancelled by a second request" message:@"Cancelled by a second request"
details:nil]); details:nil]);
_result = nil; _result = nil;
return;
} }
_result = result; _result = result;
BOOL isMultiplePick = [call.arguments boolValue];
if(isMultiplePick || [call.method isEqualToString:@"ANY"] || [call.method containsString:@"__CUSTOM"]) {
if([call.method isEqualToString:@"VIDEO"]) { self.fileType = [FileUtils resolveType:call.method];
if(self.fileType == nil) {
_result([FlutterError errorWithCode:@"Unsupported file extension"
message:@"Make sure that you are only using the extension without the dot, (ie., jpg instead of .jpg). This could also have happened because you are using an unsupported file extension. If the problem persists, you may want to consider using FileType.ALL instead."
details:nil]);
_result = nil;
} else if(self.fileType != nil) {
[self resolvePickDocumentWithMultipleSelection:isMultiplePick];
}
} else if([call.method isEqualToString:@"VIDEO"]) {
[self resolvePickVideo]; [self resolvePickVideo];
} } else if([call.method isEqualToString:@"AUDIO"]) {
else if([call.method isEqualToString:@"AUDIO"]) {
[self resolvePickAudio]; [self resolvePickAudio];
} } else if([call.method isEqualToString:@"IMAGE"]) {
else if([call.method isEqualToString:@"IMAGE"]) {
[self resolvePickImage]; [self resolvePickImage];
} else { } else {
self.fileType = [FileUtils resolveType:call.method]; result(FlutterMethodNotImplemented);
_result = nil;
if(self.fileType == nil){
result(FlutterMethodNotImplemented);
} else {
[self resolvePickDocumentWithMultipleSelection:call.arguments];
}
} }
} }
@ -70,20 +73,27 @@
- (void)resolvePickDocumentWithMultipleSelection:(BOOL)allowsMultipleSelection { - (void)resolvePickDocumentWithMultipleSelection:(BOOL)allowsMultipleSelection {
self.pickerController = [[UIDocumentPickerViewController alloc] @try{
self.documentPickerController = [[UIDocumentPickerViewController alloc]
initWithDocumentTypes:@[self.fileType] initWithDocumentTypes:@[self.fileType]
inMode:UIDocumentPickerModeImport]; inMode:UIDocumentPickerModeImport];
} @catch (NSException * e) {
Log(@"Couldn't launch documents file picker. Probably due to iOS version being below 11.0 and not having the iCloud entitlement. If so, just make sure to enable it for your app in Xcode. Exception was: %@", e);
_result = nil;
return;
}
if (@available(iOS 11.0, *)) { if (@available(iOS 11.0, *)) {
self.pickerController.allowsMultipleSelection = allowsMultipleSelection; self.documentPickerController.allowsMultipleSelection = allowsMultipleSelection;
} else if(allowsMultipleSelection) { } else if(allowsMultipleSelection) {
Log(@"Multiple file selection is only supported on iOS 11 and above. Single selection will be used."); Log(@"Multiple file selection is only supported on iOS 11 and above. Single selection will be used.");
} }
self.pickerController.delegate = self; self.documentPickerController.delegate = self;
self.pickerController.modalPresentationStyle = UIModalPresentationCurrentContext; self.documentPickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
self.galleryPickerController.allowsEditing = NO; self.galleryPickerController.allowsEditing = NO;
[_viewController presentViewController:self.pickerController animated:YES completion:nil];
[_viewController presentViewController:self.documentPickerController animated:YES completion:nil];
} }
- (void) resolvePickImage { - (void) resolvePickImage {
@ -104,6 +114,7 @@
self.audioPickerController.showsCloudItems = NO; self.audioPickerController.showsCloudItems = NO;
self.audioPickerController.allowsPickingMultipleItems = NO; self.audioPickerController.allowsPickingMultipleItems = NO;
self.audioPickerController.modalPresentationStyle = UIModalPresentationCurrentContext; self.audioPickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.viewController presentViewController:self.audioPickerController animated:YES completion:nil]; [self.viewController presentViewController:self.audioPickerController animated:YES completion:nil];
} }
@ -124,7 +135,7 @@
- (void)documentPicker:(UIDocumentPickerViewController *)controller - (void)documentPicker:(UIDocumentPickerViewController *)controller
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
[self.pickerController dismissViewControllerAnimated:YES completion:nil]; [self.documentPickerController dismissViewControllerAnimated:YES completion:nil];
NSArray * result = [FileUtils resolvePath:urls]; NSArray * result = [FileUtils resolvePath:urls];
if([result count] > 1) { if([result count] > 1) {
@ -132,6 +143,7 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
} else { } else {
_result([result objectAtIndex:0]); _result([result objectAtIndex:0]);
} }
_result = nil;
} }
@ -159,9 +171,12 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
_result([FlutterError errorWithCode:@"file_picker_error" _result([FlutterError errorWithCode:@"file_picker_error"
message:@"Temporary file could not be created" message:@"Temporary file could not be created"
details:nil]); details:nil]);
_result = nil;
return;
} }
_result([pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]); _result([pickedVideoUrl != nil ? pickedVideoUrl : pickedImageUrl path]);
_result = nil;
} }
@ -171,9 +186,10 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
[mediaPicker dismissViewControllerAnimated:YES completion:NULL]; [mediaPicker dismissViewControllerAnimated:YES completion:NULL];
NSURL *url = [[[mediaItemCollection items] objectAtIndex:0] valueForKey:MPMediaItemPropertyAssetURL]; NSURL *url = [[[mediaItemCollection items] objectAtIndex:0] valueForKey:MPMediaItemPropertyAssetURL];
if(url == nil) { if(url == nil) {
Log(@"Couldn't retrieve the audio file path, either is not locally downloaded or the file DRM protected."); Log(@"Couldn't retrieve the audio file path, either is not locally downloaded or the file is DRM protected.");
} }
_result([url absoluteString]); _result([url absoluteString]);
_result = nil;
} }
#pragma mark - Actions canceled #pragma mark - Actions canceled

View File

@ -25,6 +25,12 @@
if ([type isEqualToString:@"ANY"]) { if ([type isEqualToString:@"ANY"]) {
return @"public.item"; return @"public.item";
} else if ([type isEqualToString:@"IMAGE"]) {
return @"public.image";
} else if ([type isEqualToString:@"VIDEO"]) {
return @"public.movie";
} else if ([type isEqualToString:@"AUDIO"]) {
return @"public.audio";
} else { } else {
return nil; return nil;
} }

View File

@ -1,9 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
String _kCustomType = '__CUSTOM_';
enum FileType { enum FileType {
ANY, ANY,
IMAGE, IMAGE,
@ -18,7 +17,33 @@ class FilePicker {
FilePicker._(); FilePicker._();
static Future<dynamic> _getPath(String type, [bool multipleSelection = false]) async { /// Returns an iterable `Map<String,String>` where the `key` is the name of the file
/// and the `value` the path.
///
/// A [fileExtension] can be provided to filter the picking results.
/// If provided, it will be use the `FileType.CUSTOM` for that [fileExtension].
/// If not, `FileType.ANY` will be used and any combination of files can be multi picked at once.
static Future<Map<String, String>> getMultiFilePath({FileType type = FileType.ANY, String fileExtension}) async =>
await _getPath(_handleType(type, fileExtension), true);
/// 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<String> getFilePath({FileType type = FileType.ANY, String fileExtension}) async =>
await _getPath(_handleType(type, fileExtension), false);
/// Returns a `File` object from the selected file path.
///
/// This is an utility method that does the same of `getFilePath()` but saving some boilerplate if
/// you are planing to create a `File` for the returned path.
static Future<File> getFile({FileType type = FileType.ANY, String fileExtension}) async {
final String filePath = await _getPath(_handleType(type, fileExtension), false);
return File(filePath);
}
static Future<dynamic> _getPath(String type, bool multipleSelection) async {
try { try {
dynamic result = await _channel.invokeMethod(type, multipleSelection); dynamic result = await _channel.invokeMethod(type, multipleSelection);
if (result != null && multipleSelection) { if (result != null && multipleSelection) {
@ -29,50 +54,27 @@ class FilePicker {
} }
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
print("[$_tag] Platform exception: " + e.toString()); print('[$_tag] Platform exception: ' + e.toString());
} catch (e) { } catch (e) {
print(e.toString()); print('[$_tag] Unsupported operation. Method not found. The exception thrown was: ' + e.toString());
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; return null;
} }
/// Returns an iterable `Map<String,String>` where the `key` is the name of the file static String _handleType(FileType type, String fileExtension) {
/// and the `value` the path.
///
/// A [fileExtension] can be provided to filter the picking results.
/// If provided, it will be use the `FileType.CUSTOM` for that [fileExtension].
/// If not, `FileType.ANY` will be used and any combination of files can be multi picked at once.
static Future<Map<String, String>> getMultiFilePath({String fileExtension}) async =>
await _getPath(fileExtension != null && fileExtension != '' ? (_kCustomType + fileExtension) : 'ANY', true);
/// 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<String> getFilePath({FileType type = FileType.ANY, String fileExtension}) async {
var path;
switch (type) { switch (type) {
case FileType.IMAGE: case FileType.IMAGE:
path = _getPath('IMAGE'); return 'IMAGE';
break;
case FileType.AUDIO: case FileType.AUDIO:
path = _getPath('AUDIO'); return 'AUDIO';
break;
case FileType.VIDEO: case FileType.VIDEO:
path = _getPath('VIDEO'); return 'VIDEO';
break;
case FileType.ANY: case FileType.ANY:
path = _getPath('ANY'); return 'ANY';
break;
case FileType.CUSTOM: case FileType.CUSTOM:
path = _getPath(_kCustomType + (fileExtension ?? '')); return '__CUSTOM_' + (fileExtension ?? '');
break;
default: default:
break; return 'ANY';
} }
return await path;
} }
} }

View File

@ -1,8 +1,8 @@
name: file_picker name: file_picker
description: A plugin that allows you to filter and pick absolute paths for diferent file extensions. description: A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support.
version: 1.3.0
author: Miguel Ruivo <miguelpruivo@outlook.com> author: Miguel Ruivo <miguelpruivo@outlook.com>
homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker
version: 1.3.0
dependencies: dependencies: