see changelog (v1.4.0)
* Better handling on Android of buggy file managers that return no intent when canceling the file selection even though it returns Activity.RESULT_OK (#111) * Add file_picker Go support. (#132) * Add file_picker Go support. Originally written by @chunhunghan, cleaned up and fixed for MacOS by Geert-Johan Riemer. Co-authored-by: chunhunghan <chunhunghan@gmail.com> * Add improved instructions to go/README.md * removes deprecated Android SDK code and fixes an issue that could prevent some downloaded files from being picked * adds getMultiFile and prevents UI blocking when picking large remote files * updates readme file
This commit is contained in:
parent
3f67653fed
commit
f502423ab2
11
CHANGELOG.md
11
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.
|
||||
|
|
117
README.md
117
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
|
||||
```
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
```
|
||||
before `<application>` 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 `<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.
|
||||
|
||||
```
|
||||
<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.
|
||||
|
||||
```
|
||||
<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
|
||||
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<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:
|
||||
```
|
||||
// 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<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
|
||||
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> 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<File>` 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
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
</code_scheme>
|
||||
</component>
|
|
@ -1,3 +1,4 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mr.flutter.plugin.filepicker">
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
</manifest>
|
||||
|
|
|
@ -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<String> 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<String> 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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
In most cases you can leave this as-is, but you if you want to provide
|
||||
|
@ -18,7 +17,8 @@
|
|||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="file_picker_example"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
tools:replace="android:label">
|
||||
tools:replace="android:label"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
|
|
|
@ -5,7 +5,7 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
classpath 'com.android.tools.build:gradle:3.2.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,12 @@ allprojects {
|
|||
google()
|
||||
jcenter()
|
||||
}
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
options.compilerArgs << "-Xlint:deprecation"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#Sat Jan 26 11:50:40 EET 2019
|
||||
#Thu Aug 29 13:24:58 WEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
|
|
|
@ -14,9 +14,9 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/flutter/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
file_picker: 78c3344d9b2c343bb3090c2f032b796242ebaea7
|
||||
Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296
|
||||
file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245
|
||||
Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
|
||||
|
||||
PODFILE CHECKSUM: 1e5af4103afd21ca5ead147d7b81d06f494f51a2
|
||||
|
||||
COCOAPODS: 1.5.3
|
||||
COCOAPODS: 1.7.5
|
||||
|
|
|
@ -54,6 +54,8 @@
|
|||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9E29C2B321AA1B6738D05DCC /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
AA58896224B359D6BCEB4ED4 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D616774FB981784E6589DAC9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -137,6 +139,8 @@
|
|||
EE3450EDCED914F636FA6BB9 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D616774FB981784E6589DAC9 /* Pods-Runner.debug.xcconfig */,
|
||||
AA58896224B359D6BCEB4ED4 /* Pods-Runner.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
|
@ -266,7 +270,7 @@
|
|||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
|
||||
"${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
|
@ -275,7 +279,7 @@
|
|||
);
|
||||
runOnlyForDeploymentPostprocessing = 1;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
|
|
@ -18,14 +18,19 @@
|
|||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>Used to demonstrate file picker plugin</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppleMusicUsageDescription</key>
|
||||
<string>Used to demonstrate file picker plugin</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>Used to demonstrate file picker plugin</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
@ -36,11 +41,6 @@
|
|||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
|
|
@ -1,185 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:file_picker_example/src/file_picker_demo.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
void main() => runApp(new FilePickerDemo());
|
||||
|
||||
class FilePickerDemo extends StatefulWidget {
|
||||
@override
|
||||
_FilePickerDemoState createState() => new _FilePickerDemoState();
|
||||
}
|
||||
|
||||
class _FilePickerDemoState extends State<FilePickerDemo> {
|
||||
String _fileName;
|
||||
String _path;
|
||||
Map<String, String> _paths;
|
||||
String _extension;
|
||||
bool _multiPick = false;
|
||||
bool _hasValidMime = false;
|
||||
FileType _pickingType;
|
||||
TextEditingController _controller = new TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.addListener(() => _extension = _controller.text);
|
||||
}
|
||||
|
||||
void _openFileExplorer() async {
|
||||
if (_pickingType != FileType.CUSTOM || _hasValidMime) {
|
||||
try {
|
||||
if (_multiPick) {
|
||||
_path = null;
|
||||
_paths = await FilePicker.getMultiFilePath(
|
||||
type: _pickingType, fileExtension: _extension);
|
||||
} else {
|
||||
_paths = null;
|
||||
_path = await FilePicker.getFilePath(
|
||||
type: _pickingType, fileExtension: _extension);
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
print("Unsupported operation" + e.toString());
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_fileName = _path != null
|
||||
? _path.split('/').last
|
||||
: _paths != null ? _paths.keys.toString() : '...';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new MaterialApp(
|
||||
home: new Scaffold(
|
||||
appBar: new AppBar(
|
||||
title: const Text('File Picker example app'),
|
||||
),
|
||||
body: new Center(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
|
||||
child: new SingleChildScrollView(
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: new DropdownButton(
|
||||
hint: new Text('LOAD PATH FROM'),
|
||||
value: _pickingType,
|
||||
items: <DropdownMenuItem>[
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM AUDIO'),
|
||||
value: FileType.AUDIO,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM IMAGE'),
|
||||
value: FileType.IMAGE,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM VIDEO'),
|
||||
value: FileType.VIDEO,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM ANY'),
|
||||
value: FileType.ANY,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('CUSTOM FORMAT'),
|
||||
value: FileType.CUSTOM,
|
||||
),
|
||||
],
|
||||
onChanged: (value) => setState(() {
|
||||
_pickingType = value;
|
||||
if (_pickingType != FileType.CUSTOM) {
|
||||
_controller.text = _extension = '';
|
||||
}
|
||||
})),
|
||||
),
|
||||
new ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: 100.0),
|
||||
child: _pickingType == FileType.CUSTOM
|
||||
? new TextFormField(
|
||||
maxLength: 15,
|
||||
autovalidate: true,
|
||||
controller: _controller,
|
||||
decoration:
|
||||
InputDecoration(labelText: 'File extension'),
|
||||
keyboardType: TextInputType.text,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
validator: (value) {
|
||||
RegExp reg = new RegExp(r'[^a-zA-Z0-9]');
|
||||
if (reg.hasMatch(value)) {
|
||||
_hasValidMime = false;
|
||||
return 'Invalid format';
|
||||
}
|
||||
_hasValidMime = true;
|
||||
return null;
|
||||
},
|
||||
)
|
||||
: new Container(),
|
||||
),
|
||||
new ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: 200.0),
|
||||
child: new SwitchListTile.adaptive(
|
||||
title: new Text('Pick multiple files',
|
||||
textAlign: TextAlign.right),
|
||||
onChanged: (bool value) =>
|
||||
setState(() => _multiPick = value),
|
||||
value: _multiPick,
|
||||
),
|
||||
),
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
|
||||
child: new RaisedButton(
|
||||
onPressed: () => _openFileExplorer(),
|
||||
child: new Text("Open file picker"),
|
||||
),
|
||||
),
|
||||
new Builder(
|
||||
builder: (BuildContext context) =>
|
||||
_path != null || _paths != null
|
||||
? new Container(
|
||||
padding: const EdgeInsets.only(bottom: 30.0),
|
||||
height: MediaQuery.of(context).size.height * 0.50,
|
||||
child: new Scrollbar(
|
||||
child: new ListView.separated(
|
||||
itemCount: _paths != null && _paths.isNotEmpty
|
||||
? _paths.length
|
||||
: 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final bool isMultiPath =
|
||||
_paths != null && _paths.isNotEmpty;
|
||||
final String name = 'File $index: ' +
|
||||
(isMultiPath
|
||||
? _paths.keys.toList()[index]
|
||||
: _fileName ?? '...');
|
||||
final path = isMultiPath
|
||||
? _paths.values.toList()[index].toString()
|
||||
: _path;
|
||||
|
||||
return new ListTile(
|
||||
title: new Text(
|
||||
name,
|
||||
),
|
||||
subtitle: new Text(path),
|
||||
);
|
||||
},
|
||||
separatorBuilder:
|
||||
(BuildContext context, int index) =>
|
||||
new Divider(),
|
||||
)),
|
||||
)
|
||||
: new Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import 'package:file_picker_example/src/file_picker_demo.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
void main() {
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
|
||||
runApp(new FilePickerDemo());
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
class FilePickerDemo extends StatefulWidget {
|
||||
@override
|
||||
_FilePickerDemoState createState() => new _FilePickerDemoState();
|
||||
}
|
||||
|
||||
class _FilePickerDemoState extends State<FilePickerDemo> {
|
||||
String _fileName;
|
||||
String _path;
|
||||
Map<String, String> _paths;
|
||||
String _extension;
|
||||
bool _loadingPath = false;
|
||||
bool _multiPick = false;
|
||||
bool _hasValidMime = false;
|
||||
FileType _pickingType;
|
||||
TextEditingController _controller = new TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.addListener(() => _extension = _controller.text);
|
||||
}
|
||||
|
||||
void _openFileExplorer() async {
|
||||
if (_pickingType != FileType.CUSTOM || _hasValidMime) {
|
||||
setState(() => _loadingPath = true);
|
||||
try {
|
||||
if (_multiPick) {
|
||||
_path = null;
|
||||
_paths = await FilePicker.getMultiFilePath(
|
||||
type: _pickingType, fileExtension: _extension);
|
||||
} else {
|
||||
_paths = null;
|
||||
_path = await FilePicker.getFilePath(
|
||||
type: _pickingType, fileExtension: _extension);
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
print("Unsupported operation" + e.toString());
|
||||
}
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_loadingPath = false;
|
||||
_fileName = _path != null
|
||||
? _path.split('/').last
|
||||
: _paths != null ? _paths.keys.toString() : '...';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new MaterialApp(
|
||||
home: new Scaffold(
|
||||
appBar: new AppBar(
|
||||
title: const Text('File Picker example app'),
|
||||
),
|
||||
body: new Center(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
|
||||
child: new SingleChildScrollView(
|
||||
child: new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: new DropdownButton(
|
||||
hint: new Text('LOAD PATH FROM'),
|
||||
value: _pickingType,
|
||||
items: <DropdownMenuItem>[
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM AUDIO'),
|
||||
value: FileType.AUDIO,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM IMAGE'),
|
||||
value: FileType.IMAGE,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM VIDEO'),
|
||||
value: FileType.VIDEO,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('FROM ANY'),
|
||||
value: FileType.ANY,
|
||||
),
|
||||
new DropdownMenuItem(
|
||||
child: new Text('CUSTOM FORMAT'),
|
||||
value: FileType.CUSTOM,
|
||||
),
|
||||
],
|
||||
onChanged: (value) => setState(() {
|
||||
_pickingType = value;
|
||||
if (_pickingType != FileType.CUSTOM) {
|
||||
_controller.text = _extension = '';
|
||||
}
|
||||
})),
|
||||
),
|
||||
new ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: 100.0),
|
||||
child: _pickingType == FileType.CUSTOM
|
||||
? new TextFormField(
|
||||
maxLength: 15,
|
||||
autovalidate: true,
|
||||
controller: _controller,
|
||||
decoration:
|
||||
InputDecoration(labelText: 'File extension'),
|
||||
keyboardType: TextInputType.text,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
validator: (value) {
|
||||
RegExp reg = new RegExp(r'[^a-zA-Z0-9]');
|
||||
if (reg.hasMatch(value)) {
|
||||
_hasValidMime = false;
|
||||
return 'Invalid format';
|
||||
}
|
||||
_hasValidMime = true;
|
||||
return null;
|
||||
},
|
||||
)
|
||||
: new Container(),
|
||||
),
|
||||
new ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(width: 200.0),
|
||||
child: new SwitchListTile.adaptive(
|
||||
title: new Text('Pick multiple files',
|
||||
textAlign: TextAlign.right),
|
||||
onChanged: (bool value) =>
|
||||
setState(() => _multiPick = value),
|
||||
value: _multiPick,
|
||||
),
|
||||
),
|
||||
new Padding(
|
||||
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
|
||||
child: new RaisedButton(
|
||||
onPressed: () => _openFileExplorer(),
|
||||
child: new Text("Open file picker"),
|
||||
),
|
||||
),
|
||||
new Builder(
|
||||
builder: (BuildContext context) => _loadingPath
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: const CircularProgressIndicator())
|
||||
: _path != null || _paths != null
|
||||
? new Container(
|
||||
padding: const EdgeInsets.only(bottom: 30.0),
|
||||
height: MediaQuery.of(context).size.height * 0.50,
|
||||
child: new Scrollbar(
|
||||
child: new ListView.separated(
|
||||
itemCount: _paths != null && _paths.isNotEmpty
|
||||
? _paths.length
|
||||
: 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final bool isMultiPath =
|
||||
_paths != null && _paths.isNotEmpty;
|
||||
final String name = 'File $index: ' +
|
||||
(isMultiPath
|
||||
? _paths.keys.toList()[index]
|
||||
: _fileName ?? '...');
|
||||
final path = isMultiPath
|
||||
? _paths.values.toList()[index].toString()
|
||||
: _path;
|
||||
|
||||
return new ListTile(
|
||||
title: new Text(
|
||||
name,
|
||||
),
|
||||
subtitle: new Text(path),
|
||||
);
|
||||
},
|
||||
separatorBuilder:
|
||||
(BuildContext context, int index) =>
|
||||
new Divider(),
|
||||
)),
|
||||
)
|
||||
: new Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
# file_picker
|
||||
|
||||
This Go package implements the host-side of the Flutter [file_picker](https://github.com/miguelpruivo/plugins_flutter_file_picker) plugin.
|
||||
|
||||
## Usage
|
||||
|
||||
Modify your applications `options.go`:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
... other imports ....
|
||||
|
||||
"github.com/miguelpruivo/plugins_flutter_file_picker/go"
|
||||
)
|
||||
|
||||
var options = []flutter.Option{
|
||||
... other plugins and options ...
|
||||
|
||||
flutter.AddPlugin(&file_picker.FilePickerPlugin{}),
|
||||
}
|
||||
```
|
|
@ -0,0 +1,52 @@
|
|||
package file_picker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func fileFilter(method string) (string, error) {
|
||||
switch method {
|
||||
case "ANY":
|
||||
return `"public.item"`, nil
|
||||
case "IMAGE":
|
||||
return `"public.image"`, nil
|
||||
case "AUDIO":
|
||||
return `"public.audio"`, nil
|
||||
case "VIDEO":
|
||||
return `"public.movie"`, nil
|
||||
default:
|
||||
if strings.HasPrefix(method, "__CUSTOM_") {
|
||||
resolveType := strings.Split(method, "__CUSTOM_")
|
||||
return `"` + resolveType[1] + `"`, nil
|
||||
}
|
||||
return "", errors.New("unknown method")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func fileDialog(title string, filter string) (string, error) {
|
||||
osascript, err := exec.LookPath("osascript")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
output, err := exec.Command(osascript, "-e", `choose file of type {`+filter+`} with prompt "`+title+`"`).Output()
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
fmt.Printf("miguelpruivo/plugins_flutter_file_picker/go: file dialog exited with code %d and output `%s`\n", exitError.ExitCode(), string(output))
|
||||
return "", nil // user probably canceled or closed the selection window
|
||||
}
|
||||
return "", errors.Wrap(err, "failed to open file dialog")
|
||||
}
|
||||
|
||||
trimmedOutput := strings.TrimSpace(string(output))
|
||||
|
||||
pathParts := strings.Split(trimmedOutput, ":")
|
||||
path := string(filepath.Separator) + filepath.Join(pathParts[1:]...)
|
||||
return path, nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package file_picker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gen2brain/dlgs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func fileFilter(method string) (string, error) {
|
||||
switch method {
|
||||
case "ANY":
|
||||
return `*.*`, nil
|
||||
case "IMAGE":
|
||||
return `*.png *.jpg *.jpeg`, nil
|
||||
case "AUDIO":
|
||||
return `*.mp3`, nil
|
||||
case "VIDEO":
|
||||
return `*.webm *.mpeg *.mkv *.mp4 *.avi *.mov *.flv`, nil
|
||||
default:
|
||||
if strings.HasPrefix(method, "__CUSTOM_") {
|
||||
resolveType := strings.Split(method, "__CUSTOM_")
|
||||
return `*.` + resolveType[1], nil
|
||||
}
|
||||
return "", errors.New("unknown method")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func fileDialog(title string, filter string) (string, error) {
|
||||
filePath, _, err := dlgs.File(title, filter, false)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to open dialog picker")
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// +build !darwin,!linux,!windows
|
||||
|
||||
package file_picker
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func fileFilter(method string) (string, error) {
|
||||
return "", errors.New("platform unsupported")
|
||||
}
|
||||
|
||||
func fileDialog(title string, filter string) (string, error) {
|
||||
return "", errors.New("platform unsupported")
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package file_picker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gen2brain/dlgs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func fileFilter(method string) (string, error) {
|
||||
switch method {
|
||||
case "ANY":
|
||||
return "*", nil
|
||||
case "IMAGE":
|
||||
return "Images (*.jpeg,*.png,*.gif)\x00*.jpg;*.jpeg;*.png;*.gif\x00All Files (*.*)\x00*.*\x00\x00", nil
|
||||
case "AUDIO":
|
||||
return "Audios (*.mp3)\x00*.mp3\x00All Files (*.*)\x00*.*\x00\x00", nil
|
||||
case "VIDEO":
|
||||
return "Videos (*.webm,*.wmv,*.mpeg,*.mkv,*.mp4,*.avi,*.mov,*.flv)\x00*.webm;*.wmv;*.mpeg;*.mkv;*mp4;*.avi;*.mov;*.flv\x00All Files (*.*)\x00*.*\x00\x00", nil
|
||||
default:
|
||||
if strings.HasPrefix(method, "__CUSTOM_") {
|
||||
resolveType := strings.Split(method, "__CUSTOM_")
|
||||
return "Files (*." + resolveType[1] + ")\x00*." + resolveType[1] + "\x00All Files (*.*)\x00*.*\x00\x00", nil
|
||||
}
|
||||
return "", errors.New("unknown method")
|
||||
}
|
||||
}
|
||||
|
||||
func fileDialog(title string, filter string) (string, error) {
|
||||
filePath, _, err := dlgs.File(title, filter, false)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to open dialog picker")
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/miguelpruivo/plugins_flutter_file_picker/go
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/gen2brain/dlgs v0.0.0-20180629122906-342edb4c68c1
|
||||
github.com/go-flutter-desktop/go-flutter v0.27.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gen2brain/dlgs v0.0.0-20180629122906-342edb4c68c1 h1:16o0LcrCHuKADRSOTYxoXTRQpdVo9BDeABauof+9Em8=
|
||||
github.com/gen2brain/dlgs v0.0.0-20180629122906-342edb4c68c1/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU=
|
||||
github.com/go-flutter-desktop/go-flutter v0.27.0 h1:XYhKiRjwX/Z73Hpe+TJDBbutFL1mx1wmy9Po9lux1GU=
|
||||
github.com/go-flutter-desktop/go-flutter v0.27.0/go.mod h1:GZYxHYp7lRnt3imJV1d8EWleMv5q9J4S2ONNEqpPOfo=
|
||||
github.com/go-gl/glfw v0.0.0-20190519095719-e6da0acd62b1 h1:noz9OnjV5PMOZWNOI+y1cS5rnxuJfpY6leIgQEEdBQw=
|
||||
github.com/go-gl/glfw v0.0.0-20190519095719-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
@ -0,0 +1,55 @@
|
|||
package file_picker
|
||||
|
||||
import (
|
||||
"github.com/gen2brain/dlgs"
|
||||
"github.com/go-flutter-desktop/go-flutter"
|
||||
"github.com/go-flutter-desktop/go-flutter/plugin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const channelName = "file_picker"
|
||||
|
||||
type FilePickerPlugin struct{}
|
||||
|
||||
var _ flutter.Plugin = &FilePickerPlugin{} // compile-time type check
|
||||
|
||||
func (p *FilePickerPlugin) InitPlugin(messenger plugin.BinaryMessenger) error {
|
||||
channel := plugin.NewMethodChannel(messenger, channelName, plugin.StandardMethodCodec{})
|
||||
channel.CatchAllHandleFunc(p.handleFilePicker)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *FilePickerPlugin) handleFilePicker(methodCall interface{}) (reply interface{}, err error) {
|
||||
method := methodCall.(plugin.MethodCall)
|
||||
|
||||
filter, err := fileFilter(method.Method)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get filter")
|
||||
}
|
||||
|
||||
selectMultiple, ok := method.Arguments.(bool)
|
||||
if !ok {
|
||||
return nil, errors.Wrap(err, "invalid format for argument, not a bool")
|
||||
}
|
||||
|
||||
if selectMultiple {
|
||||
filePaths, _, err := dlgs.FileMulti("Select one or more files", filter)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to open dialog picker")
|
||||
}
|
||||
|
||||
// type []string is not supported by StandardMessageCodec
|
||||
sliceFilePaths := make([]interface{}, len(filePaths))
|
||||
for i, file := range filePaths {
|
||||
sliceFilePaths[i] = file
|
||||
}
|
||||
|
||||
return sliceFilePaths, nil
|
||||
}
|
||||
|
||||
filePath, err := fileDialog("Select a file", filter)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to open dialog picker")
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
|
@ -47,6 +47,19 @@ class FilePicker {
|
|||
return filePath != null ? File(filePath) : null;
|
||||
}
|
||||
|
||||
/// Returns a `List<File>` object from the selected files paths.
|
||||
///
|
||||
/// This is an utility method that does the same of `getMultiFilePath()` but saving some boilerplate if
|
||||
/// you are planing to create a list of `File`s for the returned paths.
|
||||
static Future<List<File>> getMultiFile(
|
||||
{FileType type = FileType.ANY, String fileExtension}) async {
|
||||
final Map<String, String> paths =
|
||||
await _getPath(_handleType(type, fileExtension), true);
|
||||
return paths != null && paths.isNotEmpty
|
||||
? paths.values.map((path) => File(path)).toList()
|
||||
: null;
|
||||
}
|
||||
|
||||
static Future<dynamic> _getPath(String type, bool multipleSelection) async {
|
||||
try {
|
||||
dynamic result = await _channel.invokeMethod(type, multipleSelection);
|
||||
|
|
|
@ -2,8 +2,7 @@ name: file_picker
|
|||
description: A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support.
|
||||
author: Miguel Ruivo <miguel@miguelruivo.com>
|
||||
homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker
|
||||
version: 1.3.8
|
||||
|
||||
version: 1.4.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
@ -12,10 +11,6 @@ dependencies:
|
|||
environment:
|
||||
sdk: ">=2.0.0 <3.0.0"
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://www.dartlang.org/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter.
|
||||
flutter:
|
||||
plugin:
|
||||
androidPackage: com.mr.flutter.plugin.filepicker
|
||||
|
|
Loading…
Reference in New Issue