adds multiple file selection option on iOS
This commit is contained in:
parent
a745cdb333
commit
a33ece96ed
|
@ -11,9 +11,11 @@ class FilePickerDemo extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FilePickerDemoState extends State<FilePickerDemo> {
|
class _FilePickerDemoState extends State<FilePickerDemo> {
|
||||||
String _fileName = '...';
|
String _fileName;
|
||||||
String _path = '...';
|
String _path;
|
||||||
|
Map<String, String> _paths;
|
||||||
String _extension;
|
String _extension;
|
||||||
|
bool _multiPick = false;
|
||||||
bool _hasValidMime = false;
|
bool _hasValidMime = false;
|
||||||
FileType _pickingType;
|
FileType _pickingType;
|
||||||
TextEditingController _controller = new TextEditingController();
|
TextEditingController _controller = new TextEditingController();
|
||||||
|
@ -27,7 +29,12 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
||||||
void _openFileExplorer() async {
|
void _openFileExplorer() async {
|
||||||
if (_pickingType != FileType.CUSTOM || _hasValidMime) {
|
if (_pickingType != FileType.CUSTOM || _hasValidMime) {
|
||||||
try {
|
try {
|
||||||
_path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension);
|
if (_multiPick) {
|
||||||
|
_paths = await FilePicker.getMultiFilePath(fileExtension: _extension);
|
||||||
|
print("cenas");
|
||||||
|
} else {
|
||||||
|
_path = await FilePicker.getFilePath(type: _pickingType, fileExtension: _extension);
|
||||||
|
}
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
print("Unsupported operation" + e.toString());
|
print("Unsupported operation" + e.toString());
|
||||||
}
|
}
|
||||||
|
@ -35,7 +42,7 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_fileName = _path != null ? _path.split('/').last : '...';
|
_fileName = _path != null ? _path.split('/').last : _paths != null ? _paths.keys.toString() : '...';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,51 +52,47 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
||||||
return new MaterialApp(
|
return new MaterialApp(
|
||||||
home: new Scaffold(
|
home: new Scaffold(
|
||||||
appBar: new AppBar(
|
appBar: new AppBar(
|
||||||
title: const Text('Plugin example app'),
|
title: const Text('File Picker example app'),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: new Center(
|
child: new Center(
|
||||||
child: new Padding(
|
child: new Padding(
|
||||||
padding: const EdgeInsets.only(top: 50.0, left: 10.0, right: 10.0),
|
padding: const EdgeInsets.only(top: 50.0, left: 10.0, right: 10.0),
|
||||||
child: new Column(
|
child: new ConstrainedBox(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
constraints: new BoxConstraints(maxWidth: 200.0),
|
||||||
children: <Widget>[
|
child: new Column(
|
||||||
new Padding(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
padding: const EdgeInsets.only(top: 20.0),
|
children: <Widget>[
|
||||||
child: new DropdownButton(
|
new Padding(
|
||||||
hint: new Text('LOAD PATH FROM'),
|
padding: const EdgeInsets.only(top: 20.0),
|
||||||
value: _pickingType,
|
child: new DropdownButton(
|
||||||
items: <DropdownMenuItem>[
|
hint: new Text('LOAD PATH FROM'),
|
||||||
// new DropdownMenuItem(
|
value: _pickingType,
|
||||||
// child: new Text('FROM CAMERA'),
|
items: <DropdownMenuItem>[
|
||||||
// value: FileType.CAMERA,
|
new DropdownMenuItem(
|
||||||
// ),
|
child: new Text('FROM AUDIO'),
|
||||||
new DropdownMenuItem(
|
value: FileType.AUDIO,
|
||||||
child: new Text('FROM AUDIO'),
|
),
|
||||||
value: FileType.AUDIO,
|
new DropdownMenuItem(
|
||||||
),
|
child: new Text('FROM GALLERY'),
|
||||||
new DropdownMenuItem(
|
value: FileType.IMAGE,
|
||||||
child: new Text('FROM GALLERY'),
|
),
|
||||||
value: FileType.IMAGE,
|
new DropdownMenuItem(
|
||||||
),
|
child: new Text('FROM VIDEO'),
|
||||||
new DropdownMenuItem(
|
value: FileType.VIDEO,
|
||||||
child: new Text('FROM VIDEO'),
|
),
|
||||||
value: FileType.VIDEO,
|
new DropdownMenuItem(
|
||||||
),
|
child: new Text('FROM ANY'),
|
||||||
new DropdownMenuItem(
|
value: FileType.ANY,
|
||||||
child: new Text('FROM ANY'),
|
),
|
||||||
value: FileType.ANY,
|
new DropdownMenuItem(
|
||||||
),
|
child: new Text('CUSTOM FORMAT'),
|
||||||
new DropdownMenuItem(
|
value: FileType.CUSTOM,
|
||||||
child: new Text('CUSTOM FORMAT'),
|
),
|
||||||
value: FileType.CUSTOM,
|
],
|
||||||
),
|
onChanged: (value) => setState(() => _pickingType = value)),
|
||||||
],
|
),
|
||||||
onChanged: (value) => setState(() => _pickingType = value)),
|
_pickingType == FileType.CUSTOM
|
||||||
),
|
|
||||||
new ConstrainedBox(
|
|
||||||
constraints: new BoxConstraints(maxWidth: 150.0),
|
|
||||||
child: _pickingType == FileType.CUSTOM
|
|
||||||
? new TextFormField(
|
? new TextFormField(
|
||||||
maxLength: 20,
|
maxLength: 20,
|
||||||
autovalidate: true,
|
autovalidate: true,
|
||||||
|
@ -107,38 +110,46 @@ class _FilePickerDemoState extends State<FilePickerDemo> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: new Container(),
|
: new Container(),
|
||||||
),
|
new Visibility(
|
||||||
new Padding(
|
visible: _pickingType == FileType.ANY || _pickingType == FileType.CUSTOM,
|
||||||
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
|
child: new SwitchListTile.adaptive(
|
||||||
child: new RaisedButton(
|
title: new Text('Pick multiple files', textAlign: TextAlign.right),
|
||||||
onPressed: () => _openFileExplorer(),
|
onChanged: (bool value) => setState(() => _multiPick = value),
|
||||||
child: new Text("Open file picker"),
|
value: _multiPick,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
new Padding(
|
||||||
new Text(
|
padding: const EdgeInsets.only(top: 50.0, bottom: 20.0),
|
||||||
'URI PATH ',
|
child: new RaisedButton(
|
||||||
textAlign: TextAlign.center,
|
onPressed: () => _openFileExplorer(),
|
||||||
style: new TextStyle(fontWeight: FontWeight.bold),
|
child: new Text("Open file picker"),
|
||||||
),
|
),
|
||||||
new Text(
|
),
|
||||||
_path ?? '...',
|
new Text(
|
||||||
textAlign: TextAlign.center,
|
'URI PATH ',
|
||||||
softWrap: true,
|
|
||||||
textScaleFactor: 0.85,
|
|
||||||
),
|
|
||||||
new Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 10.0),
|
|
||||||
child: new Text(
|
|
||||||
'FILE NAME ',
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: new TextStyle(fontWeight: FontWeight.bold),
|
style: new TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
new Text(
|
||||||
new Text(
|
_path ?? _paths?.values?.map((path) => path + '\n\n').toString() ?? '...',
|
||||||
_fileName,
|
textAlign: TextAlign.center,
|
||||||
textAlign: TextAlign.center,
|
softWrap: true,
|
||||||
),
|
textScaleFactor: 0.85,
|
||||||
],
|
),
|
||||||
|
new Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 10.0),
|
||||||
|
child: new Text(
|
||||||
|
'FILE NAME ',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: new TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Text(
|
||||||
|
_fileName ?? '...',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
@property (nonatomic) UIViewController *viewController;
|
@property (nonatomic) UIViewController *viewController;
|
||||||
@property (nonatomic) UIImagePickerController *galleryPickerController;
|
@property (nonatomic) UIImagePickerController *galleryPickerController;
|
||||||
@property (nonatomic) UIDocumentPickerViewController *pickerController;
|
@property (nonatomic) UIDocumentPickerViewController *pickerController;
|
||||||
@property (nonatomic) MPMediaPickerController *audioPickerController;
|
|
||||||
@property (nonatomic) UIDocumentInteractionController *interactionController;
|
@property (nonatomic) UIDocumentInteractionController *interactionController;
|
||||||
|
@property (nonatomic) MPMediaPickerController *audioPickerController;
|
||||||
@property (nonatomic) NSString * fileType;
|
@property (nonatomic) NSString * fileType;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
if(self.fileType == nil){
|
if(self.fileType == nil){
|
||||||
result(FlutterMethodNotImplemented);
|
result(FlutterMethodNotImplemented);
|
||||||
} else {
|
} else {
|
||||||
[self resolvePickDocument];
|
[self resolvePickDocumentWithMultipleSelection:call.arguments];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,17 +68,18 @@
|
||||||
|
|
||||||
#pragma mark - Resolvers
|
#pragma mark - Resolvers
|
||||||
|
|
||||||
- (void)resolvePickDocument {
|
- (void)resolvePickDocumentWithMultipleSelection:(BOOL)allowsMultipleSelection {
|
||||||
|
|
||||||
self.pickerController = [[UIDocumentPickerViewController alloc]
|
self.pickerController = [[UIDocumentPickerViewController alloc]
|
||||||
initWithDocumentTypes:@[self.fileType]
|
initWithDocumentTypes:@[self.fileType]
|
||||||
inMode:UIDocumentPickerModeImport];
|
inMode:UIDocumentPickerModeImport];
|
||||||
|
|
||||||
if (@available(iOS 11.0, *)) {
|
if (@available(iOS 11.0, *)) {
|
||||||
self.pickerController.allowsMultipleSelection = NO;
|
self.pickerController.allowsMultipleSelection = allowsMultipleSelection;
|
||||||
} else {
|
} else if(allowsMultipleSelection) {
|
||||||
// Fallback on earlier versions
|
NSLog(@"Multiple file selection is only supported on iOS 11 and above. Single selection will be used.");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pickerController.delegate = self;
|
self.pickerController.delegate = self;
|
||||||
self.pickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
|
self.pickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
|
||||||
self.galleryPickerController.allowsEditing = NO;
|
self.galleryPickerController.allowsEditing = NO;
|
||||||
|
@ -124,7 +125,14 @@
|
||||||
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
|
didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls{
|
||||||
|
|
||||||
[self.pickerController dismissViewControllerAnimated:YES completion:nil];
|
[self.pickerController dismissViewControllerAnimated:YES completion:nil];
|
||||||
_result([FileUtils resolvePath:urls]);
|
NSArray * result = [FileUtils resolvePath:urls];
|
||||||
|
|
||||||
|
if([result count] > 1) {
|
||||||
|
_result(result);
|
||||||
|
} else {
|
||||||
|
_result([result objectAtIndex:0]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#import <MobileCoreServices/MobileCoreServices.h>
|
#import <MobileCoreServices/MobileCoreServices.h>
|
||||||
@interface FileUtils : NSObject
|
@interface FileUtils : NSObject
|
||||||
+ (NSString*) resolveType:(NSString*)type;
|
+ (NSString*) resolveType:(NSString*)type;
|
||||||
+ (NSString*) resolvePath:(NSArray<NSURL *> *)urls;
|
+ (NSArray*) resolvePath:(NSArray<NSURL *> *)urls;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,15 +30,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (NSMutableArray*) resolvePath:(NSArray<NSURL *> *)urls{
|
||||||
+ (NSString*) resolvePath:(NSArray<NSURL *> *)urls{
|
|
||||||
NSString * uri;
|
NSString * uri;
|
||||||
|
NSMutableArray * paths = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
for (NSURL *url in urls) {
|
for (NSURL *url in urls) {
|
||||||
uri = (NSString *)[url path];
|
uri = (NSString *)[url path];
|
||||||
|
[paths addObject:uri];
|
||||||
}
|
}
|
||||||
|
|
||||||
return uri;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
// import 'package:image_picker/image_picker.dart';
|
|
||||||
|
|
||||||
/// Supported file types, [ANY] should be used if the file you need isn't listed
|
String _kCustomType = '__CUSTOM_';
|
||||||
|
|
||||||
enum FileType {
|
enum FileType {
|
||||||
ANY,
|
ANY,
|
||||||
IMAGE,
|
IMAGE,
|
||||||
VIDEO,
|
VIDEO,
|
||||||
AUDIO,
|
AUDIO,
|
||||||
// CAMERA,
|
|
||||||
CUSTOM,
|
CUSTOM,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,27 +16,34 @@ class FilePicker {
|
||||||
static const MethodChannel _channel = const MethodChannel('file_picker');
|
static const MethodChannel _channel = const MethodChannel('file_picker');
|
||||||
static const String _tag = 'FilePicker';
|
static const String _tag = 'FilePicker';
|
||||||
|
|
||||||
static Future<String> _getPath(String type) async {
|
static Future<dynamic> _getPath(String type, [bool multipleSelection = false]) async {
|
||||||
try {
|
try {
|
||||||
return await _channel.invokeMethod(type);
|
dynamic result = await _channel.invokeMethod(type, multipleSelection);
|
||||||
|
if (multipleSelection) {
|
||||||
|
if (result is String) {
|
||||||
|
result = [result];
|
||||||
|
}
|
||||||
|
return Map<String, String>.fromIterable(result, key: (path) => path.split('/').last, value: (path) => path);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
print("[$_tag] Platform exception: " + e.toString());
|
print("[$_tag] Platform exception: " + e.toString());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
print(
|
print(
|
||||||
"[$_tag] Unsupported operation. This probably have happened because [${type.split('_').last}] is an unsupported file type. You may want to try FileType.ALL instead.");
|
"[$_tag] Unsupported operation. This probably have happened because [${type.split('_').last}] is an unsupported file type. You may want to try FileType.ALL instead.");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static Future<String> _getImage(ImageSource type) async {
|
/// Returns an iterable `Map<String,String>` where the `key` is the name of the file
|
||||||
// try {
|
/// and the `value` the path.
|
||||||
// var image = await ImagePicker.pickImage(source: type);
|
///
|
||||||
// return image?.path;
|
/// A [fileExtension] can be provided to filter the picking results.
|
||||||
// } on PlatformException catch (e) {
|
/// If provided, it will be use the `FileType.CUSTOM` for that [fileExtension].
|
||||||
// print("[$_tag] Platform exception: " + e.toString());
|
/// If not, `FileType.ANY` will be used and any combination of files can be multi picked at once.
|
||||||
// }
|
static Future<Map<String, String>> getMultiFilePath({String fileExtension}) async =>
|
||||||
// return null;
|
await _getPath(fileExtension != null ? (_kCustomType + fileExtension) : 'ANY', true);
|
||||||
// }
|
|
||||||
|
|
||||||
/// Returns an absolute file path from the calling platform
|
/// Returns an absolute file path from the calling platform
|
||||||
///
|
///
|
||||||
|
@ -45,21 +51,26 @@ class FilePicker {
|
||||||
/// Can be used a custom file type with `FileType.CUSTOM`. A [fileExtension] must be provided (e.g. PDF, SVG, etc.)
|
/// 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.
|
/// 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 {
|
||||||
|
var path;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case FileType.IMAGE:
|
case FileType.IMAGE:
|
||||||
return _getPath('IMAGE');
|
path = _getPath('IMAGE');
|
||||||
// case FileType.CAMERA:
|
break;
|
||||||
// return _getImage(ImageSource.camera);
|
|
||||||
case FileType.AUDIO:
|
case FileType.AUDIO:
|
||||||
return _getPath('AUDIO');
|
path = _getPath('AUDIO');
|
||||||
|
break;
|
||||||
case FileType.VIDEO:
|
case FileType.VIDEO:
|
||||||
return _getPath('VIDEO');
|
path = _getPath('VIDEO');
|
||||||
|
break;
|
||||||
case FileType.ANY:
|
case FileType.ANY:
|
||||||
return _getPath('ANY');
|
path = _getPath('ANY');
|
||||||
|
break;
|
||||||
case FileType.CUSTOM:
|
case FileType.CUSTOM:
|
||||||
return _getPath('__CUSTOM_' + (fileExtension ?? ''));
|
path = _getPath(_kCustomType + (fileExtension ?? ''));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return _getPath('ANY');
|
break;
|
||||||
}
|
}
|
||||||
|
return await path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue