diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1adefc7..e17a5fc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+## 1.4.0
+
+**New features**
+ * Adds Desktop support throught **[go-flutter](https://github.com/go-flutter-desktop/go-flutter)**, you can see detailed instructions on how to get in runing [here](https://github.com/go-flutter-desktop/hover).
+ * Adds Desktop example, to run it just do `hover init` and then `hover run` within the plugin's example folder (you must have go and hover installed, check the previous point).
+ * Similar to `getFile`, now there is also a `getMultiFile` which behaves the same way, but returning a list of files instead.
+
+**Improvements:**
+ * Updates Android SDK deprecated code.
+ * Sometimes when a big file was being picked from a remote directory (GDrive for example), the UI could be blocked. Now this shouldn't happen anymore.
+
## 1.3.8
**Bug fix:** Fixes an issue that could cause a crash when picking files with very long names.
diff --git a/README.md b/README.md
index c902d08..a8e3709 100644
--- a/README.md
+++ b/README.md
@@ -2,103 +2,10 @@
[![Awesome Flutter](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter)
[![Codemagic build status](https://api.codemagic.io/apps/5ce89f4a9b46f5000ca89638/5ce89f4a9b46f5000ca89637/status_badge.svg)](https://codemagic.io/apps/5ce89f4a9b46f5000ca89638/5ce89f4a9b46f5000ca89637/latest_build)
-# file_picker
-
+![fluter_file_picker](https://user-images.githubusercontent.com/27860743/64064695-b88dab00-cbfc-11e9-814f-30921b66035f.png)
+# File Picker
A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support.
-## Installation
-
-First, add *file_picker* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/).
-
-```
-file_picker: ^1.3.8
-```
-### Android
-
-Add
-```
-
-```
-before `` to your app's `AndroidManifest.xml` file. This is required to access files from external storage.
-
-
-### 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 `/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).
-
- ```
- UIBackgroundModes
-
- fetch
- remote-notification
-
- ```
-
-* **_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
- Explain why your app uses music
- ```
-
-
-* **_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
- Explain why your app uses photo library
- ```
-
-**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
-There are only two methods that should be used with this package:
-
-#### `FilePicker.getFilePath()`
-
-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` 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:
-```
-// Single file path
-String filePath;
-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
-
-// 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
-Map 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
-filePaths = await FilePicker.getMultiFilePath(type: FileType.IMAGE); // will let you pick multiple image files at once
-
-List allNames = filePaths.keys; // List of all file names
-List allPaths = filePaths.values; // List of all paths
-String someFilePath = filePaths['fileName']; // Access a file path directly by its name (matching a key)
-```
-
-##### 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.
-* 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
* [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.)
@@ -107,17 +14,27 @@ String someFilePath = filePaths['fileName']; // Access a file path directly by i
* [X] Load path from **audio**
* [X] Load path from **video**
* [X] Load path from **any**
-* [X] Create a `File` object from **any** selected file
+* [X] Create a `File` or `List` objects from **any** selected file(s)
+* [X] Supports desktop through **go-flutter** (MacOS, Windows, Linux)
If you have any feature that you want to see in this package, please add it [here](https://github.com/miguelpruivo/plugins_flutter_file_picker/issues/99). 🎉
-## Demo App
+## Documentation
+See the **[File Picker Wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki)** for every detail on about how to install, setup and use it.
+1. [Installation](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Installation)
+2. [Setup](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup)
+ * [Android](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup#android)
+ * [iOS](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup#ios)
+ * [Desktop (go-flutter)](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup/_edit#desktop-go-flutter)
+3. [API](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/api)
+ * [Filters](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/API#filters)
+ * [Methods](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/API#methods)
+4. [Example App](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/lib/main.dart)
+
+## Example App
![Demo](https://github.com/miguelpruivo/plugins_flutter_file_picker/blob/master/example/example.gif)
-## Example
-See example app.
-
## Getting Started
For help getting started with Flutter, view our online
diff --git a/android/.idea/codeStyles/Project.xml b/android/.idea/codeStyles/Project.xml
deleted file mode 100644
index 30aa626..0000000
--- a/android/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 79f7c64..51cc429 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,3 +1,4 @@
+
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 ced49cd..4664a65 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
@@ -52,46 +52,58 @@ public class FilePickerPlugin implements MethodCallHandler {
instance = registrar;
instance.addActivityResultListener(new PluginRegistry.ActivityResultListener() {
@Override
- public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+ public boolean onActivityResult(int requestCode, int resultCode, final Intent data) {
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
- if(data.getClipData() != null) {
- int count = data.getClipData().getItemCount();
- int currentItem = 0;
- ArrayList paths = new ArrayList<>();
- while(currentItem < count) {
- final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
- String path = FileUtils.getPath(currentUri, instance.context());
- if(path == null) {
- path = FileUtils.getUriFromRemote(instance.activeContext(), currentUri, result);
- }
- paths.add(path);
- Log.i(TAG, "[MultiFilePick] File #" + currentItem + " - URI: " +currentUri.getPath());
- currentItem++;
- }
- if(paths.size() > 1){
- result.success(paths);
- } else {
- result.success(paths.get(0));
- }
- } else if (data != null) {
- Uri uri = data.getData();
- Log.i(TAG, "[SingleFilePick] File URI:" +data.getData().toString());
- String fullPath = FileUtils.getPath(uri, instance.context());
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (data != null) {
+ if(data.getClipData() != null) {
+ int count = data.getClipData().getItemCount();
+ int currentItem = 0;
+ ArrayList paths = new ArrayList<>();
+ while(currentItem < count) {
+ final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
+ String path = FileUtils.getPath(currentUri, instance.context());
+ if(path == null) {
+ path = FileUtils.getUriFromRemote(instance.activeContext(), currentUri, result);
+ }
+ paths.add(path);
+ Log.i(TAG, "[MultiFilePick] File #" + currentItem + " - URI: " +currentUri.getPath());
+ currentItem++;
+ }
+ if(paths.size() > 1){
+ runOnUiThread(result, paths, true);
+ } else {
+ runOnUiThread(result, paths.get(0), true);
+ }
+ } else if (data.getData() != null) {
+ Uri uri = data.getData();
+ Log.i(TAG, "[SingleFilePick] File URI:" + uri.toString());
+ String fullPath = FileUtils.getPath(uri, instance.context());
- if(fullPath == null) {
- fullPath = FileUtils.getUriFromRemote(instance.activeContext(), uri, result);
- }
+ if(fullPath == null) {
+ fullPath = FileUtils.getUriFromRemote(instance.activeContext(), uri, result);
+ }
+
+ if(fullPath != null) {
+ Log.i(TAG, "Absolute file path:" + fullPath);
+ runOnUiThread(result, fullPath, true);
+ } else {
+ runOnUiThread(result, "Failed to retrieve path.", false);
+ }
+ } else {
+ runOnUiThread(result, "Unknown activity error, please fill an issue.", false);
+ }
+ } else {
+ runOnUiThread(result, "Unknown activity error, please fill an issue.", false);
+ }
+ }
+ }).start();
+ return true;
- if(fullPath != null) {
- 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;
@@ -115,9 +127,24 @@ public class FilePickerPlugin implements MethodCallHandler {
});
}
+ private static void runOnUiThread(final Result result, final Object o, final boolean success) {
+ instance.activity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if(success) {
+ result.success(o);
+ } else if(o != null) {
+ result.error(TAG,(String)o, null);
+ } else {
+ result.notImplemented();
+ }
+ }
+ });
+ }
+
@Override
public void onMethodCall(MethodCall call, Result result) {
- this.result = result;
+ FilePickerPlugin.result = result;
fileType = resolveType(call.method);
isMultipleSelection = (boolean)call.arguments;
@@ -177,13 +204,9 @@ public class FilePickerPlugin implements MethodCallHandler {
Intent intent;
if (checkPermission()) {
- 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 = new Intent(Intent.ACTION_GET_CONTENT);
+ Uri uri = Uri.parse(FileUtils.getExternalPath(instance.activeContext()) + File.separator);
intent.setDataAndType(uri, type);
intent.setType(type);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultipleSelection);
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 dc91416..bf66f09 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
@@ -38,6 +38,13 @@ public class FileUtils {
return null;
}
+ public static String getExternalPath(Context context) {
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
+ return context.getExternalFilesDir(null).getAbsolutePath();
+ }
+ return context.getFilesDir().getAbsolutePath();
+ }
+
@TargetApi(19)
private static String getForApi19(Context context, Uri uri) {
Log.e(TAG, "Getting for API 19 or above" + uri);
@@ -50,11 +57,11 @@ public class FileUtils {
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
Log.e(TAG, "Primary External Document URI");
- return Environment.getExternalStorageDirectory() + "/" + split[1];
+ return getExternalPath(context) + "/" + split[1];
}
} else if (isDownloadsDocument(uri)) {
Log.e(TAG, "Downloads External Document URI");
- final String id = DocumentsContract.getDocumentId(uri);
+ String id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
@@ -65,6 +72,9 @@ public class FileUtils {
"content://downloads/my_downloads",
"content://downloads/all_downloads"
};
+ if(id.contains(":")){
+ id = id.split(":")[1];
+ }
for (String contentUriPrefix : contentUriPrefixesToTry) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
try {
@@ -132,6 +142,7 @@ public class FileUtils {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
+ } catch(Exception ex){
} finally {
if (cursor != null)
cursor.close();
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 3f815b3..ae8e6ae 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -15,7 +15,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 28
+ compileSdkVersion 29
lintOptions {
disable 'InvalidPackage'
@@ -24,7 +24,7 @@ android {
defaultConfig {
applicationId "com.mr.flutter.plugin.filepickerexample"
minSdkVersion 16
- targetSdkVersion 28
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 94e4412..91966c1 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -7,8 +7,7 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
-
-
+