see changelog (1.3.7) (#102)
This commit is contained in:
parent
b72483dc66
commit
c8f5d4fe69
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,3 +1,15 @@
|
|||
## 1.3.7
|
||||
|
||||
**Rollback - Breaking change:** Re-adds runtime verification for external storage read permission. Don't forget to add the permission to the `AndroidManifest.xml` file as well. More info in the README file.
|
||||
**Bug fix:** Fixes a crash that could cause some Android API to crash when multiple files were selected from external storage.
|
||||
|
||||
## 1.3.6
|
||||
|
||||
**Improvements**
|
||||
* Removes the Android write permissions requirement.
|
||||
* Minor improvements in the example app.
|
||||
* Now the exceptions are rethrown in case the user wants to handle them, despite that already being done in the plugin call.
|
||||
|
||||
## 1.3.5
|
||||
|
||||
**Bug fix:** Fixes an issue that could prevent users to pick files from the iCloud Drive app, on versions below iOS 11.
|
||||
|
|
11
README.md
11
README.md
|
@ -11,15 +11,16 @@ A package that allows you to use a native file explorer to pick single or multip
|
|||
First, add *file_picker* as a dependency in [your pubspec.yaml file](https://flutter.io/platform-plugins/).
|
||||
|
||||
```
|
||||
file_picker: ^1.3.5
|
||||
file_picker: ^1.3.7
|
||||
```
|
||||
### Android
|
||||
|
||||
Add
|
||||
```
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
```
|
||||
before `<application>` to your app's `AndroidManifest.xml` file. This is required due to file caching when a path is required from a remote file (eg. Google Drive).
|
||||
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`:
|
||||
|
@ -108,6 +109,8 @@ String someFilePath = filePaths['fileName']; // Access a file path directly by i
|
|||
* [X] Load path from **any**
|
||||
* [X] Create a `File` object from **any** selected file
|
||||
|
||||
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
|
||||
|
||||
![Demo](https://github.com/miguelpruivo/plugins_flutter_file_picker/blob/master/example/example.gif)
|
||||
|
|
|
@ -30,7 +30,7 @@ public class FilePickerPlugin implements MethodCallHandler {
|
|||
private static final int REQUEST_CODE = (FilePickerPlugin.class.hashCode() + 43) & 0x0000ffff;
|
||||
private static final int PERM_CODE = (FilePickerPlugin.class.hashCode() + 50) & 0x0000ffff;
|
||||
private static final String TAG = "FilePicker";
|
||||
private static final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
private static final String permission = Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
|
||||
private static Result result;
|
||||
private static Registrar instance;
|
||||
|
@ -63,6 +63,9 @@ public class FilePickerPlugin implements MethodCallHandler {
|
|||
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++;
|
||||
|
@ -174,21 +177,26 @@ 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);
|
||||
}
|
||||
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.setDataAndType(uri, type);
|
||||
intent.setType(type);
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultipleSelection);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
|
||||
intent.setDataAndType(uri, type);
|
||||
intent.setType(type);
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultipleSelection);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
|
||||
instance.activity().startActivityForResult(intent, REQUEST_CODE);
|
||||
if (intent.resolveActivity(instance.activity().getPackageManager()) != null) {
|
||||
instance.activity().startActivityForResult(intent, REQUEST_CODE);
|
||||
} else {
|
||||
Log.e(TAG, "Can't find a valid activity to handle the request. Make sure you've a file explorer installed.");
|
||||
result.error(TAG, "Can't handle the provided file type.", null);
|
||||
}
|
||||
} else {
|
||||
requestPermission();
|
||||
requestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,6 @@ import java.io.InputStream;
|
|||
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
/**
|
||||
* Credits to NiRRaNjAN from utils extracted of in.gauriinfotech.commons;.
|
||||
**/
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
private static final String TAG = "FilePickerUtils";
|
||||
|
@ -109,9 +105,11 @@ public class FileUtils {
|
|||
}
|
||||
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
Log.e(TAG, "NO DOCUMENT URI - CONTENT");
|
||||
if (isGooglePhotosUri(uri))
|
||||
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");
|
||||
|
@ -177,11 +175,12 @@ public class FileUtils {
|
|||
|
||||
public static String getUriFromRemote(Context context, Uri uri, MethodChannel.Result result) {
|
||||
|
||||
Log.i(TAG, "Caching file from remote/external URI");
|
||||
FileOutputStream fos = null;
|
||||
String cloudFile = context.getCacheDir().getAbsolutePath() + "/" + FileUtils.getFileName(uri, context);
|
||||
String externalFile = context.getCacheDir().getAbsolutePath() + "/" + FileUtils.getFileName(uri, context);
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(cloudFile);
|
||||
fos = new FileOutputStream(externalFile);
|
||||
try {
|
||||
BufferedOutputStream out = new BufferedOutputStream(fos);
|
||||
InputStream in = context.getContentResolver().openInputStream(uri);
|
||||
|
@ -208,10 +207,13 @@ public class FileUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Remote file loaded and cached at:" + cloudFile);
|
||||
return cloudFile;
|
||||
Log.i(TAG, "File loaded and cached at:" + externalFile);
|
||||
return externalFile;
|
||||
}
|
||||
|
||||
private static boolean isDropBoxUri(Uri uri) {
|
||||
return "com.dropbox.android.FileCache".equals(uri.getAuthority());
|
||||
}
|
||||
|
||||
private static boolean isExternalStorageDocument(Uri uri) {
|
||||
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||
|
|
|
@ -22,7 +22,6 @@ android {
|
|||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "com.mr.flutter.plugin.filepickerexample"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
|
@ -33,7 +32,6 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||
|
|
|
@ -31,10 +31,12 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
try {
|
||||
if (_multiPick) {
|
||||
_path = null;
|
||||
_paths = await FilePicker.getMultiFilePath(type: _pickingType, fileExtension: _extension);
|
||||
_paths = await FilePicker.getMultiFilePath(
|
||||
type: _pickingType, fileExtension: _extension);
|
||||
} else {
|
||||
_paths = null;
|
||||
_path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension);
|
||||
_path = await FilePicker.getFilePath(
|
||||
type: _pickingType, fileExtension: _extension);
|
||||
}
|
||||
} on PlatformException catch (e) {
|
||||
print("Unsupported operation" + e.toString());
|
||||
|
@ -42,7 +44,9 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_fileName = _path != null ? _path.split('/').last : _paths != null ? _paths.keys.toString() : '...';
|
||||
_fileName = _path != null
|
||||
? _path.split('/').last
|
||||
: _paths != null ? _paths.keys.toString() : '...';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -95,14 +99,15 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
}
|
||||
})),
|
||||
),
|
||||
ConstrainedBox(
|
||||
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'),
|
||||
decoration:
|
||||
InputDecoration(labelText: 'File extension'),
|
||||
keyboardType: TextInputType.text,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
validator: (value) {
|
||||
|
@ -112,6 +117,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
return 'Invalid format';
|
||||
}
|
||||
_hasValidMime = true;
|
||||
return null;
|
||||
},
|
||||
)
|
||||
: new Container(),
|
||||
|
@ -119,8 +125,10 @@ 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,
|
||||
),
|
||||
),
|
||||
|
@ -132,30 +140,40 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
|||
),
|
||||
),
|
||||
new Builder(
|
||||
builder: (BuildContext context) => new Container(
|
||||
padding: const EdgeInsets.only(bottom: 30.0),
|
||||
height: MediaQuery.of(context).size.height * 0.50,
|
||||
child: new Scrollbar(
|
||||
child: _path != null || _paths != null
|
||||
? 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;
|
||||
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(),
|
||||
),
|
||||
),
|
||||
return new ListTile(
|
||||
title: new Text(
|
||||
name,
|
||||
),
|
||||
subtitle: new Text(path),
|
||||
);
|
||||
},
|
||||
separatorBuilder:
|
||||
(BuildContext context, int index) =>
|
||||
new Divider(),
|
||||
)),
|
||||
)
|
||||
: new Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/lib" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/example/build" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Flutter Plugins" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,30 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="GEN_FOLDER_RELATIVE_PATH_APT" value="/android/gen" />
|
||||
<option name="GEN_FOLDER_RELATIVE_PATH_AIDL" value="/android/gen" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/android/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/android/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/android/assets" />
|
||||
<option name="LIBS_FOLDER_RELATIVE_PATH" value="/android/libs" />
|
||||
<option name="PROGUARD_LOGS_FOLDER_RELATIVE_PATH" value="/android/proguard_logs" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/android">
|
||||
<sourceFolder url="file://$MODULE_DIR$/android/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/android/gen" isTestSource="false" generated="true" />
|
||||
</content>
|
||||
<content url="file://$MODULE_DIR$/example/android">
|
||||
<sourceFolder url="file://$MODULE_DIR$/example/android/app/src/main/java" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Flutter for Android" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -23,7 +23,8 @@ class FilePicker {
|
|||
/// A [fileExtension] can be provided to filter the picking results.
|
||||
/// If provided, it will be use the `FileType.CUSTOM` for that [fileExtension].
|
||||
/// If not, `FileType.ANY` will be used and any combination of files can be multi picked at once.
|
||||
static Future<Map<String, String>> getMultiFilePath({FileType type = FileType.ANY, String fileExtension}) async =>
|
||||
static Future<Map<String, String>> getMultiFilePath(
|
||||
{FileType type = FileType.ANY, String fileExtension}) async =>
|
||||
await _getPath(_handleType(type, fileExtension), true);
|
||||
|
||||
/// Returns an absolute file path from the calling platform.
|
||||
|
@ -31,15 +32,18 @@ class FilePicker {
|
|||
/// A [type] must be provided to filter the picking results.
|
||||
/// Can be used a custom file type with `FileType.CUSTOM`. A [fileExtension] must be provided (e.g. PDF, SVG, etc.)
|
||||
/// Defaults to `FileType.ANY` which will display all file types.
|
||||
static Future<String> getFilePath({FileType type = FileType.ANY, String fileExtension}) async =>
|
||||
static Future<String> getFilePath(
|
||||
{FileType type = FileType.ANY, String fileExtension}) async =>
|
||||
await _getPath(_handleType(type, fileExtension), false);
|
||||
|
||||
/// 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, String fileExtension}) async {
|
||||
final String filePath = await _getPath(_handleType(type, fileExtension), false);
|
||||
static Future<File> getFile(
|
||||
{FileType type = FileType.ANY, String fileExtension}) async {
|
||||
final String filePath =
|
||||
await _getPath(_handleType(type, fileExtension), false);
|
||||
return filePath != null ? File(filePath) : null;
|
||||
}
|
||||
|
||||
|
@ -50,15 +54,18 @@ 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.toString());
|
||||
print('[$_tag] Platform exception: $e');
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
print('[$_tag] Unsupported operation. Method not found. The exception thrown was: ' + e.toString());
|
||||
print(
|
||||
'[$_tag] Unsupported operation. Method not found. The exception thrown was: $e');
|
||||
rethrow;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String _handleType(FileType type, String fileExtension) {
|
||||
|
|
|
@ -2,7 +2,8 @@ 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.5
|
||||
version: 1.3.7
|
||||
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
|
Loading…
Reference in New Issue