Merge pull request #44 from miguelpruivo/#Roadmap-1.3.0

see changelog
This commit is contained in:
Miguel Ruivo 2019-03-10 01:38:17 +00:00 committed by GitHub
commit 7b295b7d5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 485 additions and 279 deletions

View File

@ -1,7 +1,20 @@
## 1.3.0
**Breaking changes**
* `FileType.CAMERA` is no longer available, if you need it, you can use this package along with [image_picker](https://pub.dartlang.org/packages/image_picker).
**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 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).
**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.
* Fixes an issue where sometimes the _InputStream_ wasn't being properly closed. Also, its exception is now being forward to the plugin caller.
* Fixes an issue where the picker, when canceled, wasn't calling the result callback on the underlying platforms.
## 1.2.0 ## 1.2.0
**Breaking change** **Breaking change**: Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library.
Migrate from the deprecated original Android Support Library to AndroidX. This shouldn't result in any functional changes, but it requires any Android apps using this plugin to [also migrate](https://developer.android.com/jetpack/androidx/migrate) if they're using the original support library.
## 1.1.1 ## 1.1.1

114
README.md
View File

@ -10,89 +10,76 @@ File picker plugin alows you to use a native file explorer to load absolute file
First, add *file_picker* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/). First, add *file_picker* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/).
``` ```
file_picker: ^1.2.0 file_picker: ^1.3.0
``` ```
## Android ## Android
Add `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>` to your app `AndroidManifest.xml` file. 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
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 `<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`:
* `NSPhotoLibraryUsageDescription` - describe why your app needs permission for the photo library. This is called _Privacy - Photo 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.
* `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. * **_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 - 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).
* **_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).
## Usage ## Usage
There's only one method within this package There are only two methods that should be used with this package:
`FilePicker.getFilePath()`
this receives 2 optional parameters, the `fileType` and a `fileExtension` to be used along with `FileType.CUSTOM`. #### `FilePicker.getFilePath()`
So, 2 basically usages may be:
Will let you pick a **single** file. This receives two optional parameters: the `fileType` for specifying the type of the picker and a `fileExtension` parameter to filter selectable files. The available filters are:
* `FileType.ANY` - Will let you pick all available files.
* `FileType.CUSTOM` - Will let you pick a single path for the extension matching the `fileExtension` provided.
* `FileType.IMAGE` - Will let you pick a single image file. Opens gallery on iOS.
* `FileType.VIDEO` - WIll let you pick a single video file. Opens gallery on iOS.
* `FileType.AUDIO` - Will let you pick a single audio file. Opens music on iOS. Note that DRM protected files won't provide a path, `null` will be returned instead.
#### `FilePicker.getMultiFilePath()`
Will let you select **multiple** files and retrieve its path at once. Optionally you can provide a `fileExtension` parameter to filter the allowed selectable files.
Will return a `Map<String,String>` with the files name (`key`) and corresponding path (`value`) of all selected files.
Picking multiple paths from iOS gallery (image and video) aren't currently supported.
#### Usages
So, a few example usages can be as follow:
``` ```
await FilePicker.getFilePath(type: FileType.ANY); // will display all file types // Single file path
await FilePicker.getFilePath(type: FileType.CUSTOM, fileExtension: 'svg'); // will filter and display only files with SVG extension. String filePath;
filePath = await FilePicker.getFilePath(type: FileType.ANY); // will let you pick one file, from all extensions
filePath = await FilePicker.getFilePath(type: FileType.CUSTOM, fileExtension: 'svg'); // will filter and only let you pick files with svg extension.
// Multi file path
Map<String,String> filesPaths;
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
List<String> allNames = filePaths.keys; // List of all file names
List<String> allPaths = filePaths.values; // List of all paths
String someFilePath = filePaths['fileName']; // Access a file path directly by its name (matching a key)
``` ```
**Note:** When using `FileType.CUSTOM`, unsupported extensions will throw a `MissingPluginException` that is handled by the plugin. ##### A few notes
* 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.
## 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 **gallery**
* [X] Load path from **camera**
* [X] Load path from **video**
* [X] Load path from **any** type of file (without filtering)
* [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 **gallery**
* [X] Load path from **audio**
* [X] Load path from **video**
* [X] Load path from **any** file type (without filtering, just pick what you want)
## Demo App ## Demo App
![Demo](https://github.com/miguelpruivo/plugins_flutter_file_picker/blob/master/example/example.gif) ![Demo](https://github.com/miguelpruivo/plugins_flutter_file_picker/blob/master/example/example.gif)
## Example ## Example
``` See example app.
import 'package:file_picker/file_picker.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _filePath;
void getFilePath() async {
try {
String filePath = await FilePicker.getFilePath(type: FileType.ANY);
if (filePath == '') {
return;
}
print("File path: " + filePath);
setState((){this._filePath = filePath;});
} on PlatformException catch (e) {
print("Error while picking the file: " + e.toString());
}
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('File Picker Example'),
),
body: new Center(
child: _filePath == null
? new Text('No file selected.')
: new Text('Path' + _filePath),
),
floatingActionButton: new FloatingActionButton(
onPressed: getFilePath,
tooltip: 'Select file',
child: new Icon(Icons.sd_storage),
),
);
}
}
```
## Getting Started ## Getting Started
@ -100,3 +87,4 @@ For help getting started with Flutter, view our online
[documentation](https://flutter.io/). [documentation](https://flutter.io/).
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

@ -12,10 +12,9 @@ import androidx.core.content.ContextCompat;
import android.util.Log; import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.util.ArrayList;
import java.io.InputStream;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
@ -36,6 +35,7 @@ public class FilePickerPlugin implements MethodCallHandler {
private static Result result; private static Result result;
private static Registrar instance; private static Registrar instance;
private static String fileType; private static String fileType;
private static boolean isMultipleSelection = false;
/** Plugin registration. */ /** Plugin registration. */
public static void registerWith(Registrar registrar) { public static void registerWith(Registrar registrar) {
@ -49,48 +49,42 @@ public class FilePickerPlugin implements MethodCallHandler {
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (data != null) { if(data.getClipData() != null) {
int count = data.getClipData().getItemCount();
int currentItem = 0;
ArrayList<String> paths = new ArrayList<>();
while(currentItem < count) {
final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
String path = FileUtils.getPath(currentUri, instance.context());
paths.add(path);
Log.i(TAG, "[MultiFilePick] File #" + currentItem + " - URI: " +currentUri.getPath());
currentItem++;
}
result.success(paths);
} else if (data != null) {
Uri uri = data.getData(); Uri uri = data.getData();
Log.i(TAG, "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; String cloudFile = null;
if(fullPath == null)
{
FileOutputStream fos = null;
cloudFile = instance.activeContext().getCacheDir().getAbsolutePath() + "/" + FileUtils.getFileName(uri, instance.activeContext());
try { if(fullPath == null) {
fos = new FileOutputStream(cloudFile); fullPath = FileUtils.getUriFromRemote(instance.activeContext(), uri, result);
try{
BufferedOutputStream out = new BufferedOutputStream(fos);
InputStream in = instance.activeContext().getContentResolver().openInputStream(uri);
byte[] buffer = new byte[8192];
int len = 0;
while ((len = in.read(buffer)) >= 0){
out.write(buffer, 0, len);
}
out.flush();
} finally {
fos.getFD().sync();
}
} catch (Exception e) {
e.printStackTrace();
}
Log.i(TAG, "Cloud file loaded and cached on:" + cloudFile);
fullPath = cloudFile;
} }
Log.i(TAG, "Absolute file path:" + fullPath); if(fullPath != null) {
result.success(fullPath); Log.i(TAG, "Absolute file path:" + fullPath);
result.success(fullPath);
} else {
result.error(TAG, "Failed to retrieve path." ,null);
}
} }
return true;
} else if(requestCode == REQUEST_CODE && resultCode == Activity.RESULT_CANCELED) {
result.success(null);
return true;
} }
result.error(TAG, "Unknown activity error, please report issue." ,null);
return false; return false;
} }
}); });
@ -112,8 +106,9 @@ public class FilePickerPlugin implements MethodCallHandler {
public void onMethodCall(MethodCall call, Result result) { public void onMethodCall(MethodCall call, Result result) {
this.result = result; this.result = result;
fileType = resolveType(call.method); fileType = resolveType(call.method);
isMultipleSelection = (boolean)call.arguments;
if(fileType == null){ if(fileType == null) {
result.notImplemented(); result.notImplemented();
} else { } else {
startFileExplorer(fileType); startFileExplorer(fileType);
@ -128,7 +123,6 @@ public class FilePickerPlugin implements MethodCallHandler {
} }
private static void requestPermission() { private static void requestPermission() {
Activity activity = instance.activity(); Activity activity = instance.activity();
Log.i(TAG, "Requesting permission: " + permission); Log.i(TAG, "Requesting permission: " + permission);
String[] perm = { permission }; String[] perm = { permission };
@ -147,8 +141,10 @@ public class FilePickerPlugin implements MethodCallHandler {
} }
switch (type) { switch (type) {
case "PDF": case "AUDIO":
return "application/pdf"; return "audio/*";
case "IMAGE":
return "image/*";
case "VIDEO": case "VIDEO":
return "video/*"; return "video/*";
case "ANY": case "ANY":
@ -174,10 +170,9 @@ public class FilePickerPlugin implements MethodCallHandler {
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator); Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
intent.setDataAndType(uri, type); intent.setDataAndType(uri, type);
intent.setType(type); intent.setType(type);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultipleSelection);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
Log.d(TAG, "Intent: " + intent.toString());
instance.activity().startActivityForResult(intent, REQUEST_CODE); instance.activity().startActivityForResult(intent, REQUEST_CODE);
} else { } else {
requestPermission(); requestPermission();

View File

@ -11,7 +11,13 @@ import android.provider.DocumentsContract;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.webkit.MimeTypeMap;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import io.flutter.plugin.common.MethodChannel;
/** /**
* Credits to NiRRaNjAN from utils extracted of in.gauriinfotech.commons;. * Credits to NiRRaNjAN from utils extracted of in.gauriinfotech.commons;.
@ -19,7 +25,7 @@ import android.webkit.MimeTypeMap;
public class FileUtils { public class FileUtils {
private static final String tag = "FilePathPicker"; private static final String TAG = "FilePickerUtils";
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; final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
@ -38,27 +44,26 @@ public class FileUtils {
@TargetApi(19) @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); Log.e(TAG, "Getting for API 19 or above" + uri);
if (DocumentsContract.isDocumentUri(context, uri)) { if (DocumentsContract.isDocumentUri(context, uri)) {
Log.e(tag, "+++ Document URI"); Log.e(TAG, "Document URI");
if (isExternalStorageDocument(uri)) { if (isExternalStorageDocument(uri)) {
Log.e(tag, "+++ External Document URI"); Log.e(TAG, "External Document URI");
final String docId = DocumentsContract.getDocumentId(uri); final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":"); final String[] split = docId.split(":");
final String type = split[0]; final String type = split[0];
if ("primary".equalsIgnoreCase(type)) { if ("primary".equalsIgnoreCase(type)) {
Log.e(tag, "+++ Primary External Document URI"); Log.e(TAG, "Primary External Document URI");
return Environment.getExternalStorageDirectory() + "/" + split[1]; return Environment.getExternalStorageDirectory() + "/" + split[1];
} }
} else if (isDownloadsDocument(uri)) { } else if (isDownloadsDocument(uri)) {
Log.e(tag, "+++ Downloads External Document URI"); Log.e(TAG, "Downloads External Document URI");
final String id = DocumentsContract.getDocumentId(uri); final String id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) { if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) { if (id.startsWith("raw:")) {
return id.replaceFirst("raw:", ""); return id.replaceFirst("raw:", "");
} }
String[] contentUriPrefixesToTry = new String[]{ String[] contentUriPrefixesToTry = new String[]{
"content://downloads/public_downloads", "content://downloads/public_downloads",
"content://downloads/my_downloads", "content://downloads/my_downloads",
@ -72,26 +77,26 @@ public class FileUtils {
return path; return path;
} }
} catch (Exception e) { } 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)) { } else if (isMediaDocument(uri)) {
Log.e(tag, "+++ Media Document URI"); Log.e(TAG, "Media Document URI");
final String docId = DocumentsContract.getDocumentId(uri); final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":"); final String[] split = docId.split(":");
final String type = split[0]; final String type = split[0];
Uri contentUri = null; Uri contentUri = null;
if ("image".equals(type)) { if ("image".equals(type)) {
Log.e(tag, "+++ Image Media Document URI"); Log.i(TAG, "Image Media Document URI");
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_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"); Log.i(TAG, "Video Media Document URI");
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_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"); Log.i(TAG, "Audio Media Document URI");
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
} }
@ -103,13 +108,13 @@ public class FileUtils {
return getDataColumn(context, contentUri, selection, selectionArgs); 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 "); Log.e(TAG, "NO DOCUMENT URI - CONTENT");
if (isGooglePhotosUri(uri)) if (isGooglePhotosUri(uri))
return uri.getLastPathSegment(); return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null); 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 "); Log.e(TAG, "No DOCUMENT URI - FILE");
return uri.getPath(); return uri.getPath();
} }
return null; return null;
@ -170,6 +175,43 @@ public class FileUtils {
return result; return result;
} }
public static String getUriFromRemote(Context context, Uri uri, MethodChannel.Result result) {
FileOutputStream fos = null;
String cloudFile = context.getCacheDir().getAbsolutePath() + "/" + FileUtils.getFileName(uri, context);
try {
fos = new FileOutputStream(cloudFile);
try {
BufferedOutputStream out = new BufferedOutputStream(fos);
InputStream in = context.getContentResolver().openInputStream(uri);
byte[] buffer = new byte[8192];
int len = 0;
while ((len = in.read(buffer)) >= 0) {
out.write(buffer, 0, len);
}
out.flush();
} finally {
fos.getFD().sync();
}
} catch (Exception e) {
try {
fos.close();
} catch(IOException ex) {
Log.e(TAG, "Failed to close file streams: " + e.getMessage(),null);
return null;
}
Log.e(TAG, "Failed to retrieve path: " + e.getMessage(),null);
return null;
}
Log.i(TAG, "Remote file loaded and cached at:" + cloudFile);
return cloudFile;
}
private static boolean isExternalStorageDocument(Uri uri) { private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority()); return "com.android.externalstorage.documents".equals(uri.getAuthority());

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -2,26 +2,20 @@ PODS:
- file_picker (0.0.1): - file_picker (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- image_picker (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `.symlinks/flutter/ios`) - Flutter (from `.symlinks/flutter/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
EXTERNAL SOURCES: EXTERNAL SOURCES:
file_picker: file_picker:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
:path: ".symlinks/flutter/ios" :path: ".symlinks/flutter/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
file_picker: 78c3344d9b2c343bb3090c2f032b796242ebaea7 file_picker: 78c3344d9b2c343bb3090c2f032b796242ebaea7
Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296
image_picker: ee00aab0487cedc80a304085219503cc6d0f2e22
PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2 PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2

