adds multiple file selection option on iOS

This commit is contained in:
Miguel Ruivo 2019-03-06 01:16:35 +00:00
parent a745cdb333
commit a33ece96ed
5 changed files with 136 additions and 105 deletions

View File

@ -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,
),
],
),
), ),
)), )),
), ),

View File

@ -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]);
}
} }

View File

@ -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

View File

@ -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

View File

@ -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;
} }
} }