Allow picking directory path
Adds support for picking directory paths on both iOS & Android through getDirectoryPath() method.
This commit is contained in:
parent
2214e87c4a
commit
b1f8cd6064
|
@ -1,3 +1,6 @@
|
|||
## 1.10.0
|
||||
**Android & iOS:** Adds `getDirectoryPath()` method that allows you to select and pick directory paths. Android, requires SDK 21 or above for this to work, and iOS requires iOS 11 or above.
|
||||
|
||||
## 1.9.0+1
|
||||
Adds a temporary workaround on Android where it can trigger `onRequestPermissionsResult` twice, related to Flutter issue [49365](https://github.com/flutter/flutter/issues/49365) for anyone affected in Flutter versions below 1.14.6.
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ A package that allows you to use a native file explorer to pick single or multip
|
|||
* 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)
|
||||
* Supports desktop through **go-flutter** (MacOS, Windows, Linux)
|
||||
|
|
|
@ -5,7 +5,9 @@ import android.app.Activity;
|
|||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
@ -85,12 +87,17 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
|
|||
finishWithSuccess(paths.get(0));
|
||||
}
|
||||
} else if (data.getData() != null) {
|
||||
final Uri uri = data.getData();
|
||||
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());
|
||||
String fullPath = FileUtils.getPath(uri, FilePickerDelegate.this.activity);
|
||||
fullPath = FileUtils.getPath(uri, FilePickerDelegate.this.activity);
|
||||
|
||||
if (fullPath == null) {
|
||||
fullPath = FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
|
||||
fullPath = type.equals("dir") ? FileUtils.getFullPathFromTreeUri(uri, activity) : FileUtils.getUriFromRemote(FilePickerDelegate.this.activity, uri);
|
||||
}
|
||||
|
||||
if (fullPath != null) {
|
||||
|
@ -99,6 +106,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
|
|||
} else {
|
||||
finishWithError("unknown_path", "Failed to retrieve path.");
|
||||
}
|
||||
|
||||
} else {
|
||||
finishWithError("unknown_activity", "Unknown activity error, please fill an issue.");
|
||||
}
|
||||
|
@ -160,20 +168,24 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
|
|||
return;
|
||||
}
|
||||
|
||||
intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
final Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
|
||||
Log.d(TAG, "Selected type " + type);
|
||||
intent.setDataAndType(uri, this.type);
|
||||
intent.setType(this.type);
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, this.isMultipleSelection);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
if (type.equals("dir")) {
|
||||
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
} else {
|
||||
intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
final Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
|
||||
Log.d(TAG, "Selected type " + type);
|
||||
intent.setDataAndType(uri, this.type);
|
||||
intent.setType(this.type);
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, this.isMultipleSelection);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
if (type.contains(",")) {
|
||||
allowedExtensions = type.split(",");
|
||||
}
|
||||
if (type.contains(",")) {
|
||||
allowedExtensions = type.split(",");
|
||||
}
|
||||
|
||||
if (allowedExtensions != null) {
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, allowedExtensions);
|
||||
if (allowedExtensions != null) {
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, allowedExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
if (intent.resolveActivity(this.activity.getPackageManager()) != null) {
|
||||
|
|
|
@ -152,13 +152,16 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
|
|||
}
|
||||
|
||||
fileType = FilePickerPlugin.resolveType(call.method);
|
||||
isMultipleSelection = (boolean) arguments.get("allowMultipleSelection");
|
||||
|
||||
final String[] allowedExtensions = FileUtils.getMimeTypes((ArrayList<String>) arguments.get("allowedExtensions"));
|
||||
String[] allowedExtensions = null;
|
||||
|
||||
if (fileType == null) {
|
||||
result.notImplemented();
|
||||
} else if (fileType == "custom" && (allowedExtensions == null || allowedExtensions.length == 0)) {
|
||||
} else if (fileType != "dir") {
|
||||
isMultipleSelection = (boolean) arguments.get("allowMultipleSelection");
|
||||
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);
|
||||
|
@ -168,7 +171,6 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
|
|||
|
||||
private static String resolveType(final String type) {
|
||||
|
||||
|
||||
switch (type) {
|
||||
case "audio":
|
||||
return "audio/*";
|
||||
|
@ -181,6 +183,8 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
|
|||
case "any":
|
||||
case "custom":
|
||||
return "*/*";
|
||||
case "dir":
|
||||
return "dir";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.mr.flutter.plugin.filepicker;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
|
@ -7,23 +8,29 @@ 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;
|
||||
|
@ -53,7 +60,7 @@ 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 Environment.getExternalStorageDirectory() + (split.length > 1 ? ("/" + split[1]) : "");
|
||||
}
|
||||
} else if (isDownloadsDocument(uri)) {
|
||||
Log.e(TAG, "Downloads External Document URI");
|
||||
|
@ -270,6 +277,78 @@ public class FileUtils {
|
|||
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());
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ SPEC CHECKSUMS:
|
|||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
||||
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
|
||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
||||
flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b
|
||||
flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35
|
||||
SDWebImage: 97351f6582ceca541ea294ba66a1fcb342a331c2
|
||||
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
|
||||
4FE42FA345519DC2CF8F7CAB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
620D3249E16C66CF31F39A1D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
|
@ -43,7 +42,6 @@
|
|||
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
|
@ -67,9 +65,7 @@
|
|||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B80C3931E831B6300D905FE /* App.framework */,
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEBA1CF902C7004384FC /* Flutter.framework */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
|
|
|
@ -31,17 +31,11 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
if (_multiPick) {
|
||||
_path = null;
|
||||
_paths = await FilePicker.getMultiFilePath(
|
||||
type: _pickingType,
|
||||
allowedExtensions: (_extension?.isNotEmpty ?? false)
|
||||
? _extension?.replaceAll(' ', '')?.split(',')
|
||||
: null);
|
||||
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);
|
||||
type: _pickingType, allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null);
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
print("Unsupported operation" + e.toString());
|
||||
|
@ -49,9 +43,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
if (!mounted) return;
|
||||
setState(() {
|
||||
_loadingPath = false;
|
||||
_fileName = _path != null
|
||||
? _path.split('/').last
|
||||
: _paths != null ? _paths.keys.toString() : '...';
|
||||
_fileName = _path != null ? _path.split('/').last : _paths != null ? _paths.keys.toString() : '...';
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -60,14 +52,18 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
_scaffoldKey.currentState.showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: result ? Colors.green : Colors.red,
|
||||
content: Text((result
|
||||
? 'Temporary files removed with success.'
|
||||
: 'Failed to clean temporary files')),
|
||||
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(
|
||||
|
@ -128,8 +124,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
maxLength: 15,
|
||||
autovalidate: true,
|
||||
controller: _controller,
|
||||
decoration:
|
||||
InputDecoration(labelText: 'File extension'),
|
||||
decoration: InputDecoration(labelText: 'File extension'),
|
||||
keyboardType: TextInputType.text,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
)
|
||||
|
@ -138,10 +133,8 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
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),
|
||||
title: new Text('Pick multiple files', textAlign: TextAlign.right),
|
||||
onChanged: (bool value) => setState(() => _multiPick = value),
|
||||
value: _multiPick,
|
||||
),
|
||||
),
|
||||
|
@ -153,6 +146,10 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
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"),
|
||||
|
@ -162,28 +159,18 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
),
|
||||
new Builder(
|
||||
builder: (BuildContext context) => _loadingPath
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: const CircularProgressIndicator())
|
||||
? 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,
|
||||
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;
|
||||
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(
|
||||
|
@ -192,9 +179,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
subtitle: new Text(path),
|
||||
);
|
||||
},
|
||||
separatorBuilder:
|
||||
(BuildContext context, int index) =>
|
||||
new Divider(),
|
||||
separatorBuilder: (BuildContext context, int index) => new Divider(),
|
||||
)),
|
||||
)
|
||||
: new Container(),
|
||||
|
|
|
@ -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>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -54,6 +54,11 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if([call.method isEqualToString:@"dir"]) {
|
||||
[self resolvePickDocumentWithMultiPick:NO pickDirectory:YES];
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary * arguments = call.arguments;
|
||||
BOOL isMultiplePick = ((NSNumber*)[arguments valueForKey:@"allowMultipleSelection"]).boolValue;
|
||||
if([call.method isEqualToString:@"any"] || [call.method containsString:@"custom"]) {
|
||||
|
@ -64,7 +69,7 @@
|
|||
details:nil]);
|
||||
_result = nil;
|
||||
} else if(self.allowedExtensions != nil) {
|
||||
[self resolvePickDocumentWithMultipleSelection:isMultiplePick];
|
||||
[self resolvePickDocumentWithMultiPick:isMultiplePick pickDirectory:NO];
|
||||
}
|
||||
} else if([call.method isEqualToString:@"video"] || [call.method isEqualToString:@"image"] || [call.method isEqualToString:@"media"]) {
|
||||
[self resolvePickMedia:[FileUtils resolveMediaType:call.method] withMultiPick:isMultiplePick];
|
||||
|
@ -78,13 +83,12 @@
|
|||
}
|
||||
|
||||
#pragma mark - Resolvers
|
||||
|
||||
- (void)resolvePickDocumentWithMultipleSelection:(BOOL)allowsMultipleSelection {
|
||||
- (void)resolvePickDocumentWithMultiPick:(BOOL)allowsMultipleSelection pickDirectory:(BOOL)isDirectory {
|
||||
|
||||
@try{
|
||||
self.documentPickerController = [[UIDocumentPickerViewController alloc]
|
||||
initWithDocumentTypes: self.allowedExtensions
|
||||
inMode:UIDocumentPickerModeImport];
|
||||
initWithDocumentTypes: isDirectory ? @[@"public.folder"] : self.allowedExtensions
|
||||
inMode: isDirectory ? UIDocumentPickerModeOpen : UIDocumentPickerModeImport];
|
||||
} @catch (NSException * e) {
|
||||
Log(@"Couldn't launch documents file picker. Probably due to iOS version being below 11.0 and not having the iCloud entitlement. If so, just make sure to enable it for your app in Xcode. Exception was: %@", e);
|
||||
_result = nil;
|
||||
|
|
|
@ -15,8 +15,7 @@ enum FileType {
|
|||
|
||||
class FilePicker {
|
||||
FilePicker._();
|
||||
static const MethodChannel _channel =
|
||||
const MethodChannel('miguelruivo.flutter.plugins.filepicker');
|
||||
static const MethodChannel _channel = const MethodChannel('miguelruivo.flutter.plugins.filepicker');
|
||||
static const String _tag = 'FilePicker';
|
||||
|
||||
/// Returns an iterable `Map<String,String>` where the `key` is the name of the file
|
||||
|
@ -25,9 +24,7 @@ class FilePicker {
|
|||
/// A `List` with [allowedExtensions] can be provided to filter the allowed files to picked.
|
||||
/// If provided, make sure you select `FileType.custom` as type.
|
||||
/// Defaults to `FileType.any`, which allows any combination of files to be multi selected at once.
|
||||
static Future<Map<String, String>> getMultiFilePath(
|
||||
{FileType type = FileType.any,
|
||||
List<String> allowedExtensions}) async =>
|
||||
static Future<Map<String, String>> getMultiFilePath({FileType type = FileType.any, List<String> allowedExtensions}) async =>
|
||||
await _getPath(describeEnum(type), true, allowedExtensions);
|
||||
|
||||
/// Returns an absolute file path from the calling platform.
|
||||
|
@ -35,19 +32,15 @@ class FilePicker {
|
|||
/// Extension filters are allowed with `FileType.custom`, when used, make sure to provide a `List`
|
||||
/// of [allowedExtensions] (e.g. [`pdf`, `svg`, `jpg`].).
|
||||
/// Defaults to `FileType.any` which will display all file types.
|
||||
static Future<String> getFilePath(
|
||||
{FileType type = FileType.any,
|
||||
List<String> allowedExtensions}) async =>
|
||||
static Future<String> getFilePath({FileType type = FileType.any, List<String> allowedExtensions}) async =>
|
||||
await _getPath(describeEnum(type), false, allowedExtensions);
|
||||
|
||||
/// Returns a `File` object from the selected file path.
|
||||
///
|
||||
/// This is an utility method that does the same of `getFilePath()` but saving some boilerplate if
|
||||
/// you are planing to create a `File` for the returned path.
|
||||
static Future<File> getFile(
|
||||
{FileType type = FileType.any, List<String> allowedExtensions}) async {
|
||||
final String filePath =
|
||||
await _getPath(describeEnum(type), false, allowedExtensions);
|
||||
static Future<File> getFile({FileType type = FileType.any, List<String> allowedExtensions}) async {
|
||||
final String filePath = await _getPath(describeEnum(type), false, allowedExtensions);
|
||||
return filePath != null ? File(filePath) : null;
|
||||
}
|
||||
|
||||
|
@ -66,20 +59,30 @@ class FilePicker {
|
|||
///
|
||||
/// 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, List<String> allowedExtensions}) async {
|
||||
final Map<String, String> paths =
|
||||
await _getPath(describeEnum(type), true, allowedExtensions);
|
||||
return paths != null && paths.isNotEmpty
|
||||
? paths.values.map((path) => File(path)).toList()
|
||||
: null;
|
||||
static Future<List<File>> getMultiFile({FileType type = FileType.any, List<String> allowedExtensions}) async {
|
||||
final Map<String, String> paths = await _getPath(describeEnum(type), true, allowedExtensions);
|
||||
return paths != null && paths.isNotEmpty ? paths.values.map((path) => File(path)).toList() : null;
|
||||
}
|
||||
|
||||
static Future<dynamic> _getPath(String type, bool allowMultipleSelection,
|
||||
List<String> allowedExtensions) async {
|
||||
/// Selects a directory and returns its absolute path.
|
||||
///
|
||||
/// On Android, this requires to be running on SDK 21 or above, else won't work.
|
||||
/// Returns `null` if folder path couldn't be resolved.
|
||||
static Future<String> getDirectoryPath() async {
|
||||
try {
|
||||
return await _channel.invokeMethod('dir');
|
||||
} on PlatformException catch (ex) {
|
||||
if (ex.code == "unknown_path") {
|
||||
print(
|
||||
'[$_tag] Could not resolve directory path. Maybe it\'s a protected one or unsupported (such as Downloads folder). If you are on Android, make sure that you are on SDK 21 or above.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<dynamic> _getPath(String type, bool allowMultipleSelection, List<String> allowedExtensions) async {
|
||||
if (type != 'custom' && (allowedExtensions?.isNotEmpty ?? false)) {
|
||||
throw Exception(
|
||||
'If you are using a custom extension filter, please use the FileType.custom instead.');
|
||||
throw Exception('If you are using a custom extension filter, please use the FileType.custom instead.');
|
||||
}
|
||||
try {
|
||||
dynamic result = await _channel.invokeMethod(type, {
|
||||
|
@ -90,16 +93,14 @@ class FilePicker {
|
|||
if (result is String) {
|
||||
result = [result];
|
||||
}
|
||||
return Map<String, String>.fromIterable(result,
|
||||
key: (path) => path.split('/').last, value: (path) => path);
|
||||
return Map<String, String>.fromIterable(result, key: (path) => path.split('/').last, value: (path) => path);
|
||||
}
|
||||
return result;
|
||||
} on PlatformException catch (e) {
|
||||
print('[$_tag] Platform exception: $e');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
print(
|
||||
'[$_tag] Unsupported operation. Method not found. The exception thrown was: $e');
|
||||
print('[$_tag] Unsupported operation. Method not found. The exception thrown was: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,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 extension filtering support.
|
||||
homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker
|
||||
version: 1.9.0+1
|
||||
version: 1.10.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
|
Loading…
Reference in New Issue