View File

@ -22,10 +22,6 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSCameraUsageDescription</key>
<string>Used to demonstrate image picker plugin</string>
<key>NSMicrophoneUsageDescription</key>
<string>Used to capture audio for image picker plugin</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>Used to demonstrate image picker plugin</string> <string>Used to demonstrate image picker plugin</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
@ -38,11 +34,11 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>fetch</string> <string>fetch</string>
<string>remote-notification</string> <string>remote-notification</string>
</array> </array>
<key>UISupportedInterfaceOrientations~ipad</key> <key>UISupportedInterfaceOrientations~ipad</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>

View File

@ -3,17 +3,19 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:file_picker/file_picker.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 @override
_MyAppState createState() => new _MyAppState(); _FilePickerDemoState createState() => new _FilePickerDemoState();
} }
class _MyAppState extends State<MyApp> { class _FilePickerDemoState extends State<FilePickerDemo> {
String _fileName = '...'; String _fileName;
String _path = '...'; String _path;
Map<String, String> _paths;
String _extension; String _extension;
bool _multiPick = false;
bool _hasValidMime = false; bool _hasValidMime = false;
FileType _pickingType; FileType _pickingType;
TextEditingController _controller = new TextEditingController(); TextEditingController _controller = new TextEditingController();
@ -27,15 +29,20 @@ class _MyAppState extends State<MyApp> {
void _openFileExplorer() async { void _openFileExplorer() async {
if (_pickingType != FileType.CUSTOM || _hasValidMime) { if (_pickingType != FileType.CUSTOM || _hasValidMime) {
try { try {
_path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension); if (_multiPick) {
_path = null;
_paths = await FilePicker.getMultiFilePath(fileExtension: _extension);
} else {
_paths = null;
_path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension);
}
} on PlatformException catch (e) { } on PlatformException catch (e) {
print("Unsupported operation" + e.toString()); print("Unsupported operation" + e.toString());
} }
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_fileName = _path != null ? _path.split('/').last : '...'; _fileName = _path != null ? _path.split('/').last : _paths != null ? _paths.keys.toString() : '...';
}); });
} }
} }
@ -45,47 +52,55 @@ class _MyAppState extends State<MyApp> {
return new MaterialApp( return new MaterialApp(
home: new Scaffold( home: new Scaffold(
appBar: new AppBar( appBar: new AppBar(
title: const Text('Plugin example app'), title: const Text('File Picker example app'),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(
child: 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 Column( child: new ConstrainedBox(
mainAxisAlignment: MainAxisAlignment.center, constraints: new BoxConstraints(maxWidth: 200.0),
children: <Widget>[ child: new Column(
new Padding( mainAxisAlignment: MainAxisAlignment.center,
padding: const EdgeInsets.only(top: 20.0), children: <Widget>[
child: new DropdownButton( new Padding(
hint: new Text('LOAD PATH FROM'), padding: const EdgeInsets.only(top: 20.0),
value: _pickingType, child: new DropdownButton(
items: <DropdownMenuItem>[ hint: new Text('LOAD PATH FROM'),
new DropdownMenuItem( value: _pickingType,
child: new Text('FROM CAMERA'), items: <DropdownMenuItem>[
value: FileType.CAMERA, new DropdownMenuItem(
), child: new Text('FROM AUDIO'),
new DropdownMenuItem( value: FileType.AUDIO,
child: new Text('FROM GALLERY'), ),
value: FileType.IMAGE, new DropdownMenuItem(
), child: new Text('FROM GALLERY'),
new DropdownMenuItem( value: FileType.IMAGE,
child: new Text('FROM VIDEO'), ),
value: FileType.VIDEO, new DropdownMenuItem(
), child: new Text('FROM VIDEO'),
new DropdownMenuItem( value: FileType.VIDEO,
child: new Text('FROM ANY'), ),
value: FileType.ANY, new DropdownMenuItem(
), child: new Text('FROM ANY'),
new DropdownMenuItem( value: FileType.ANY,
child: new Text('CUSTOM FORMAT'), ),
value: FileType.CUSTOM, new DropdownMenuItem(
), child: new Text('CUSTOM FORMAT'),
], value: FileType.CUSTOM,
onChanged: (value) => setState(() => _pickingType = value)), ),
), ],
new ConstrainedBox( onChanged: (value) => setState(() {
constraints: new BoxConstraints(maxWidth: 150.0), _pickingType = value;
child: _pickingType == FileType.CUSTOM if (_pickingType != FileType.CUSTOM && _pickingType != FileType.ANY) {
_multiPick = false;
}
if (_pickingType != FileType.CUSTOM) {
_controller.text = _extension = '';
}
})),
),
_pickingType == FileType.CUSTOM
? new TextFormField( ? new TextFormField(
maxLength: 20, maxLength: 20,
autovalidate: true, autovalidate: true,
@ -103,38 +118,46 @@ class _MyAppState extends State<MyApp> {
}, },
) )
: new Container(), : new Container(),
), new Visibility(
new Padding( visible: _pickingType == FileType.ANY || _pickingType == FileType.CUSTOM,
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0), child: new SwitchListTile.adaptive(
child: new RaisedButton( title: new Text('Pick multiple files', textAlign: TextAlign.right),
onPressed: () => _openFileExplorer(), onChanged: (bool value) => setState(() => _multiPick = value),
child: new Text("Open file picker"), value: _multiPick,
),
), ),
), new Padding(
new Text( padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
'URI PATH ', child: new RaisedButton(
textAlign: TextAlign.center, onPressed: () => _openFileExplorer(),
style: new TextStyle(fontWeight: FontWeight.bold), child: new Text("Open file picker"),
), ),
new Text( ),
_path ?? '...', new Text(
textAlign: TextAlign.center, 'URI PATH ',
softWrap: true,
textScaleFactor: 0.85,
),
new Padding(
padding: const EdgeInsets.only(top: 10.0),
child: new Text(
'FILE NAME ',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: new TextStyle(fontWeight: FontWeight.bold), style: new TextStyle(fontWeight: FontWeight.bold),
), ),
), new Text(
new Text( _path ?? ((_paths != null && _paths.isNotEmpty) ? _paths.values.map((path) => path + '\n\n').toString() : '...'),
_fileName, textAlign: TextAlign.center,
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,
),
],
),
), ),
)), )),
), ),

View File

@ -1,5 +1,7 @@
#import <Flutter/Flutter.h> #import <Flutter/Flutter.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <Photos/Photos.h>
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
@interface FilePickerPlugin : NSObject<FlutterPlugin, UIDocumentPickerDelegate, UITabBarDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate> @interface FilePickerPlugin : NSObject<FlutterPlugin, UIDocumentPickerDelegate, UITabBarDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>

View File

@ -1,11 +1,14 @@
#import "FilePickerPlugin.h" #import "FilePickerPlugin.h"
#import "FileUtils.h" #import "FileUtils.h"
#import "ImageUtils.h"
@interface FilePickerPlugin() @interface FilePickerPlugin() <UIImagePickerControllerDelegate, MPMediaPickerControllerDelegate>
@property (nonatomic) FlutterResult result; @property (nonatomic) FlutterResult result;
@property (nonatomic) UIViewController *viewController; @property (nonatomic) UIViewController *viewController;
@property (nonatomic) UIImagePickerController *galleryPickerController;
@property (nonatomic) UIDocumentPickerViewController *pickerController; @property (nonatomic) UIDocumentPickerViewController *pickerController;
@property (nonatomic) UIDocumentInteractionController *interactionController; @property (nonatomic) UIDocumentInteractionController *interactionController;
@property (nonatomic) MPMediaPickerController *audioPickerController;
@property (nonatomic) NSString * fileType; @property (nonatomic) NSString * fileType;
@end @end
@ -32,16 +35,6 @@
return self; 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 { - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if (_result) { if (_result) {
_result([FlutterError errorWithCode:@"multiple_request" _result([FlutterError errorWithCode:@"multiple_request"
@ -56,57 +49,152 @@
if([call.method isEqualToString:@"VIDEO"]) { if([call.method isEqualToString:@"VIDEO"]) {
[self resolvePickVideo]; [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]; self.fileType = [FileUtils resolveType:call.method];
if(self.fileType == nil){ if(self.fileType == nil){
result(FlutterMethodNotImplemented); result(FlutterMethodNotImplemented);
} else { } else {
[self initPicker]; [self resolvePickDocumentWithMultipleSelection:call.arguments];
[_viewController presentViewController:self.pickerController animated:YES completion:^{
if (@available(iOS 11.0, *)) {
self.pickerController.allowsMultipleSelection = NO;
}
}];
} }
} }
} }
#pragma mark - Resolvers
- (void)resolvePickDocumentWithMultipleSelection:(BOOL)allowsMultipleSelection {
self.pickerController = [[UIDocumentPickerViewController alloc]
initWithDocumentTypes:@[self.fileType]
inMode:UIDocumentPickerModeImport];
if (@available(iOS 11.0, *)) {
self.pickerController.allowsMultipleSelection = allowsMultipleSelection;
} else if(allowsMultipleSelection) {
Log(@"Multiple file selection is only supported on iOS 11 and above. Single selection will be used.");
}
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 - (void)documentPicker:(UIDocumentPickerViewController *)controller
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
[self.pickerController dismissViewControllerAnimated:YES completion:nil]; [self.pickerController dismissViewControllerAnimated:YES completion:nil];
_result([FileUtils resolvePath:urls]); NSArray * result = [FileUtils resolvePath:urls];
if([result count] > 1) {
_result(result);
} else {
_result([result objectAtIndex:0]);
}
} }
// VideoPicker delegate // ImagePicker 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];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - (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]; [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) {
Log(@"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 {
Log(@"FilePicker canceled");
_result(nil);
_result = nil;
[controller dismissViewControllerAnimated:YES completion:NULL];
} }
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
Log(@"FilePicker canceled");
_result(nil);
_result = nil; _result = nil;
[controller dismissViewControllerAnimated:YES completion:NULL]; [controller dismissViewControllerAnimated:YES completion:NULL];
} }
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
Log(@"FilePicker canceled");
_result(nil);
_result = nil; _result = nil;
[picker dismissViewControllerAnimated:YES completion:NULL]; [picker dismissViewControllerAnimated:YES completion:NULL];
} }

View File

@ -5,9 +5,16 @@
// Created by Miguel Ruivo on 05/12/2018. // Created by Miguel Ruivo on 05/12/2018.
// //
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
#ifdef DEBUG
#define Log(fmt, ...) NSLog((@"\n\n***** " fmt @"\n* %s [Line %d]\n\n\n"), ##__VA_ARGS__, __PRETTY_FUNCTION__, __LINE__)
#else
#define Log(fmt, ...)
#endif
@interface FileUtils : NSObject @interface FileUtils : NSObject
+ (NSString*) resolveType:(NSString*)type; + (NSString*) resolveType:(NSString*)type;
+ (NSString*) resolvePath:(NSArray<NSURL *> *)urls; + (NSArray*) resolvePath:(NSArray<NSURL *> *)urls;
@end @end

View File

@ -19,29 +19,27 @@
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[format pathExtension], NULL); CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[format pathExtension], NULL);
NSString * UTIString = (__bridge NSString *)(UTI); NSString * UTIString = (__bridge NSString *)(UTI);
CFRelease(UTI); CFRelease(UTI);
NSLog(@"Custom file type: %@", UTIString); Log(@"Custom file type: %@", UTIString);
return [UTIString containsString:@"dyn."] ? nil : UTIString; return [UTIString containsString:@"dyn."] ? nil : UTIString;
} }
if ([type isEqualToString:@"PDF"]) { if ([type isEqualToString:@"ANY"]) {
return @"com.adobe.pdf";
}
else if ([type isEqualToString:@"ANY"]) {
return @"public.item"; return @"public.item";
} else { } else {
return nil; return nil;
} }
} }
+ (NSMutableArray*) resolvePath:(NSArray<NSURL *> *)urls{
+ (NSString*) resolvePath:(NSArray<NSURL *> *)urls{
NSString * uri; NSString * uri;
NSMutableArray * paths = [[NSMutableArray alloc] init];
for (NSURL *url in urls) { for (NSURL *url in urls) {
uri = (NSString *)[url path]; uri = (NSString *)[url path];
[paths addObject:uri];
} }
return uri; return paths;
} }
@end @end

11
ios/Classes/ImageUtils.h Normal file
View File

@ -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

35
ios/Classes/ImageUtils.m Normal file
View File

@ -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

View File

@ -1,14 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
/// Supported file types, [ANY] should be used if the file you need isn't listed String _kCustomType = '__CUSTOM_';
enum FileType { enum FileType {
ANY, ANY,
IMAGE, IMAGE,
VIDEO, VIDEO,
CAMERA, AUDIO,
CUSTOM, CUSTOM,
} }
@ -16,27 +16,36 @@ class FilePicker {
static const MethodChannel _channel = const MethodChannel('file_picker'); static const MethodChannel _channel = const MethodChannel('file_picker');
static const String _tag = 'FilePicker'; static const String _tag = 'FilePicker';
static Future<String> _getPath(String type) async { FilePicker._();
static Future<dynamic> _getPath(String type, [bool multipleSelection = false]) async {
try { try {
return await _channel.invokeMethod(type); dynamic result = await _channel.invokeMethod(type, multipleSelection);
if (result != null && multipleSelection) {
if (result is String) {
result = [result];
}
return Map<String, String>.fromIterable(result, key: (path) => path.split('/').last, value: (path) => path);
}
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( 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."); "[$_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;
} }
static Future<String> _getImage(ImageSource type) async { /// Returns an iterable `Map<String,String>` where the `key` is the name of the file
try { /// and the `value` the path.
var image = await ImagePicker.pickImage(source: type); ///
return image?.path; /// A [fileExtension] can be provided to filter the picking results.
} on PlatformException catch (e) { /// If provided, it will be use the `FileType.CUSTOM` for that [fileExtension].
print("[$_tag] Platform exception: " + e.toString()); /// 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 =>
return null; await _getPath(fileExtension != null && fileExtension != '' ? (_kCustomType + fileExtension) : 'ANY', true);
}
/// Returns an absolute file path from the calling platform /// Returns an absolute file path from the calling platform
/// ///
@ -44,19 +53,26 @@ class FilePicker {
/// Can be used a custom file type with `FileType.CUSTOM`. A [fileExtension] must be provided (e.g. PDF, SVG, etc.) /// 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. /// Defaults to `FileType.ANY` which will display all file types.
static Future<String> getFilePath({FileType type = FileType.ANY, String fileExtension}) async { static Future<String> getFilePath({FileType type = FileType.ANY, String fileExtension}) async {
var path;
switch (type) { switch (type) {
case FileType.IMAGE: case FileType.IMAGE:
return _getImage(ImageSource.gallery); path = _getPath('IMAGE');
case FileType.CAMERA: break;
return _getImage(ImageSource.camera); case FileType.AUDIO:
path = _getPath('AUDIO');
break;
case FileType.VIDEO: case FileType.VIDEO:
return _getPath('VIDEO'); path = _getPath('VIDEO');
break;
case FileType.ANY: case FileType.ANY:
return _getPath('ANY'); path = _getPath('ANY');
break;
case FileType.CUSTOM: case FileType.CUSTOM:
return _getPath('__CUSTOM_' + (fileExtension ?? '')); path = _getPath(_kCustomType + (fileExtension ?? ''));
break;
default: default:
return _getPath('ANY'); break;
} }
return await path;
} }
} }

View File

@ -1,6 +1,6 @@
name: file_picker name: file_picker
description: A plugin that allows you to pick absolute paths from diferent file types. description: A plugin that allows you to filter and pick absolute paths for diferent file extensions.
version: 1.2.0 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
@ -8,9 +8,7 @@ homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
image_picker: ^0.5.0
meta: ^1.1.5
environment: environment:
sdk: ">=2.0.0 <3.0.0" sdk: ">=2.0.0 <3.0.0"