diff --git a/CHANGELOG.md b/CHANGELOG.md index 725daae..aab3f57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 2.0.0 +**Breaking Changes** +- Unifies all platforms (IO, Web and Desktop) in a single plugin (file_picker) that can be used seamlessly across all. Both [file_picker_interface](https://pub.dev/packages/file_picker_platform_interface) and [file_picker_web](https://pub.dev/packages/file_picker_web) are no longer mantained from now on. +- You'll now have to use `FilePicker.platform.pickFiles()` and extract the files from `FilePickerResult`; +- Because platforms are unified and mixing `File` instances from dart:io and dart:html required stubbing and bloated code, it's no longer available as helper method so you'll have to instanciate a `File` with the picked paths; + +**New features** +- Simplified usage, now you only need to use `FilePicker.platform.pickFiles()` with desired parameters which will return a `FilePickerResult` with a `List` containing the picked data; +- Added classes `FilePickerResult` and `PlatformFile` classes with helper getters; +- On Android all picked files are scoped cached which should result in most of files being available. Caching process is only made once, so once done, the picked instance should be the same; +- On iOS picking audio now supports multiple and cloud picks; +- Added parameter `withData` that allows file data to be immediately available on memory as `Uint8List` (part of `PlatformFile` instance). This is particularly helpful on web or if you are going to upload to somehwere else; +- Major refactor with some clean-up and improvements; + +**Removed** +- Single methods such as `getFilePath()`, `getMultiFilePath()`, `getFile()` and `getMultiFile()` are no longer availble in favor o `pickFiles()`; + ## 1.13.3 Go: Updates MacOS directory picker to applescript (thank you @trister1997). diff --git a/README.md b/README.md index 74ee6c3..6b61dac 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,17 @@ A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extensions filtering support. ## Currently supported features -* Load paths from **cloud files** (GDrive, Dropbox, iCloud) -* Load path from a **custom format** by providing a list of file extensions (pdf, svg, zip, etc.) -* Load path from **multiple files** optionally, supplying file extensions -* Load path from **media** (video & image only) -* 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` objects from **any** selected file(s) +* Load files from **cloud files** (GDrive, Dropbox, iCloud) +* Load files from a **custom format** by providing a list of file extensions (pdf, svg, zip, etc.) +* Load files from **multiple files** optionally, supplying file extensions +* Load files from **media** (video & image only) +* Load files from **audio** only +* Load files from **image** only +* Load files from **video** only +* Load files from **directory** +* Load files from **any** +* Load files data immediately to memory (`Uint8List`); +* Supports web; * Supports desktop through **go-flutter** (MacOS, Windows, Linux) If you have any feature that you want to see in this package, please feel free to issue a suggestion. 🎉 @@ -54,19 +55,42 @@ Quick simple usage example: #### Single file ``` -File file = await FilePicker.getFile(); +FilePickerResult result = await FilePicker.platform.pickFiles(); + +if(result != null) { + File file = File(result.files.single.path); +} ``` #### Multiple files ``` -List files = await FilePicker.getMultiFile(); +FilePickerResult result = await FilePicker.platform.pickFiles(allowMultiple: true); + +if(result != null) { + List files = result.paths.map((path) => File(path)); +} ``` #### Multiple files with extension filter ``` - List files = await FilePicker.getMultiFile( +FilePickerResult result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['jpg', 'pdf', 'doc'], ); ``` +### Load result and file details +``` +FilePickerResult result = await FilePicker.platform.pickFiles(); + +if(result != null) { + PlatformFile file = result.files.first; + + print(file.name); + print(file.bytes); + print(file.size); + print(file.extension); + print(file.path); +} +``` + For full usage details refer to the **[Wiki](https://github.com/miguelpruivo/flutter_file_picker/wiki)** above. ## Example App diff --git a/example/lib/src/file_picker_demo.dart b/example/lib/src/file_picker_demo.dart index 2b114e4..c67d222 100644 --- a/example/lib/src/file_picker_demo.dart +++ b/example/lib/src/file_picker_demo.dart @@ -32,7 +32,9 @@ class _FilePickerDemoState extends State { _paths = (await FilePicker.platform.pickFiles( type: _pickingType, allowMultiple: _multiPick, - allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null, + allowedExtensions: (_extension?.isNotEmpty ?? false) + ? _extension?.replaceAll(' ', '')?.split(',') + : null, )) ?.files; } on PlatformException catch (e) { @@ -52,7 +54,9 @@ class _FilePickerDemoState extends State { _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 +128,8 @@ class _FilePickerDemoState extends State { maxLength: 15, autovalidate: true, controller: _controller, - decoration: InputDecoration(labelText: 'File extension'), + decoration: + InputDecoration(labelText: 'File extension'), keyboardType: TextInputType.text, textCapitalization: TextCapitalization.none, ) @@ -133,8 +138,10 @@ class _FilePickerDemoState extends State { ConstrainedBox( constraints: const BoxConstraints.tightFor(width: 200.0), child: SwitchListTile.adaptive( - title: Text('Pick multiple files', textAlign: TextAlign.right), - onChanged: (bool value) => setState(() => _multiPick = value), + title: + Text('Pick multiple files', textAlign: TextAlign.right), + onChanged: (bool value) => + setState(() => _multiPick = value), value: _multiPick, ), ), @@ -171,15 +178,28 @@ class _FilePickerDemoState extends State { : _paths != null ? Container( padding: const EdgeInsets.only(bottom: 30.0), - height: MediaQuery.of(context).size.height * 0.50, + height: + MediaQuery.of(context).size.height * 0.50, child: Scrollbar( child: 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.map((e) => e.name).toList()[index] : _fileName ?? '...'); - final path = _paths.map((e) => e.path).toList()[index].toString(); + 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 + .map((e) => e.name) + .toList()[index] + : _fileName ?? '...'); + final path = _paths + .map((e) => e.path) + .toList()[index] + .toString(); return ListTile( title: Text( @@ -188,7 +208,9 @@ class _FilePickerDemoState extends State { subtitle: Text(path), ); }, - separatorBuilder: (BuildContext context, int index) => const Divider(), + separatorBuilder: + (BuildContext context, int index) => + const Divider(), )), ) : const SizedBox(), diff --git a/lib/src/file_picker.dart b/lib/src/file_picker.dart index d1646f2..cd16d75 100644 --- a/lib/src/file_picker.dart +++ b/lib/src/file_picker.dart @@ -43,12 +43,20 @@ abstract class FilePicker extends PlatformInterface { /// Retrieves the file(s) from the underlying platform /// - /// Default [type] set to `FileType.any` with [allowMultiple] set to `false` - /// Optionally, [allowedExtensions] might be provided (e.g. [`pdf`, `svg`, `jpg`].). + /// Default [type] set to [FileType.any] with [allowMultiple] set to [false] + /// Optionally, [allowedExtensions] might be provided (e.g. `[pdf, svg, jpg]`.). + /// + /// If [withData] is set, picked files will have its byte data immediately available on memory as [Uint8List] + /// which can be useful if you are picking it for server upload or similar. /// /// 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. + /// + /// The result is wrapped in a [FilePickerResult] which contains helper getters + /// with useful information regarding the picked [List]. + /// + /// Returns [null] if aborted. Future pickFiles({ FileType type = FileType.any, List allowedExtensions, @@ -65,12 +73,14 @@ abstract class FilePicker extends PlatformInterface { /// each platform and it isn't required to invoke this as the system should take care /// of it whenever needed. However, this will force the cleanup if you want to manage those on your own. /// - /// Returns `true` if the files were removed with success, `false` otherwise. - Future clearTemporaryFiles() async => throw UnimplementedError('clearTemporaryFiles() has not been implemented.'); + /// Returns [true] if the files were removed with success, [false] otherwise. + Future clearTemporaryFiles() async => throw UnimplementedError( + 'clearTemporaryFiles() has not been implemented.'); /// 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. - Future getDirectoryPath() async => throw UnimplementedError('getDirectoryPath() has not been implemented.'); + /// Returns [null] if folder path couldn't be resolved. + Future getDirectoryPath() async => + throw UnimplementedError('getDirectoryPath() has not been implemented.'); } diff --git a/lib/src/file_picker_io.dart b/lib/src/file_picker_io.dart index d41860a..0d3dfbd 100644 --- a/lib/src/file_picker_io.dart +++ b/lib/src/file_picker_io.dart @@ -7,8 +7,10 @@ import 'package:flutter/services.dart'; import 'file_picker_result.dart'; -const MethodChannel _channel = MethodChannel('miguelruivo.flutter.plugins.filepicker'); -const EventChannel _eventChannel = EventChannel('miguelruivo.flutter.plugins.filepickerevent'); +const MethodChannel _channel = + MethodChannel('miguelruivo.flutter.plugins.filepicker'); +const EventChannel _eventChannel = + EventChannel('miguelruivo.flutter.plugins.filepickerevent'); /// An implementation of [FilePicker] that uses method channels. class FilePickerIO extends FilePicker { @@ -34,7 +36,8 @@ class FilePickerIO extends FilePicker { ); @override - Future clearTemporaryFiles() async => _channel.invokeMethod('clear'); + Future clearTemporaryFiles() async => + _channel.invokeMethod('clear'); @override Future getDirectoryPath() async { @@ -59,13 +62,16 @@ class FilePickerIO extends FilePicker { ) async { final String type = describeEnum(fileType); 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 { _eventSubscription?.cancel(); if (onFileLoading != null) { _eventSubscription = _eventChannel.receiveBroadcastStream().listen( - (data) => onFileLoading((data as bool) ? FilePickerStatus.picking : FilePickerStatus.done), + (data) => onFileLoading((data as bool) + ? FilePickerStatus.picking + : FilePickerStatus.done), onError: (error) => throw Exception(error), ); } @@ -81,12 +87,14 @@ class FilePickerIO extends FilePicker { return null; } - return FilePickerResult(result.map((file) => PlatformFile.fromMap(file)).toList()); + return FilePickerResult( + result.map((file) => PlatformFile.fromMap(file)).toList()); } 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; } } diff --git a/lib/src/file_picker_result.dart b/lib/src/file_picker_result.dart index b3b5cfc..30432ee 100644 --- a/lib/src/file_picker_result.dart +++ b/lib/src/file_picker_result.dart @@ -19,7 +19,10 @@ class FilePickerResult { /// original files (which can be accessed through its URI property). /// /// Only available on IO. Throws `UnsupportedError` on Web. - List get paths => files.map((file) => kIsWeb ? throw UnsupportedError('Unsupported on Web') : file.path).toList(); + List get paths => files + .map((file) => + kIsWeb ? throw UnsupportedError('Unsupported on Web') : file.path) + .toList(); /// A `List` containing all names from picked files with its extensions. List get names => files.map((file) => file.name).toList(); diff --git a/lib/src/file_picker_web.dart b/lib/src/file_picker_web.dart index 6d951a0..025b92f 100644 --- a/lib/src/file_picker_web.dart +++ b/lib/src/file_picker_web.dart @@ -26,7 +26,8 @@ class FilePickerWeb extends FilePicker { bool allowCompression, bool withData = true, }) async { - final Completer> filesCompleter = Completer>(); + final Completer> filesCompleter = + Completer>(); String accept = _fileType(type, allowedExtensions); html.InputElement uploadInput = html.FileUploadInputElement(); @@ -42,7 +43,8 @@ class FilePickerWeb extends FilePicker { List pickedFiles = []; reader.onLoadEnd.listen((e) { - final Uint8List bytes = Base64Decoder().convert(reader.result.toString().split(",").last); + final Uint8List bytes = + Base64Decoder().convert(reader.result.toString().split(",").last); pickedFiles.add( PlatformFile( @@ -83,7 +85,8 @@ class FilePickerWeb extends FilePicker { return 'video/*|image/*'; case FileType.custom: - return allowedExtensions.fold('', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next'); + return allowedExtensions.fold( + '', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next'); break; } return ''; diff --git a/lib/src/platform_file.dart b/lib/src/platform_file.dart index 435e14f..2ddebd2 100644 --- a/lib/src/platform_file.dart +++ b/lib/src/platform_file.dart @@ -6,15 +6,13 @@ class PlatformFile { this.name, this.bytes, this.size, - this.isDirectory = false, }); PlatformFile.fromMap(Map data) : this.path = data['path'], this.name = data['name'], this.bytes = data['bytes'], - this.size = data['size'], - this.isDirectory = data['isDirectory']; + this.size = data['size']; /// The absolute path for a cached copy of this file. It can be used to create a /// a file instance with a descriptor for the given path. @@ -33,9 +31,6 @@ class PlatformFile { /// The file size in KB. final int size; - /// Whether this file references a directory or not. - final bool isDirectory; - /// File extension for this file. String get extension => path?.split('.')?.last; }