Adds onLoadingFile parameter to handle file processing status

This commit is contained in:
Miguel Ruivo 2020-06-12 02:19:25 +01:00
parent b1881d521b
commit ffed7696c7
14 changed files with 192 additions and 45 deletions

View File

@ -1,5 +1,8 @@
## 1.11.0
Adds `onFileLoading` handler for every picking method that will provide picking status: `FilePickerStatus.loading` and `FilePickerStatus.done` so you can, for example, display a custom loader.
## 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.
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.

View File

@ -7,7 +7,7 @@
<img alt="Awesome Flutter" src="https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square">
</a>
<a href="https://codemagic.io/apps/5ce89f4a9b46f5000ca89638/5ce89f4a9b46f5000ca89637/latest_build">
<img alt="Build Status" src="https://api.codemagic.io/apps/5ce89f4a9b46f5000ca89638/5ce89f4a9b46f5000ca89637/status_badge.svg">
<img alt="Build Status" src="https://api.codemagic.io/apps/5ee2d379c2d4737a756cbd00/5ee2d3b829657a000c4c4317/status_badge.svg">
</a>
<a href="https://www.buymeacoffee.com/gQyz2MR">
<img alt="Buy me a coffee" src="https://img.shields.io/badge/Donate-Buy%20Me%20A%20Coffee-yellow.svg">
@ -35,15 +35,18 @@ If you have any feature that you want to see in this package, please feel free t
## Documentation
See the **[File Picker Wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki)** for every detail on about how to install, setup and use it.
### File Picker Wiki
1. [Installation](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Installation)
2. [Setup](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup)
* [Android](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup#android)
* [iOS](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup#ios)
* [Web](https://github.com/miguelpruivo/flutter_file_picker/wiki/Setup#--web)
* [Desktop (go-flutter)](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/Setup/_edit#desktop-go-flutter)
3. [API](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/api)
* [Filters](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/API#filters)
* [Parameters](https://github.com/miguelpruivo/flutter_file_picker/wiki/API#parameters)
* [Methods](https://github.com/miguelpruivo/plugins_flutter_file_picker/wiki/API#methods)
4. [Example App](https://github.com/miguelpruivo/flutter_file_picker/blob/master/example/lib/src/file_picker_demo.dart)
5. [Troubleshooting](https://github.com/miguelpruivo/flutter_file_picker/wiki/Troubleshooting)
## Usage

View File

@ -7,6 +7,9 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.DocumentsContract;
import android.util.Log;
@ -16,6 +19,7 @@ import androidx.core.app.ActivityCompat;
import java.io.File;
import java.util.ArrayList;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
@ -30,6 +34,7 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
private boolean isMultipleSelection = false;
private String type;
private String[] allowedExtensions;
private EventChannel.EventSink eventSink;
public FilePickerDelegate(final Activity activity) {
this(
@ -51,6 +56,10 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
);
}
public void setEventHandler(final EventChannel.EventSink eventSink) {
this.eventSink = eventSink;
}
@VisibleForTesting
FilePickerDelegate(final Activity activity, final MethodChannel.Result result, final PermissionManager permissionManager) {
this.activity = activity;
@ -63,6 +72,11 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
public boolean onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (eventSink != null) {
eventSink.success(true);
}
new Thread(new Runnable() {
@Override
public void run() {
@ -217,6 +231,10 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
}
private void finishWithSuccess(final 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) {
this.pendingResult.success(data);
@ -228,10 +246,23 @@ public class FilePickerDelegate implements PluginRegistry.ActivityResultListener
if (this.pendingResult == null) {
return;
}
if (eventSink != null) {
this.dispatchEventStatus(false);
}
this.pendingResult.error(errorCode, errorMessage, null);
this.clearPendingResult();
}
private void dispatchEventStatus(final boolean status) {
new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(final Message message) {
eventSink.success(status);
}
}.obtainMessage().sendToTarget();
}
private void clearPendingResult() {
this.pendingResult = null;

View File

@ -19,6 +19,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
@ -31,6 +32,7 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
private static final String TAG = "FilePicker";
private static final String CHANNEL = "miguelruivo.flutter.plugins.filepicker";
private static final String EVENT_CHANNEL = "miguelruivo.flutter.plugins.filepickerevent";
private class LifeCycleObserver
implements Application.ActivityLifecycleCallbacks, DefaultLifecycleObserver {
@ -249,6 +251,17 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
this.delegate = new FilePickerDelegate(activity);
this.channel = new MethodChannel(messenger, CHANNEL);
this.channel.setMethodCallHandler(this);
new EventChannel(messenger, EVENT_CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
@Override
public void onListen(final Object arguments, final EventChannel.EventSink events) {
delegate.setEventHandler(events);
}
@Override
public void onCancel(final Object arguments) {
delegate.setEventHandler(null);
}
});
this.observer = new LifeCycleObserver(activity);
if (registrar != null) {
// V1 embedding setup for activity listeners.
@ -273,6 +286,7 @@ public class FilePickerPlugin implements MethodChannel.MethodCallHandler, Flutte
this.delegate = null;
this.channel.setMethodCallHandler(null);
this.channel = null;
this.delegate.setEventHandler(null);
this.application.unregisterActivityLifecycleCallbacks(this.observer);
this.application = null;
}

View File

@ -31,11 +31,17 @@ 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());
@ -43,7 +49,9 @@ 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() : '...';
});
}
@ -52,7 +60,9 @@ 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')),
),
);
});
@ -124,7 +134,8 @@ 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,
)
@ -133,8 +144,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,
),
),
@ -159,18 +172,28 @@ 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(
@ -179,7 +202,9 @@ 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(),

View File

@ -4,5 +4,5 @@
#import <Photos/Photos.h>
#import <MobileCoreServices/MobileCoreServices.h>
@interface FilePickerPlugin : NSObject<FlutterPlugin, UIDocumentPickerDelegate, UITabBarDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
@interface FilePickerPlugin : NSObject<FlutterPlugin, FlutterStreamHandler, UIDocumentPickerDelegate, UITabBarDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate>
@end

View File

@ -6,6 +6,7 @@
@interface FilePickerPlugin() <UIImagePickerControllerDelegate, MPMediaPickerControllerDelegate, DKImageAssetExporterObserver>
@property (nonatomic) FlutterResult result;
@property (nonatomic) FlutterEventSink eventSink;
@property (nonatomic) UIViewController *viewController;
@property (nonatomic) UIImagePickerController *galleryPickerController;
@property (nonatomic) UIDocumentPickerViewController *documentPickerController;
@ -21,13 +22,17 @@
methodChannelWithName:@"miguelruivo.flutter.plugins.filepicker"
binaryMessenger:[registrar messenger]];
FlutterEventChannel* eventChannel = [FlutterEventChannel
eventChannelWithName:@"miguelruivo.flutter.plugins.filepickerevent"
binaryMessenger:[registrar messenger]];
UIViewController *viewController = [UIApplication sharedApplication].delegate.window.rootViewController;
FilePickerPlugin* instance = [[FilePickerPlugin alloc] initWithViewController:viewController];
[registrar addMethodCallDelegate:instance channel:channel];
[eventChannel setStreamHandler:instance];
}
- (instancetype)initWithViewController:(UIViewController *)viewController {
self = [super init];
if(self) {
@ -37,6 +42,16 @@
return self;
}
- (FlutterError *)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)events {
_eventSink = events;
return nil;
}
- (FlutterError *)onCancelWithArguments:(id)arguments {
_eventSink = nil;
return nil;
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if (_result) {
result([FlutterError errorWithCode:@"multiple_request"
@ -143,16 +158,20 @@
- (void) resolveMultiPickFromGallery:(MediaType)type {
DKImagePickerController * dkImagePickerController = [[DKImagePickerController alloc] init];
// Create alert dialog for asset caching
UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"" message:@"" preferredStyle:UIAlertControllerStyleAlert];
[alert.view setCenter: _viewController.view.center];
[alert.view addConstraint: [NSLayoutConstraint constraintWithItem:alert.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:100]];
UIActivityIndicatorView* indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
indicator.hidesWhenStopped = YES;
[indicator setCenter: alert.view.center];
indicator.autoresizingMask = (UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin);
[alert.view addSubview: indicator];
if(_eventSink == nil) {
// Create alert dialog for asset caching
[alert.view setCenter: _viewController.view.center];
[alert.view addConstraint: [NSLayoutConstraint constraintWithItem:alert.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:100]];
// Create a default loader if user don't provide a status handler
indicator.hidesWhenStopped = YES;
[indicator setCenter: alert.view.center];
indicator.autoresizingMask = (UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin);
[alert.view addSubview: indicator];
}
dkImagePickerController.exportsWhenCompleted = YES;
dkImagePickerController.showsCancelButton = YES;
@ -164,11 +183,22 @@
if(status == DKImagePickerControllerExportStatusExporting && dkImagePickerController.selectedAssets.count > 0){
Log("Exporting assets, this operation may take a while if remote (iCloud) assets are being cached.");
[indicator startAnimating];
[self->_viewController showViewController:alert sender:nil];
if(self->_eventSink != nil){
self->_eventSink([NSNumber numberWithBool:YES]);
} else {
[indicator startAnimating];
[self->_viewController showViewController:alert sender:nil];
}
} else {
[indicator stopAnimating];
[alert dismissViewControllerAnimated:YES completion:nil];
if(self->_eventSink != nil) {
self->_eventSink([NSNumber numberWithBool:NO]);
} else {
[indicator stopAnimating];
[alert dismissViewControllerAnimated:YES completion:nil];
}
}
}];

View File

@ -3,7 +3,8 @@ import 'dart:io';
import 'package:file_picker_platform_interface/file_picker_platform_interface.dart';
export 'package:file_picker_platform_interface/file_picker_platform_interface.dart' show FileType;
export 'package:file_picker_platform_interface/file_picker_platform_interface.dart'
show FileType;
final FilePickerPlatform _filePickerPlatform = FilePickerPlatform.instance;
@ -14,11 +15,21 @@ class FilePicker {
///
/// Extension filters are allowed with `FileType.custom`, when used, make sure to provide a `List`
/// of [allowedExtensions] (e.g. [`pdf`, `svg`, `jpg`].).
///
/// If you want to track picking status, for example, because some files may take some time to be
/// cached (particularly those picked from cloud providers), you may want to set [onFileLoading] handler
/// that will give you the current status of picking.
///
/// 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,
Function(FilePickerStatus) onFileLoading,
}) async =>
await _filePickerPlatform.getFiles(
type: type,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
);
/// Returns an iterable `Map<String,String>` where the `key` is the name of the file
@ -26,22 +37,37 @@ 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.
///
/// If you want to track picking status, for example, because some files may take some time to be
/// cached (particularly those picked from cloud providers), you may want to set [onFileLoading] handler
/// that will give you the current status of picking.
///
/// 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,
Function(FilePickerStatus) onFileLoading,
}) async =>
await _filePickerPlatform.getFiles(
type: type,
allowMultiple: true,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
);
/// 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 {
static Future<File> getFile({
FileType type = FileType.any,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
}) async {
final String filePath = await _filePickerPlatform.getFiles(
type: type,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
);
return filePath != null ? File(filePath) : null;
}
@ -50,14 +76,21 @@ 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 {
static Future<List<File>> getMultiFile({
FileType type = FileType.any,
List<String> allowedExtensions,
Function(FilePickerStatus) onFileLoading,
}) async {
final Map<String, String> paths = await _filePickerPlatform.getFiles(
type: type,
allowMultiple: true,
allowedExtensions: allowedExtensions,
onFileLoading: onFileLoading,
);
return paths != null && paths.isNotEmpty ? paths.values.map((path) => File(path)).toList() : null;
return paths != null && paths.isNotEmpty
? paths.values.map((path) => File(path)).toList()
: null;
}
/// Selects a directory and returns its absolute path.

View File

@ -1,14 +1,13 @@
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.10.0
version: 1.11.0
dependencies:
flutter:
sdk: flutter
flutter_plugin_android_lifecycle: ^1.0.6
file_picker_platform_interface: ^1.1.0
file_picker_web: ^1.0.0
file_picker_platform_interface: ^1.2.0
environment:
sdk: ">=2.0.0 <3.0.0"

View File

@ -1,3 +1,11 @@
## 1.0.1+1
Updates homepage & description.
## 1.0.1
Updates API to support `onFileLoading` from FilePickerInterface.
## 1.0.0
Adds public API documentation and updates file_picker_platform_interface dependency.

View File

@ -18,7 +18,7 @@ This is what the above means to your `pubspec.yaml`:
...
dependencies:
...
file_picker_web: ^0.0.1
file_picker_web: ^1.0.0
...
```

View File

@ -16,7 +16,7 @@ class _MyAppState extends State<MyApp> {
List<File> _files = [];
void _pickFiles() async {
_files = await FilePicker.getFile() ?? [];
_files = await FilePicker.getMultiFile() ?? [];
setState(() {});
}

View File

@ -40,6 +40,7 @@ class FilePicker extends FilePickerPlatform {
FileType type = FileType.any,
List<String> allowedExtensions,
bool allowMultiple = false,
Function(FilePickerStatus) onFileLoading,
}) async {
final Completer<List<html.File>> pickedFiles = Completer<List<html.File>>();
html.InputElement uploadInput = html.FileUploadInputElement();

View File

@ -1,14 +1,14 @@
name: file_picker_web
description: Web platform implementation of file_picker
homepage: https://github.com/miguelpruivo/plugins_flutter_file_picker/file_picker_web
version: 1.0.0
description: Web platform implementation of file_picker. Provides a way to pick files with filter support for Web.
homepage: https://github.com/miguelpruivo/flutter_file_picker/tree/master/file_picker_web
version: 1.0.1+1
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.10.0"
dependencies:
file_picker_platform_interface: ^1.0.0
file_picker_platform_interface: ^1.2.0
flutter:
sdk: flutter
flutter_web_plugins: