Merge branch 'single-plugin-for-all-platforms'

This commit is contained in:
Miguel Ruivo 2020-09-11 18:52:24 +01:00
commit 4ff33b6acc
142 changed files with 1075 additions and 1307 deletions

View File

@ -1,3 +1,20 @@
## 2.0.0
**Breaking Changes**
- Unifies all platforms (IO, Web and Desktop) in a single plugin (file_picker) that can be used seamlessly across all. Both [file_picker_interface](https://pub.dev/packages/file_picker_platform_interface) and [file_picker_web](https://pub.dev/packages/file_picker_web) are no longer mantained from now on.
- You'll now have to use `FilePicker.platform.pickFiles()` and extract the files from `FilePickerResult`;
- Because platforms are unified and mixing `File` instances from dart:io and dart:html required stubbing and bloated code, it's no longer available as helper method so you'll have to instanciate a `File` with the picked paths;
**New features**
- Simplified usage, now you only need to use `FilePicker.platform.pickFiles()` with desired parameters which will return a `FilePickerResult` with a `List<PlatformFile>` containing the picked data;
- Added classes `FilePickerResult` and `PlatformFile` classes with helper getters;
- On Android all picked files are scoped cached which should result in most of files being available. Caching process is only made once, so once done, the picked instance should be the same;
- On iOS picking audio now supports multiple and cloud picks;
- Added parameter `withData` that allows file data to be immediately available on memory as `Uint8List` (part of `PlatformFile` instance). This is particularly helpful on web or if you are going to upload to somehwere else;
- Major refactor with some clean-up and improvements;
**Removed**
- Single methods such as `getFilePath()`, `getMultiFilePath()`, `getFile()` and `getMultiFile()` are no longer availble in favor o `pickFiles()`;
## 1.13.3
Go: Updates MacOS directory picker to applescript (thank you @trister1997).

View File

@ -18,16 +18,17 @@
A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support.
## Currently supported features
* Load paths from **cloud files** (GDrive, Dropbox, iCloud)
* Load path from a **custom format** by providing a list of file extensions (pdf, svg, zip, etc.)
* Load path from **multiple files** optionally, supplying file extensions
* Load path from **media** (video & image only)
* Load path from **audio** only
* Load path from **image** only
* Load path from **video** only
* Load path from **directory**
* Load path from **any**
* Create a `File` or `List<File>` objects from **any** selected file(s)
* Load files from **cloud files** (GDrive, Dropbox, iCloud)
* Load files from a **custom format** by providing a list of file extensions (pdf, svg, zip, etc.)
* Load files from **multiple files** optionally, supplying file extensions
* Load files from **media** (video & image only)
* Load files from **audio** only
* Load files from **image** only
* Load files from **video** only
* Load files from **directory**
* Load files from **any**
* Load files data immediately to memory (`Uint8List`);
* Supports web;
* Supports desktop through **go-flutter** (MacOS, Windows, Linux)
If you have any feature that you want to see in this package, please feel free to issue a suggestion. 🎉
@ -54,19 +55,42 @@ Quick simple usage example:
#### Single file
```
File file = await FilePicker.getFile();
FilePickerResult result = await FilePicker.platform.pickFiles();
if(result != null) {
File file = File(result.files.single.path);
}
```
#### Multiple files
```
List<File> files = await FilePicker.getMultiFile();
FilePickerResult result = await FilePicker.platform.pickFiles(allowMultiple: true);
if(result != null) {
List<File> files = result.paths.map((path) => File(path));
}
```
#### Multiple files with extension filter
```
List<File> files = await FilePicker.getMultiFile(
FilePickerResult result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'pdf', 'doc'],
);
```
### Load result and file details
```
FilePickerResult result = await FilePicker.platform.pickFiles();
if(result != null) {
PlatformFile file = result.files.first;
print(file.name);
print(file.bytes);
print(file.size);
print(file.extension);
print(file.path);
}
```
For full usage details refer to the **[Wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki)** above.
## Example App

View File

@ -0,0 +1,62 @@
package com.mr.flutter.plugin.filepicker;
import android.net.Uri;
import java.util.HashMap;
public class FileInfo {
final String path;
final String name;
final int size;
final byte[] bytes;
public FileInfo(String path, String name, int size, byte[] bytes) {
this.path = path;
this.name = name;
this.size = size;
this.bytes = bytes;
}
public static class Builder {
private String path;
private String name;
private int size;
private byte[] bytes;
public Builder withPath(String path){
this.path = path;
return this;
}
public Builder withName(String name){
this.name = name;
return this;
}
public Builder withSize(int size){
this.size = size;
return this;
}
public Builder withData(byte[] bytes){
this.bytes = bytes;
return this;
}
public FileInfo build() {
return new FileInfo(this.path, this.name, this.size, this.bytes);
}
}
public HashMap<String, Object> toMap() {
final HashMap<String, Object> data = new HashMap<>();
data.put("path", path);
data.put("name", name);
data.put("size", size);
data.put("bytes", bytes);
return data;
}
}

View File

@ -18,6 +18,7 @@ import androidx.core.app.ActivityCompat;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
@ -32,6 +33,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
private final PermissionManager permissionManager;
private MethodChannel.Result pendingResult;
private boolean isMultipleSelection = false;
private boolean loadDataToMemory = false;
private String type;
private String[] allowedExtensions;
private EventChannel.EventSink eventSink;
@ -81,51 +83,51 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
@Override
public void run() {
if (data != null) {
final ArrayList<FileInfo> files = new ArrayList<>();
if (data.getClipData() != null) {
final int count = data.getClipData().getItemCount();
int currentItem = 0;
final ArrayList<String> paths = new ArrayList<>();
while (currentItem < count) {
final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
String path;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
path = FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, currentUri);
} else {
path = FileUtils.getPath(currentUri, FilePickerDelegate.this.activity);
if (path == null) {
path = FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, currentUri);
}
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, currentUri, loadDataToMemory);
if(file != null) {
files.add(file);
Log.d(FilePickerDelegate.TAG, "[MultiFilePick] File #" + currentItem + " - URI: " + currentUri.getPath());
}
paths.add(path);
Log.i(FilePickerDelegate.TAG, "[MultiFilePick] File #" + currentItem + " - URI: " + currentUri.getPath());
currentItem++;
}
if (paths.size() > 1) {
finishWithSuccess(paths);
} else {
finishWithSuccess(paths.get(0));
}
finishWithSuccess(files);
} else if (data.getData() != null) {
Uri uri = data.getData();
String fullPath;
if (type.equals("dir") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
}
Log.i(FilePickerDelegate.TAG, "[SingleFilePick] File URI:" + uri.toString());
Log.d(FilePickerDelegate.TAG, "[SingleFilePick] File URI:" + uri.toString());
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
fullPath = type.equals("dir") ? FileUtils.getFullPathFromTreeUri(uri, activity) : FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
} else {
fullPath = FileUtils.getPath(uri, FilePickerDelegate.this.activity);
if (fullPath == null) {
fullPath = type.equals("dir") ? FileUtils.getFullPathFromTreeUri(uri, activity) : FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && type.equals("dir")) {
final String dirPath = FileUtils.getFullPathFromTreeUri(uri, activity);
if(dirPath != null) {
finishWithSuccess(dirPath);
} else {
finishWithError("unknown_path", "Failed to retrieve directory path.");
}
return;
}
if (fullPath != null) {
Log.i(FilePickerDelegate.TAG, "Absolute file path:" + fullPath);
finishWithSuccess(fullPath);
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, uri, loadDataToMemory);
if(file != null) {
files.add(file);
}
if (!files.isEmpty()) {
Log.d(FilePickerDelegate.TAG, "File path:" + files.toString());
finishWithSuccess(files);
} else {
finishWithError("unknown_path", "Failed to retrieve path.");
}
@ -138,7 +140,6 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
}
}
}).start();
return true;
} else if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_CANCELED) {
@ -220,7 +221,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
}
@SuppressWarnings("deprecation")
public void startFileExplorer(final String type, final boolean isMultipleSelection, final String[] allowedExtensions, final MethodChannel.Result result) {
public void startFileExplorer(final String type, final boolean isMultipleSelection, final boolean withData, final String[] allowedExtensions, final MethodChannel.Result result) {
if (!this.setPendingMethodCallAndResult(result)) {
finishWithAlreadyActiveError(result);
@ -229,6 +230,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
this.type = type;
this.isMultipleSelection = isMultipleSelection;
this.loadDataToMemory = withData;
this.allowedExtensions = allowedExtensions;
if (!this.permissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) {
@ -239,13 +241,24 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
this.startFileExplorer();
}
private void finishWithSuccess(final Object data) {
@SuppressWarnings("unchecked")
private void finishWithSuccess(Object data) {
if (eventSink != null) {
this.dispatchEventStatus(false);
}
// Temporary fix, remove this null-check after Flutter Engine 1.14 has landed on stable
if (this.pendingResult != null) {
if(data != null && !(data instanceof String)) {
final ArrayList<HashMap<String, Object>> files = new ArrayList<>();
for (FileInfo file : (ArrayList<FileInfo>)data) {
files.add(file.toMap());
}
data = files;
}
this.pendingResult.success(data);
this.clearPendingResult();
}

View File

@ -112,6 +112,7 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
private MethodChannel channel;
private static String fileType;
private static boolean isMultipleSelection = false;
private static boolean withData = false;
/**
* Plugin registration.
@ -160,13 +161,14 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
result.notImplemented();
} else if (fileType != "dir") {
isMultipleSelection = (boolean) arguments.get("allowMultipleSelection");
withData = (boolean) arguments.get("withData");
allowedExtensions = FileUtils.getMimeTypes((ArrayList<String>) arguments.get("allowedExtensions"));
}
if (fileType == "custom" && (allowedExtensions == null || allowedExtensions.length == 0)) {
result.error(TAG, "Unsupported filter. Make sure that you are only using the extension without the dot, (ie., jpg instead of .jpg). This could also have happened because you are using an unsupported file extension. If the problem persists, you may want to consider using FileType.all instead.", null);
} else {
this.delegate.startFileExplorer(fileType, isMultipleSelection, allowedExtensions, result);
this.delegate.startFileExplorer(fileType, isMultipleSelection, withData, allowedExtensions, result);
}
}

View File

@ -0,0 +1,274 @@
package com.mr.flutter.plugin.filepicker;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Random;
public class FileUtils {
private static final String TAG = "FilePickerUtils";
private static final String PRIMARY_VOLUME_NAME = "primary";
public static String[] getMimeTypes(final ArrayList<String> allowedExtensions) {
if (allowedExtensions == null || allowedExtensions.isEmpty()) {
return null;
}
final ArrayList<String> mimes = new ArrayList<>();
for (int i = 0; i < allowedExtensions.size(); i++) {
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(allowedExtensions.get(i));
if (mime == null) {
Log.w(TAG, "Custom file type " + allowedExtensions.get(i) + " is unsupported and will be ignored.");
continue;
}
mimes.add(mime);
}
Log.d(TAG, "Allowed file extensions mimes: " + mimes);
return mimes.toArray(new String[0]);
}
public static String getFileName(Uri uri, final Context context) {
String result = null;
//if uri is content
if (uri.getScheme() != null && uri.getScheme().equals("content")) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
//local filesystem
int index = cursor.getColumnIndex("_data");
if (index == -1)
//google drive
{
index = cursor.getColumnIndex("_display_name");
}
result = cursor.getString(index);
if (result != null) {
uri = Uri.parse(result);
} else {
return null;
}
}
} catch (final Exception ex) {
Log.e(TAG, "Failed to decode file name: " + ex.toString());
} finally {
if (cursor != null) {
cursor.close();
}
}
}
if (uri.getPath() != null) {
result = uri.getPath();
final int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
public static boolean clearCache(final Context context) {
try {
final File cacheDir = new File(context.getCacheDir() + "/file_picker/");
final File[] files = cacheDir.listFiles();
if (files != null) {
for (final File file : files) {
file.delete();
}
}
} catch (final Exception ex) {
Log.e(TAG, "There was an error while clearing cached files: " + ex.toString());
return false;
}
return true;
}
public static FileInfo openFileStream(final Context context, final Uri uri, boolean withData) {
Log.i(TAG, "Caching from URI: " + uri.toString());
FileOutputStream fos = null;
final FileInfo.Builder fileInfo = new FileInfo.Builder();
final String fileName = FileUtils.getFileName(uri, context);
final String path = context.getCacheDir().getAbsolutePath() + "/file_picker/" + (fileName != null ? fileName : new Random().nextInt(100000));
final File file = new File(path);
if(file.exists() && withData) {
int size = (int) file.length();
byte[] bytes = new byte[size];
try {
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
buf.read(bytes, 0, bytes.length);
buf.close();
} catch (FileNotFoundException e) {
Log.e(TAG, "File not found: " + e.getMessage(), null);
} catch (IOException e) {
Log.e(TAG, "Failed to close file streams: " + e.getMessage(), null);
}
fileInfo.withData(bytes);
} else {
file.getParentFile().mkdirs();
try {
fos = new FileOutputStream(path);
try {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final InputStream in = context.getContentResolver().openInputStream(uri);
final byte[] buffer = new byte[8192];
int len = 0;
while ((len = in.read(buffer)) >= 0) {
out.write(buffer, 0, len);
}
if(withData) {
fileInfo.withData(out.toByteArray());
}
out.writeTo(fos);
out.flush();
} finally {
fos.getFD().sync();
}
} catch (final Exception e) {
try {
fos.close();
} catch (final IOException | NullPointerException 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.d(TAG, "File loaded and cached at:" + path);
fileInfo
.withPath(path)
.withName(fileName)
.withSize(Integer.parseInt(String.valueOf(file.length()/1024)));
return fileInfo.build();
}
@Nullable
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
if (treeUri == null) {
return null;
}
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con);
FileInfo.Builder fileInfo = new FileInfo.Builder();
if (volumePath == null) {
return File.separator;
}
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
if (documentPath.startsWith(File.separator)) {
return volumePath + documentPath;
}
else {
return volumePath + File.separator + documentPath;
}
} else {
return volumePath;
}
}
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(final String volumeId, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
}

View File

@ -1,6 +1,6 @@
# file_picker_web_example
# example
Demonstrates how to use the file_picker_web plugin.
A new Flutter project.
## Getting Started

View File

@ -22,7 +22,7 @@ android {
}
defaultConfig {
applicationId "com.mr.flutter.plugin.filepickerexample"
applicationId "com.mr.flutter.plugin.filepicker.example"
minSdkVersion 16
targetSdkVersion 29
versionCode 1

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mr.flutter.plugin.filepicker.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mr.flutter.plugin.filepickerexample"
package="com.mr.flutter.plugin.filepicker.example"
xmlns:tools="http://schemas.android.com/tools">
<!-- The INTERNET permission is required for development. Specifically,

View File

@ -1,4 +1,4 @@
package com.mr.flutter.plugin.filepickerexample;
package com.mr.flutter.plugin.filepicker.example;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;

View File

@ -0,0 +1,6 @@
package com.mr.flutter.plugin.filepicker.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 544 B

View File

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 442 B

View File

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 721 B

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mr.flutter.plugin.filepicker.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -17,7 +17,7 @@ allprojects {
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
options.compilerArgs << "-Xlint:deprecation"
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
}

View File

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 3.0 MiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -0,0 +1 @@
0915ff87e81a3bfb122df4ced418a2b0

View File

@ -164,7 +164,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1010;
LastUpgradeCheck = 1170;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@ -176,10 +176,9 @@
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
@ -310,7 +309,6 @@
/* Begin XCBuildConfiguration section */
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -366,7 +364,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@ -418,6 +415,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
@ -445,6 +443,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

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

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -0,0 +1,16 @@
//
// Generated file. Do not edit.
//
// ignore: unused_import
import 'dart:ui';
import 'package:file_picker/src/file_picker_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// ignore: public_member_api_docs
void registerPlugins(PluginRegistry registry) {
FilePickerWeb.registerWith(registry.registrarFor(FilePickerWeb));
registry.registerMessageHandler();
}

View File

@ -0,0 +1,225 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:file_picker/file_picker.dart';
class FilePickerDemo extends StatefulWidget {
@override
_FilePickerDemoState createState() => _FilePickerDemoState();
}
class _FilePickerDemoState extends State<FilePickerDemo> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String _fileName;
List<PlatformFile> _paths;
String _directoryPath;
String _extension;
bool _loadingPath = false;
bool _multiPick = false;
FileType _pickingType = FileType.any;
TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_controller.addListener(() => _extension = _controller.text);
}
void _openFileExplorer() async {
setState(() => _loadingPath = true);
try {
_directoryPath = null;
_paths = (await FilePicker.platform.pickFiles(
type: _pickingType,
allowMultiple: _multiPick,
allowedExtensions: (_extension?.isNotEmpty ?? false)
? _extension?.replaceAll(' ', '')?.split(',')
: null,
))
?.files;
} on PlatformException catch (e) {
print("Unsupported operation" + e.toString());
} catch (ex) {
print(ex);
}
if (!mounted) return;
setState(() {
_loadingPath = false;
_fileName = _paths != null ? _paths.map((e) => e.name).toString() : '...';
});
}
void _clearCachedFiles() {
FilePicker.platform.clearTemporaryFiles().then((result) {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
backgroundColor: result ? Colors.green : Colors.red,
content: Text((result
? 'Temporary files removed with success.'
: 'Failed to clean temporary files')),
),
);
});
}
void _selectFolder() {
FilePicker.platform.getDirectoryPath().then((value) {
setState(() => _directoryPath = value);
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('File Picker example app'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: DropdownButton(
hint: Text('LOAD PATH FROM'),
value: _pickingType,
items: <DropdownMenuItem>[
DropdownMenuItem(
child: Text('FROM AUDIO'),
value: FileType.audio,
),
DropdownMenuItem(
child: Text('FROM IMAGE'),
value: FileType.image,
),
DropdownMenuItem(
child: Text('FROM VIDEO'),
value: FileType.video,
),
DropdownMenuItem(
child: Text('FROM MEDIA'),
value: FileType.media,
),
DropdownMenuItem(
child: Text('FROM ANY'),
value: FileType.any,
),
DropdownMenuItem(
child: Text('CUSTOM FORMAT'),
value: FileType.custom,
),
],
onChanged: (value) => setState(() {
_pickingType = value;
if (_pickingType != FileType.custom) {
_controller.text = _extension = '';
}
})),
),
ConstrainedBox(
constraints: const BoxConstraints.tightFor(width: 100.0),
child: _pickingType == FileType.custom
? TextFormField(
maxLength: 15,
autovalidate: true,
controller: _controller,
decoration:
InputDecoration(labelText: 'File extension'),
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.none,
)
: const SizedBox(),
),
ConstrainedBox(
constraints: const BoxConstraints.tightFor(width: 200.0),
child: SwitchListTile.adaptive(
title:
Text('Pick multiple files', textAlign: TextAlign.right),
onChanged: (bool value) =>
setState(() => _multiPick = value),
value: _multiPick,
),
),
Padding(
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () => _openFileExplorer(),
child: Text("Open file picker"),
),
RaisedButton(
onPressed: () => _selectFolder(),
child: Text("Pick folder"),
),
RaisedButton(
onPressed: () => _clearCachedFiles(),
child: Text("Clear temporary files"),
),
],
),
),
Builder(
builder: (BuildContext context) => _loadingPath
? Padding(
padding: const EdgeInsets.only(bottom: 10.0),
child: const CircularProgressIndicator(),
)
: _directoryPath != null
? ListTile(
title: Text('Directory path'),
subtitle: Text(_directoryPath),
)
: _paths != null
? Container(
padding: const EdgeInsets.only(bottom: 30.0),
height:
MediaQuery.of(context).size.height * 0.50,
child: Scrollbar(
child: 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
.map((e) => e.name)
.toList()[index]
: _fileName ?? '...');
final path = _paths
.map((e) => e.path)
.toList()[index]
.toString();
return ListTile(
title: Text(
name,
),
subtitle: Text(path),
);
},
separatorBuilder:
(BuildContext context, int index) =>
const Divider(),
)),
)
: const SizedBox(),
),
],
),
),
)),
),
);
}
}

View File

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -3,18 +3,18 @@
<head>
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="Demonstrates how to use the file_picker_web plugin.">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="file_picker_web_example">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="shortcut icon" type="image/png" href="favicon.png"/>
<title>file_picker_web_example</title>
<title>example</title>
<link rel="manifest" href="manifest.json">
</head>
<body>

View File

@ -1,11 +1,11 @@
{
"name": "file_picker_web_example",
"short_name": "file_picker_web_example",
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "minimal-ui",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "Demonstrates how to use the file_picker_web plugin.",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [

View File

@ -1,372 +0,0 @@
package com.mr.flutter.plugin.filepicker;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Random;
public class FileUtils {
private static final String TAG = "FilePickerUtils";
private static final String PRIMARY_VOLUME_NAME = "primary";
public static String getPath(final Uri uri, final Context context) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
if (isKitKat) {
return getForApi19(context, uri);
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
}
return getDataColumn(context, uri, null, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
@TargetApi(19)
@SuppressWarnings("deprecation")
private static String getForApi19(final Context context, final Uri uri) {
Log.e(TAG, "Getting for API 19 or above" + uri);
if (DocumentsContract.isDocumentUri(context, uri)) {
Log.e(TAG, "Document URI");
if (isExternalStorageDocument(uri)) {
Log.e(TAG, "External Document URI");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
Log.e(TAG, "Primary External Document URI");
return Environment.getExternalStorageDirectory() + (split.length > 1 ? ("/" + split[1]) : "");
}
} else if (isDownloadsDocument(uri)) {
Log.e(TAG, "Downloads External Document URI");
String id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
return id.replaceFirst("raw:", "");
}
final String[] contentUriPrefixesToTry = new String[]{
"content://downloads/public_downloads",
"content://downloads/my_downloads",
"content://downloads/all_downloads"
};
if (id.contains(":")) {
id = id.split(":")[1];
}
for (final String contentUriPrefix : contentUriPrefixesToTry) {
try {
final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
final String path = getDataColumn(context, contentUri, null, null);
if (path != null) {
return path;
}
} catch (final Exception e) {
Log.e(TAG, "Something went wrong while retrieving document path: " + e.toString());
return null;
}
}
}
} else if (isMediaDocument(uri)) {
Log.e(TAG, "Media Document URI");
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
Log.i(TAG, "Image Media Document URI");
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
Log.i(TAG, "Video Media Document URI");
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
Log.i(TAG, "Audio Media Document URI");
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = MediaStore.Images.Media._ID + "=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
Log.e(TAG, "NO DOCUMENT URI - CONTENT: " + uri.getPath());
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
} else if (isDropBoxUri(uri)) {
return null;
}
return getDataColumn(context, uri, null, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
Log.e(TAG, "No DOCUMENT URI - FILE: " + uri.getPath());
return uri.getPath();
}
return null;
}
public static String[] getMimeTypes(final ArrayList<String> allowedExtensions) {
if (allowedExtensions == null || allowedExtensions.isEmpty()) {
return null;
}
final ArrayList<String> mimes = new ArrayList<>();
for (int i = 0; i < allowedExtensions.size(); i++) {
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(allowedExtensions.get(i));
if (mime == null) {
Log.w(TAG, "Custom file type " + allowedExtensions.get(i) + " is unsupported and will be ignored.");
continue;
}
mimes.add(mime);
}
Log.d(TAG, "Allowed file extensions mimes: " + mimes);
return mimes.toArray(new String[0]);
}
private static String getDataColumn(final Context context, final Uri uri, final String selection,
final String[] selectionArgs) {
Cursor cursor = null;
final String column = MediaStore.Images.Media.DATA;
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(index);
}
} catch (final Exception ex) {
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
public static String getFileName(Uri uri, final Context context) {
String result = null;
//if uri is content
if (uri.getScheme() != null && uri.getScheme().equals("content")) {
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
//local filesystem
int index = cursor.getColumnIndex("_data");
if (index == -1)
//google drive
{
index = cursor.getColumnIndex("_display_name");
}
result = cursor.getString(index);
if (result != null) {
uri = Uri.parse(result);
} else {
return null;
}
}
} catch (final Exception ex) {
Log.e(TAG, "Failed to decode file name: " + ex.toString());
} finally {
if (cursor != null) {
cursor.close();
}
}
}
if (uri.getPath() != null) {
result = uri.getPath();
final int cut = result.lastIndexOf('/');
if (cut != -1) {
result = result.substring(cut + 1);
}
}
return result;
}
public static boolean clearCache(final Context context) {
try {
final File cacheDir = new File(context.getCacheDir() + "/file_picker/");
final File[] files = cacheDir.listFiles();
if (files != null) {
for (final File file : files) {
file.delete();
}
}
} catch (final Exception ex) {
Log.e(TAG, "There was an error while clearing cached files: " + ex.toString());
return false;
}
return true;
}
public static String getUriFromRemote(final Context context, final Uri uri) {
Log.i(TAG, "Caching file from remote/external URI");
FileOutputStream fos = null;
final String fileName = FileUtils.getFileName(uri, context);
final String externalFile = context.getCacheDir().getAbsolutePath() + "/file_picker/" + (fileName != null ? fileName : new Random().nextInt(100000));
new File(externalFile).getParentFile().mkdirs();
try {
fos = new FileOutputStream(externalFile);
try {
final BufferedOutputStream out = new BufferedOutputStream(fos);
final InputStream in = context.getContentResolver().openInputStream(uri);
final 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 (final Exception e) {
try {
fos.close();
} catch (final IOException | NullPointerException 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, "File loaded and cached at:" + externalFile);
return externalFile;
}
@Nullable
public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
if (treeUri == null) return null;
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri), con);
if (volumePath == null) return File.separator;
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
if (documentPath.startsWith(File.separator))
return volumePath + documentPath;
else
return volumePath + File.separator + documentPath;
} else return volumePath;
}
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(final String volumeId, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
private static boolean isDropBoxUri(final Uri uri) {
return "com.dropbox.android.FileCache".equals(uri.getAuthority());
}
private static boolean isExternalStorageDocument(final Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
private static boolean isDownloadsDocument(final Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
private static boolean isMediaDocument(final Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
private static boolean isGooglePhotosUri(final Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}

View File

@ -1,221 +0,0 @@
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> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String _fileName;
String _path;
Map<String, String> _paths;
String _extension;
bool _loadingPath = false;
bool _multiPick = false;
FileType _pickingType = FileType.any;
TextEditingController _controller = new TextEditingController();
@override
void initState() {
super.initState();
_controller.addListener(() => _extension = _controller.text);
}
void _openFileExplorer() async {
setState(() => _loadingPath = true);
try {
if (_multiPick) {
_path = null;
_paths = await FilePicker.getMultiFilePath(
type: _pickingType,
allowedExtensions: (_extension?.isNotEmpty ?? false)
? _extension?.replaceAll(' ', '')?.split(',')
: null,
);
} else {
_paths = null;
_path = await FilePicker.getFilePath(
type: _pickingType,
allowedExtensions: (_extension?.isNotEmpty ?? false)
? _extension?.replaceAll(' ', '')?.split(',')
: null,
);
}
} 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() : '...';
});
}
void _clearCachedFiles() {
FilePicker.clearTemporaryFiles().then((result) {
_scaffoldKey.currentState.showSnackBar(
SnackBar(
backgroundColor: result ? Colors.green : Colors.red,
content: Text((result
? 'Temporary files removed with success.'
: 'Failed to clean temporary files')),
),
);
});
}
void _selectFolder() {
FilePicker.getDirectoryPath().then((value) {
setState(() => _path = value);
});
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
key: _scaffoldKey,
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 MEDIA'),
value: FileType.media,
),
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,
)
: 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: Column(
children: <Widget>[
new RaisedButton(
onPressed: () => _openFileExplorer(),
child: new Text("Open file picker"),
),
new RaisedButton(
onPressed: () => _selectFolder(),
child: new Text("Pick folder"),
),
new RaisedButton(
onPressed: () => _clearCachedFiles(),
child: new Text("Clear temporary files"),
),
],
),
),
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(),
),
],
),
),
)),
),
);
}
}

Some files were not shown because too many files have changed in this diff Show More